summaryrefslogtreecommitdiffstats
path: root/libnetdata
diff options
context:
space:
mode:
authorAndrew Moss <1043609+amoss@users.noreply.github.com>2019-09-18 16:45:52 +0200
committerChris Akritidis <43294513+cakrit@users.noreply.github.com>2019-09-18 16:45:52 +0200
commit187620911c06585882350ff871ef49bb6e6acd1e (patch)
treec47356692e6e9378b2e9767131e745340cf2f9ba /libnetdata
parent217cd7b939218cf00b11addd6a75df1468c32ee5 (diff)
Allow hostnames in Access Control Lists (#6796)
##### Summary The Access Control List (ACL) configuration parameters can now use hostnames with simple patterns. Incoming connections are resolved using reverse DNS to obtain the hostname. Where a hostname is resolved, forward DNS resolution is performed to check the IP address is really associated with the hostname. If the checks pass then the patterns supplied are checked. Any patterns supplied for numeric ip addresses are also checked. ##### Component Name daemon ##### Additional Information Fixes #6438 * Reverse lookup on ip to get hostname * Forward lookup on hostname to get IP addresses. * Validation that the incomming ip is associated with the host. If these checks fail the hostname is discarded so it cannot match against the access-list patterns. If these checks validate the ip successfully then the resolved hostname is pattern-matched as described in the previous commit.
Diffstat (limited to 'libnetdata')
-rw-r--r--libnetdata/socket/socket.c127
-rw-r--r--libnetdata/socket/socket.h11
2 files changed, 112 insertions, 26 deletions
diff --git a/libnetdata/socket/socket.c b/libnetdata/socket/socket.c
index 22abb47f4b..ee38a4ca89 100644
--- a/libnetdata/socket/socket.c
+++ b/libnetdata/socket/socket.c
@@ -995,21 +995,103 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) {
}
#endif
+/*
+ * ---------------------------------------------------------------------------------------------------------------------
+ * connection_allowed() - if there is an access list then check the connection matches a pattern.
+ * Numeric patterns are checked against the IP address first, only if they
+ * do not match is the hostname resolved (reverse-DNS) and checked. If the
+ * hostname matches then we perform forward DNS resolution to check the IP
+ * is really associated with the DNS record. This call is repeatable: the
+ * web server may check more refined matches against the connection. Will
+ * update the client_host if uninitialized - ensure the hostsize is the number
+ * of *writable* bytes (i.e. be aware of the strdup used to compact the pollinfo).
+ */
+extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list,
+ const char *patname) {
+ if (!access_list)
+ return 1;
+ if (simple_pattern_matches(access_list, client_ip))
+ return 1;
+ // If the hostname is unresolved (and needed) then attempt the DNS lookups.
+ if (client_host[0]==0)
+ {
+ struct sockaddr_storage sadr;
+ socklen_t addrlen = sizeof(sadr);
+ int err = getpeername(fd, (struct sockaddr*)&sadr, &addrlen);
+ if (err != 0 ||
+ (err = getnameinfo((struct sockaddr *)&sadr, addrlen, client_host, (socklen_t)hostsize,
+ NULL, 0, NI_NAMEREQD)) != 0) {
+ error("Incoming connection on '%s' does not match a numeric pattern, "
+ "and host could not be resolved (err=%s)", client_ip, gai_strerror(err));
+ if (hostsize >= 8)
+ strcpy(client_host,"UNKNOWN");
+ return 0;
+ }
+ struct addrinfo *addr_infos = NULL;
+ if (getaddrinfo(client_host, NULL, NULL, &addr_infos) !=0 ) {
+ error("LISTENER: cannot validate hostname '%s' from '%s' by resolving it",
+ client_host, client_ip);
+ if (hostsize >= 8)
+ strcpy(client_host,"UNKNOWN");
+ return 0;
+ }
+ struct addrinfo *scan = addr_infos;
+ int validated = 0;
+ while (scan) {
+ char address[INET6_ADDRSTRLEN];
+ address[0] = 0;
+ switch (scan->ai_addr->sa_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &((struct sockaddr_in*)(scan->ai_addr))->sin_addr, address, INET6_ADDRSTRLEN);
+ break;
+ case AF_INET6:
+ inet_ntop(AF_INET6, &((struct sockaddr_in6*)(scan->ai_addr))->sin6_addr, address, INET6_ADDRSTRLEN);
+ break;
+ }
+ debug(D_LISTENER, "Incoming ip %s rev-resolved onto %s, validating against forward-resolution %s",
+ client_ip, client_host, address);
+ if (!strcmp(client_ip, address)) {
+ validated = 1;
+ break;
+ }
+ scan = scan->ai_next;
+ }
+ if (!validated) {
+ error("LISTENER: Cannot validate '%s' as ip of '%s', not listed in DNS", client_ip, client_host);
+ if (hostsize >= 8)
+ strcpy(client_host,"UNKNOWN");
+ }
+ if (addr_infos!=NULL)
+ freeaddrinfo(addr_infos);
+ }
+ if (!simple_pattern_matches(access_list, client_host)) {
+ debug(D_LISTENER, "Incoming connection on '%s' (%s) does not match allowed pattern for %s",
+ client_ip, client_host, patname);
+ return 0;
+ }
+ return 1;
+}
// --------------------------------------------------------------------------------------------------------------------
// accept_socket() - accept a socket and store client IP and port
-int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize, SIMPLE_PATTERN *access_list) {
+int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
+ char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list) {
struct sockaddr_storage sadr;
socklen_t addrlen = sizeof(sadr);
int nfd = accept4(fd, (struct sockaddr *)&sadr, &addrlen, flags);
if (likely(nfd >= 0)) {
- if (getnameinfo((struct sockaddr *)&sadr, addrlen, client_ip, (socklen_t)ipsize, client_port, (socklen_t)portsize, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+ if (getnameinfo((struct sockaddr *)&sadr, addrlen, client_ip, (socklen_t)ipsize,
+ client_port, (socklen_t)portsize, NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
error("LISTENER: cannot getnameinfo() on received client connection.");
strncpyz(client_ip, "UNKNOWN", ipsize - 1);
strncpyz(client_port, "UNKNOWN", portsize - 1);
}
+ if(!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
+ strncpy(client_ip, "localhost", ipsize);
+ client_ip[ipsize - 1] = '\0';
+ }
#ifdef __FreeBSD__
if(((struct sockaddr *)&sadr)->sa_family == AF_LOCAL)
@@ -1044,21 +1126,12 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
debug(D_LISTENER, "New UNKNOWN web client from %s port %s on socket %d.", client_ip, client_port, fd);
break;
}
-
- if(access_list) {
- if(!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
- strncpy(client_ip, "localhost", ipsize);
- client_ip[ipsize - 1] = '\0';
- }
-
- if(unlikely(!simple_pattern_matches(access_list, client_ip))) {
- errno = 0;
- debug(D_LISTENER, "Permission denied for client '%s', port '%s'", client_ip, client_port);
- error("DENIED ACCESS to client '%s'", client_ip);
- close(nfd);
- nfd = -1;
- errno = EPERM;
- }
+ if(!connection_allowed(nfd, client_ip, client_host, hostsize, access_list, "connection")) {
+ errno = 0;
+ error("Permission denied for client '%s', port '%s'", client_ip, client_port);
+ close(nfd);
+ nfd = -1;
+ errno = EPERM;
}
}
#ifdef HAVE_ACCEPT4
@@ -1084,6 +1157,7 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
, uint32_t flags
, const char *client_ip
, const char *client_port
+ , const char *client_host
, void *(*add_callback)(POLLINFO * /*pi*/, short int * /*events*/, void * /*data*/)
, void (*del_callback)(POLLINFO * /*pi*/)
, int (*rcv_callback)(POLLINFO * /*pi*/, short int * /*events*/)
@@ -1123,6 +1197,7 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
p->inf[i].client_ip = NULL;
p->inf[i].client_port = NULL;
+ p->inf[i].client_host = NULL;
p->inf[i].del_callback = p->del_callback;
p->inf[i].rcv_callback = p->rcv_callback;
p->inf[i].snd_callback = p->snd_callback;
@@ -1153,8 +1228,9 @@ inline POLLINFO *poll_add_fd(POLLJOB *p
pi->port_acl = port_acl;
pi->flags = flags;
pi->next = NULL;
- pi->client_ip = strdupz(client_ip);
+ pi->client_ip = strdupz(client_ip);
pi->client_port = strdupz(client_port);
+ pi->client_host = strdupz(client_host);
pi->del_callback = del_callback;
pi->rcv_callback = rcv_callback;
@@ -1356,13 +1432,16 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
int nfd;
do {
- char client_ip[NI_MAXHOST + 1];
- char client_port[NI_MAXSERV + 1];
- client_ip[0] = 0x00;
- client_port[0] = 0x00;
+ char client_ip[INET6_ADDRSTRLEN];
+ char client_port[NI_MAXSERV];
+ char client_host[NI_MAXHOST];
+ client_host[0] = 0;
+ client_ip[0] = 0;
+ client_port[0] = 0;
debug(D_POLLFD, "POLLFD: LISTENER: calling accept4() slot %zu (fd %d)", i, fd);
- nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, NI_MAXHOST + 1, client_port, NI_MAXSERV + 1, p->access_list);
+ nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, INET6_ADDRSTRLEN, client_port, NI_MAXSERV,
+ client_host, NI_MAXHOST, p->access_list);
if (unlikely(nfd < 0)) {
// accept failed
@@ -1387,6 +1466,7 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
, POLLINFO_FLAG_CLIENT_SOCKET
, client_ip
, client_port
+ , client_host
, p->add_callback
, p->del_callback
, p->rcv_callback
@@ -1530,6 +1610,7 @@ void poll_events(LISTEN_SOCKETS *sockets
, POLLINFO_FLAG_SERVER_SOCKET
, (sockets->fds_names[i])?sockets->fds_names[i]:"UNKNOWN"
, ""
+ , ""
, p.add_callback
, p.del_callback
, p.rcv_callback
diff --git a/libnetdata/socket/socket.h b/libnetdata/socket/socket.h
index 76b15def52..227f054345 100644
--- a/libnetdata/socket/socket.h
+++ b/libnetdata/socket/socket.h
@@ -72,7 +72,10 @@ extern int sock_setreuse_port(int fd, int reuse);
extern int sock_enlarge_in(int fd);
extern int sock_enlarge_out(int fd);
-extern int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize, SIMPLE_PATTERN *access_list);
+extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize,
+ SIMPLE_PATTERN *access_list, const char *patname);
+extern int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
+ char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list);
#ifndef HAVE_ACCEPT4
extern int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags);
@@ -104,8 +107,9 @@ typedef struct pollinfo {
int fd; // the file descriptor
int socktype; // the client socket type
WEB_CLIENT_ACL port_acl; // the access lists permitted on this web server port (it's -1 for client sockets)
- char *client_ip; // the connected client IP
- char *client_port; // the connected client port
+ char *client_ip; // Max INET6_ADDRSTRLEN bytes
+ char *client_port; // Max NI_MAXSERV bytes
+ char *client_host; // Max NI_MAXHOST bytes
time_t connected_t; // the time the socket connected
time_t last_received_t; // the time the socket last received data
@@ -173,6 +177,7 @@ extern POLLINFO *poll_add_fd(POLLJOB *p
, uint32_t flags
, const char *client_ip
, const char *client_port
+ , const char *client_host
, void *(*add_callback)(POLLINFO *pi, short int *events, void *data)
, void (*del_callback)(POLLINFO *pi)
, int (*rcv_callback)(POLLINFO *pi, short int *events)