Here is a patch for host name support in pg_hba.conf. I have reviewed various past threads about this, and there appeared to have been a 50/50 split of for and against reverse lookup. I went with the reverse lookup, because
0) I like it. 1) It is more secure. 2) It allows extending it to wildcards in the future. 3) Apache (Allow from) does it that way. To clarify how it works: The client's IP address (known from the kernel) is reverse looked up, which results in a host name. That host name is compared with the line in pg_hba.conf. If it matches, a forward lookup is performed on the host name to check if any of the resulting IP addresses match the client's IP address. If yes, the line is considered to match and the authentication method is selected. Anyway, assuming we will go with this, you will also notice that in the patch I changed the default pg_hba.conf to match against "localhost" instead of numeric addresses. Initially thought of as a temporary change for testing this patch, I think this might actually have some permanent value because it saves you from having to change the IPv4 and IPv6 lines in tandem most of the times, which is a moderately common mistake. We already rely on localhost being (forward) resolvable for the stats collector. Something to think about: Maybe we need a quoting mechanism in case someone names their hosts "samenet".
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index e8995f9..808c468 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -101,9 +101,9 @@ A record can have one of the seven formats <synopsis> local <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> -host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> -hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> -hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> @@ -218,12 +218,17 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </varlistentry> <varlistentry> - <term><replaceable>CIDR-address</replaceable></term> + <term><replaceable>address</replaceable></term> <listitem> <para> Specifies the client machine IP address range that this record - matches. This field contains an IP address in standard dotted decimal - notation and a <acronym>CIDR</> mask length. (IP addresses can only be + matches. This field can contain either a host name, an IP + address range, one of the special key words mentioned below. + </para> + + <para> + An IP address is specified in standard dotted decimal + notation with a <acronym>CIDR</> mask length. (IP addresses can only be specified numerically, not as domain or host names.) The mask length indicates the number of high-order bits of the client IP address that must match. Bits to the right of this must @@ -233,14 +238,7 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </para> <para> - Instead of a <replaceable>CIDR-address</replaceable>, you can write - <literal>samehost</literal> to match any of the server's own IP - addresses, or <literal>samenet</literal> to match any address in any - subnet that the server is directly connected to. - </para> - - <para> - Typical examples of a <replaceable>CIDR-address</replaceable> are + Typical examples of an IP address range specified this way are <literal>172.20.143.89/32</literal> for a single host, or <literal>172.20.143.0/24</literal> for a small network, or <literal>10.6.0.0/16</literal> for a larger one. @@ -260,6 +258,24 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </para> <para> + You can also write + <literal>samehost</literal> to match any of the server's own IP + addresses, or <literal>samenet</literal> to match any address in any + subnet that the server is directly connected to. + </para> + + <para> + If a host name is specified (anything that is not an IP address + or a special key word is processed as a potential host name), a + reverse DNS lookup is performed on the client's IP address, + then a forward DNS lookup on the resulting name to check if it + matches the original IP address (that is, at least one of the + potentially many IP addresses matches the original one), and + the name found in the reverse lookup is compared with the + specified host name. + </para> + + <para> This field only applies to <literal>host</literal>, <literal>hostssl</literal>, and <literal>hostnossl</> records. </para> @@ -511,12 +527,12 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> # any database user name using Unix-domain sockets (the default for local # connections). # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD local all all trust # The same using local loopback TCP/IP connections. # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD host all all 127.0.0.1/32 trust # The same as the previous line, but using a separate netmask column @@ -524,17 +540,27 @@ host all all 127.0.0.1/32 trust # TYPE DATABASE USER IP-ADDRESS IP-MASK METHOD host all all 127.0.0.1 255.255.255.255 trust +# The same over IPv6. +# +# TYPE DATABASE USER ADDRESS METHOD +host all all ::1/128 trust + +# The same using a host name (would typically cover both IPv4 and IPv6). +# +# TYPE DATABASE USER ADDRESS METHOD +host all all localhost trust + # Allow any user from any host with IP address 192.168.93.x to connect # to database "postgres" as the same user name that ident reports for # the connection (typically the operating system user name). # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD host postgres all 192.168.93.0/24 ident # Allow any user from host 192.168.12.10 to connect to database # "postgres" if the user's password is correctly supplied. # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD host postgres all 192.168.12.10/32 md5 # In the absence of preceding "host" lines, these two lines will @@ -543,7 +569,7 @@ host postgres all 192.168.12.10/32 md5 # on the Internet. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject host all all 0.0.0.0/0 krb5 @@ -553,7 +579,7 @@ host all all 0.0.0.0/0 krb5 # connection is allowed if there is an entry in pg_ident.conf for map # "omicron" that says "bryanh" is allowed to connect as "guest1". # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD host all all 192.168.0.0/16 ident map=omicron # If these are the only three lines for local connections, they will @@ -563,7 +589,7 @@ host all all 192.168.0.0/16 ident map=omicro # $PGDATA/admins contains a list of names of administrators. Passwords # are required in all cases. # -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD local sameuser all md5 local all @admins md5 local all +support md5 diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index aec12b0..b2ff30d 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -540,6 +540,99 @@ check_db(const char *dbname, const char *role, Oid roleid, char *param_str) return false; } +static bool +ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b) +{ + return (a->sin_addr.s_addr == b->sin_addr.s_addr); +} + +static bool +ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b) +{ + int i; + + for (i = 0; i < 16; i++) + if (a->sin6_addr.s6_addr[i] != b->sin6_addr.s6_addr[i]) + return false; + + return true; +} + +/* + * Check to see if a connecting IP matches a given host name. + */ +static bool +check_hostname(hbaPort *port, const char *hostname) +{ + struct addrinfo *gai_result, *gai; + int ret; + bool found; + + /* Lookup remote host name if not already done */ + if (!port->remote_hostname) + { + char remote_hostname[NI_MAXHOST]; + + if (pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen, + remote_hostname, sizeof(remote_hostname), + NULL, 0, + 0)) + return false; + + port->remote_hostname = strdup(remote_hostname); + } + + if (strcmp(port->remote_hostname, hostname) != 0) + return false; + + /* Lookup IP from host name and check against original IP */ + + ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result); + if (ret != 0) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("getaddrinfo failed on \"%s\": %s", + port->remote_hostname, gai_strerror(ret)))); + + found = false; + for (gai = gai_result; gai; gai = gai->ai_next) + { + char hostinfo[NI_MAXHOST]; + + getnameinfo(gai->ai_addr, gai->ai_addrlen, + hostinfo, sizeof(hostinfo), + NULL, 0, + NI_NUMERICHOST); + + if (gai->ai_addr->sa_family == port->raddr.addr.ss_family) + { + if (gai->ai_addr->sa_family == AF_INET) + { + if (ipv4eq((struct sockaddr_in *) gai->ai_addr, + (struct sockaddr_in *) &port->raddr.addr)) + { + found = true; + break; + } + } + else if (gai->ai_addr->sa_family == AF_INET6) + { + if (ipv6eq((struct sockaddr_in6 *) gai->ai_addr, + (struct sockaddr_in6 *) &port->raddr.addr)) + { + found = true; + break; + } + } + } + } + + if (gai_result) + freeaddrinfo(gai_result); + + return found; +} + /* * Check to see if a connecting IP matches the given address and netmask. */ @@ -816,7 +909,12 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) hints.ai_next = NULL; ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result); - if (ret || !gai_result) + if (ret == 0 && gai_result) + memcpy(&parsedline->addr, gai_result->ai_addr, + gai_result->ai_addrlen); + else if (ret == EAI_NONAME) + parsedline->hostname = token; + else { ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -830,12 +928,10 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) return false; } - memcpy(&parsedline->addr, gai_result->ai_addr, - gai_result->ai_addrlen); pg_freeaddrinfo_all(hints.ai_family, gai_result); /* Get the netmask */ - if (cidr_slash) + if (!parsedline->hostname && cidr_slash) { if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1, parsedline->addr.ss_family) < 0) @@ -852,7 +948,7 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) } pfree(token); } - else + else if (!parsedline->hostname) { /* Read the mask field. */ pfree(token); @@ -1369,10 +1465,19 @@ check_hba(hbaPort *port) switch (hba->ip_cmp_method) { case ipCmpMask: - if (!check_ip(&port->raddr, - (struct sockaddr *) & hba->addr, - (struct sockaddr *) & hba->mask)) - continue; + if (hba->hostname) + { + if (!check_hostname(port, + hba->hostname)) + continue; + } + else + { + if (!check_ip(&port->raddr, + (struct sockaddr *) & hba->addr, + (struct sockaddr *) & hba->mask)) + continue; + } break; case ipCmpSameHost: case ipCmpSameNet: diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index e1017cf..65842ea 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -10,9 +10,9 @@ # databases they can access. Records take one of these forms: # # local DATABASE USER METHOD [OPTIONS] -# host DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] -# hostssl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] -# hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] +# host DATABASE USER ADDRESS METHOD [OPTIONS] +# hostssl DATABASE USER ADDRESS METHOD [OPTIONS] +# hostnossl DATABASE USER ADDRESS METHOD [OPTIONS] # # (The uppercase items must be replaced by actual values.) # @@ -29,7 +29,8 @@ # you can also write a file name prefixed with "@" to include names # from a separate file. # -# CIDR-ADDRESS specifies the set of hosts the record matches. It is +# ADDRESS specifies the set of hosts the record matches. It can be a +# host name, or it is # made up of an IP address and a CIDR mask that is an integer (between # 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies the number # of significant bits in the mask. Alternatively, you can write an IP @@ -70,11 +71,9 @@ @authcomment@ -# TYPE DATABASE USER CIDR-ADDRESS METHOD +# TYPE DATABASE USER ADDRESS METHOD @remove-line-for-nolo...@# "local" is for Unix domain socket connections only @remove-line-for-nolo...@local all all @authmethod@ -# IPv4 local connections: -host all all 127.0.0.1/32 @authmethod@ -# IPv6 local connections: -host all all ::1/128 @authmethod@ +# IPv4/6 local connections: +host all all localhost @authmethod@ diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index db61569..83182ac 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -3407,6 +3407,8 @@ BackendInitialize(Port *port) */ port->remote_host = strdup(remote_host); port->remote_port = strdup(remote_port); + if (log_hostname) + port->remote_hostname = port->remote_host; /* * Ready to begin client interaction. We will give up and exit(1) after a diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index da3a2a3..97686ab 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -56,6 +56,7 @@ typedef struct struct sockaddr_storage addr; struct sockaddr_storage mask; IPCompareMethod ip_cmp_method; + char *hostname; UserAuth auth_method; char *usermap; diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 0a80f74..e364ff0 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -109,6 +109,7 @@ typedef struct Port SockAddr laddr; /* local addr (postmaster) */ SockAddr raddr; /* remote addr (client) */ char *remote_host; /* name (or ip addr) of remote host */ + char *remote_hostname; /* name (not ip addr) of remote host, if available */ char *remote_port; /* text rep of remote port */ CAC_state canAcceptConnections; /* postmaster connection status */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers