Support host names in pg_hba.conf

Peter Eisentraut, reviewed by KaiGai Kohei and Tom Lane
This commit is contained in:
Peter Eisentraut 2010-10-15 22:53:39 +03:00
parent 3cde44374a
commit 6ab42ae367
6 changed files with 248 additions and 49 deletions

View file

@ -80,9 +80,9 @@
A record is made
up of a number of fields which are separated by spaces and/or tabs.
Fields can contain white space if the field value is quoted.
Quoting one of the keywords in a database or user name field (e.g.,
Quoting one of the keywords in a database, user, or address field (e.g.,
<literal>all</> or <literal>replication</>) makes the word lose its special
character, and just match a database or user with that name.
character, and just match a database, user, or host with that name.
</para>
<para>
@ -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,13 +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
specified numerically, not as domain or host names.) The mask
Specifies the client machine addresses that this record
matches. This field can contain either a host name, an IP
address range, or 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. 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
be zero in the given IP address.
@ -233,14 +237,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.
@ -259,6 +256,67 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
support for IPv6 addresses.
</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),
that name is compared with the result of a reverse name
resolution of the client's IP address (e.g., reverse DNS
lookup, if DNS is used). Host name comparisons are case
insensitive. If there is a match, then a forward name
resolution (e.g., forward DNS lookup) is performed on the host
name to check whether any of the addresses it resolves to are
equal to the client's IP address. If both directions match,
then the entry is considered to match. (The host name that is
used in <filename>pg_hba.conf</filename> should be the one that
address-to-name resolution of the client's IP address returns,
otherwise the line won't be matched. Some host name databases
allow associating an IP address with multiple host names, but
the operating system will only return one host name when asked
to resolve an IP address.)
</para>
<para>
When host names are specified
in <filename>pg_hba.conf</filename>, you should make sure that
name resolution is reasonably fast. It can be of advantage to
set up a local name resolution cache such
as <command>nscd</command>. Also, you may wish to enable the
configuration parameter <varname>log_hostname</varname> to see
the client's host name instead of the IP address in the log.
</para>
<sidebar>
<para>
Occasionally, users have wondered why host names are handled
in this seemingly complicated way with two name resolutions
and requiring reverse lookup of IP addresses, which is
sometimes not set up or points to some undesirable host name.
It is primarily for efficiency: A connection attempt requires
two resolver lookups of the current client's address. If
there is resolver problem with that address, it becomes only
that client's problem. A hypothetical alternative
implementation which only does forward lookups would have to
resolve every host name mentioned in
<filename>pg_hba.conf</filename> at every connection attempt.
That would already be slow by itself. And if there is a
resolver problem with one of the host names, it becomes
everyone's problem.
</para>
<para>
Note that this behavior is consistent with other popular
implementations of host name-based access control, such as the
Apache HTTP Server and TCP Wrappers.
</para>
</sidebar>
<para>
This field only applies to <literal>host</literal>,
<literal>hostssl</literal>, and <literal>hostnossl</> records.
@ -511,12 +569,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 +582,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 +611,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 +621,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 +631,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

View file

@ -102,8 +102,8 @@ pg_isblank(const char c)
* whichever comes first. If no more tokens on line, position the file to the
* beginning of the next line or EOF, whichever comes first.
*
* Handle comments. Treat unquoted keywords that might be role names or
* database names specially, by appending a newline to them. Also, when
* Handle comments. Treat unquoted keywords that might be role, database, or
* host names specially, by appending a newline to them. Also, when
* a token is terminated by a comma, the comma is included in the returned
* token.
*/
@ -198,6 +198,8 @@ next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
if (!saw_quote &&
(strcmp(start_buf, "all") == 0 ||
strcmp(start_buf, "samehost") == 0 ||
strcmp(start_buf, "samenet") == 0 ||
strcmp(start_buf, "sameuser") == 0 ||
strcmp(start_buf, "samegroup") == 0 ||
strcmp(start_buf, "samerole") == 0 ||
@ -540,6 +542,102 @@ 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 = pstrdup(remote_hostname);
}
if (pg_strcasecmp(port->remote_hostname, hostname) != 0)
return false;
/* Lookup IP from host name and check against original IP */
if (port->remote_hostname_resolv == +1)
return true;
if (port->remote_hostname_resolv == -1)
return false;
ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result);
if (ret != 0)
ereport(ERROR,
(errmsg("could not translate host name \"%s\" to address: %s",
port->remote_hostname, gai_strerror(ret))));
found = false;
for (gai = gai_result; gai; gai = gai->ai_next)
{
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);
if (!found)
elog(DEBUG2, "pg_hba.conf host name \"%s\" rejected because address resolution did not return a match with IP address of client",
hostname);
port->remote_hostname_resolv = found ? +1 : -1;
return found;
}
/*
* Check to see if a connecting IP matches the given address and netmask.
*/
@ -782,12 +880,12 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
token = lfirst(line_item);
/* Is it equal to 'samehost' or 'samenet'? */
if (strcmp(token, "samehost") == 0)
if (strcmp(token, "samehost\n") == 0)
{
/* Any IP on this host is allowed to connect */
parsedline->ip_cmp_method = ipCmpSameHost;
}
else if (strcmp(token, "samenet") == 0)
else if (strcmp(token, "samenet\n") == 0)
{
/* Any IP on the host's subnets is allowed to connect */
parsedline->ip_cmp_method = ipCmpSameNet;
@ -816,7 +914,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,13 +933,24 @@ 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 = '/'; /* restore token for message */
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
token),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
pfree(token);
return false;
}
if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
parsedline->addr.ss_family) < 0)
{
@ -852,7 +966,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 +1483,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:

View file

@ -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,14 +29,15 @@
# 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
# 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
# address and netmask in separate columns to specify the set of hosts.
# Instead of a CIDR-address, you can write "samehost" to match any of
# the server's own IP addresses, or "samenet" to match any address in
# any subnet that the server is directly connected to.
# 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 address and netmask in separate
# columns to specify the set of hosts. Instead of a CIDR-address, you
# can write "samehost" to match any of the server's own IP addresses,
# or "samenet" to match any address in any subnet that the server is
# directly connected to.
#
# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
# "krb5", "ident", "pam", "ldap", "radius" or "cert". Note that
@ -70,7 +71,7 @@
@authcomment@
# TYPE DATABASE USER CIDR-ADDRESS METHOD
# TYPE DATABASE USER ADDRESS METHOD
@remove-line-for-nolocal@# "local" is for Unix domain socket connections only
@remove-line-for-nolocal@local all all @authmethod@

View file

@ -3422,6 +3422,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

View file

@ -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;

View file

@ -109,6 +109,10 @@ 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 */
int remote_hostname_resolv; /* +1 = remote_hostname is known to resolve to client's IP address;
-1 = remote_hostname is known NOT to resolve to client's IP address;
0 = we have not done the forward DNS lookup yet */
char *remote_port; /* text rep of remote port */
CAC_state canAcceptConnections; /* postmaster connection status */