On Tue, Sep 20, 2022 at 01:33:09PM +0200, Drouvot, Bertrand wrote: > I assume (maybe i should not) that if objects starting with / already exist > there is very good reason(s) behind. Then I don't think that preventing > their creation in the DDL would help (quite the contrary for the ones that > really need them).
I have been pondering on this point for the last few weeks, and I'd like to change my opinion and side with Tom on this one as per the very unlikeliness of this being a problem in the wild. I have studied the places that would require restrictions but that was just feeling adding a bit more bloat into the CREATE/ALTER ROLE paths for what's aimed at providing a consistent experience for the user across pg_hba.conf and pg_ident.conf. > It looks to me that adding a GUC (off by default) to enable/disable the > regexp usage in the hba could be a fair compromise. It won't block any > creation starting with a / and won't open more doors (if such objects exist) > by default. Enforcing a behavior change in HBA policies with a GUC does not strike me as a good thing in the long term. I am ready to bet that it would just sit around for nothing like the compatibility GUCs. Anyway, I have looked at the patch. + List *roles_re; + List *databases_re; + regex_t hostname_re; I am surprised by the approach of using separate lists for the regular expressions and the raw names. Wouldn't it be better to store everything in a single list but assign an entry type? In this case it would be either regex or plain string. This would minimize the footprint of the changes (no extra arguments *_re in the routines checking for a match on the roles, databases or hosts). And it seems to me that this would make unnecessary the use of re_num here and there. The hostname is different, of course, requiring only an extra field for its type, or something like that. Perhaps the documentation would gain in clarity if there were more examples, like a set of comma-separated examples (mix of regex and raw strings for example, for all the field types that gain support for regexes)? -$node->append_conf('postgresql.conf', "log_connections = on\n"); +$node->append_conf( + 'postgresql.conf', qq{ +listen_addresses = '127.0.0.1' +log_connections = on +}); Hmm. I think that we may need to reconsider the location of the tests for the regexes with the host name, as the "safe" regression tests should not switch listen_addresses. One location where we already do that is src/test/ssl/, so these could be moved there. Keeping the database and user name parts in src/test/authentication/ is fine. Something that stood out on a first review is the refactoring of 001_password.pl that can be done independently of the main patch: - test_role() -> test_conn() to be able to pass down a database name. - reset_pg_hba() to control the host, db and user parts. The host part does not really apply after moving the hosts checks to a more secure location, so I guess that this had better be extended just for the user and database, keeping host=local all the time. I am planning to apply 0001 attached independently, reducing the footprint of 0002, which is your previous patch left untouched (mostly!). -- Michael
From 203122ff1eeae942fc78b5d7ad85122547a2a6ed Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Wed, 5 Oct 2022 15:14:41 +0900 Subject: [PATCH v4 1/2] Refactor TAP test 001_password.pl --- src/test/authentication/t/001_password.pl | 60 ++++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 58e4176e80..9e02697355 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -24,28 +24,30 @@ if (!$use_unix_sockets) sub reset_pg_hba { my $node = shift; + my $host = shift; + my $database = shift; + my $role = shift; my $hba_method = shift; unlink($node->data_dir . '/pg_hba.conf'); # just for testing purposes, use a continuation line - $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method"); + $node->append_conf('pg_hba.conf', "$host $database $role\\\n $hba_method"); $node->reload; return; } -# Test access for a single role, useful to wrap all tests into one. Extra -# named parameters are passed to connect_ok/fails as-is. -sub test_role +# Test access for a connection string, useful to wrap all tests into one. +# Extra named parameters are passed to connect_ok/fails as-is. +sub test_conn { local $Test::Builder::Level = $Test::Builder::Level + 1; - my ($node, $role, $method, $expected_res, %params) = @_; + my ($node, $connstr, $method, $expected_res, %params) = @_; my $status_string = 'failed'; $status_string = 'success' if ($expected_res eq 0); - my $connstr = "user=$role"; my $testname = - "authentication $status_string for method $method, role $role"; + "authentication $status_string for method $method, connstr $connstr"; if ($expected_res eq 0) { @@ -81,10 +83,10 @@ $ENV{"PGPASSWORD"} = 'pass'; # For "trust" method, all users should be able to connect. These users are not # considered to be authenticated. -reset_pg_hba($node, 'trust'); -test_role($node, 'scram_role', 'trust', 0, +reset_pg_hba($node, 'local','all', 'all', 'trust'); +test_conn($node, 'user=scram_role', 'trust', 0, log_unlike => [qr/connection authenticated:/]); -test_role($node, 'md5_role', 'trust', 0, +test_conn($node, 'user=md5_role', 'trust', 0, log_unlike => [qr/connection authenticated:/]); # SYSTEM_USER is null when not authenticated. @@ -106,40 +108,40 @@ is($res, 't', ); # For plain "password" method, all users should also be able to connect. -reset_pg_hba($node, 'password'); -test_role($node, 'scram_role', 'password', 0, +reset_pg_hba($node, 'local','all', 'all', 'password'); +test_conn($node, 'user=scram_role', 'password', 0, log_like => [qr/connection authenticated: identity="scram_role" method=password/]); -test_role($node, 'md5_role', 'password', 0, +test_conn($node, 'user=md5_role', 'password', 0, log_like => [qr/connection authenticated: identity="md5_role" method=password/]); # For "scram-sha-256" method, user "scram_role" should be able to connect. -reset_pg_hba($node, 'scram-sha-256'); -test_role( +reset_pg_hba($node, 'local','all', 'all', 'scram-sha-256'); +test_conn( $node, - 'scram_role', + 'user=scram_role', 'scram-sha-256', 0, log_like => [ qr/connection authenticated: identity="scram_role" method=scram-sha-256/ ]); -test_role($node, 'md5_role', 'scram-sha-256', 2, +test_conn($node, 'user=md5_role', 'scram-sha-256', 2, log_unlike => [qr/connection authenticated:/]); # Test that bad passwords are rejected. $ENV{"PGPASSWORD"} = 'badpass'; -test_role($node, 'scram_role', 'scram-sha-256', 2, +test_conn($node, 'user=scram_role', 'scram-sha-256', 2, log_unlike => [qr/connection authenticated:/]); $ENV{"PGPASSWORD"} = 'pass'; # For "md5" method, all users should be able to connect (SCRAM # authentication will be performed for the user with a SCRAM secret.) -reset_pg_hba($node, 'md5'); -test_role($node, 'scram_role', 'md5', 0, +reset_pg_hba($node, 'local','all', 'all', 'md5'); +test_conn($node, 'user=scram_role', 'md5', 0, log_like => [qr/connection authenticated: identity="scram_role" method=md5/]); -test_role($node, 'md5_role', 'md5', 0, +test_conn($node, 'user=md5_role', 'md5', 0, log_like => [qr/connection authenticated: identity="md5_role" method=md5/]); @@ -164,13 +166,13 @@ is($res, 't', # Tests for channel binding without SSL. # Using the password authentication method; channel binding can't work -reset_pg_hba($node, 'password'); +reset_pg_hba($node, 'local','all', 'all', 'password'); $ENV{"PGCHANNELBINDING"} = 'require'; -test_role($node, 'scram_role', 'scram-sha-256', 2); +test_conn($node, 'user=scram_role', 'scram-sha-256', 2); # SSL not in use; channel binding still can't work -reset_pg_hba($node, 'scram-sha-256'); +reset_pg_hba($node, 'local','all', 'all', 'scram-sha-256'); $ENV{"PGCHANNELBINDING"} = 'require'; -test_role($node, 'scram_role', 'scram-sha-256', 2); +test_conn($node, 'user=scram_role', 'scram-sha-256', 2); # Test .pgpass processing; but use a temp file, don't overwrite the real one! my $pgpassfile = "${PostgreSQL::Test::Utils::tmp_check}/pgpass"; @@ -187,15 +189,15 @@ append_to_file( !); chmod 0600, $pgpassfile or die; -reset_pg_hba($node, 'password'); -test_role($node, 'scram_role', 'password from pgpass', 0); -test_role($node, 'md5_role', 'password from pgpass', 2); +reset_pg_hba($node, 'local','all', 'all', 'password'); +test_conn($node, 'user=scram_role', 'password from pgpass', 0); +test_conn($node, 'user=md5_role', 'password from pgpass', 2); append_to_file( $pgpassfile, qq! *:*:*:md5_role:p\\ass !); -test_role($node, 'md5_role', 'password from pgpass', 0); +test_conn($node, 'user=md5_role', 'password from pgpass', 0); done_testing(); -- 2.37.3
From cd8debbc5c54f5342cd7db4e43091532fbdb065d Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Wed, 5 Oct 2022 16:17:28 +0900 Subject: [PATCH v4 2/2] Main patch from Bertrand Drouvot --- src/include/libpq/hba.h | 3 + src/backend/libpq/hba.c | 225 ++++++++++++++++++---- src/test/authentication/t/001_password.pl | 42 ++++ doc/src/sgml/client-auth.sgml | 26 ++- 4 files changed, 245 insertions(+), 51 deletions(-) diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index d06da81806..24fe502d06 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -120,6 +120,9 @@ typedef struct HbaLine char *radiusidentifiers_s; List *radiusports; char *radiusports_s; + List *roles_re; + List *databases_re; + regex_t hostname_re; } HbaLine; typedef struct IdentLine diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 4637426d62..89a4fa8b98 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -117,6 +117,9 @@ static List *tokenize_inc_file(List *tokens, const char *outer_filename, const char *inc_filename, int elevel, char **err_msg); static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int elevel, char **err_msg); +static bool token_regcomp(regex_t *re, char *string, char *filename, + int line_num, char **err_msg, int elevel); +static bool token_regexec(const char *match, regex_t *re); /* @@ -574,13 +577,15 @@ is_member(Oid userid, const char *role) } /* - * Check AuthToken list for a match to role, allowing group names. + * Check AuthToken list for a match to role. + * We are allowing group names and regular expressions. */ static bool -check_role(const char *role, Oid roleid, List *tokens) +check_role(const char *role, Oid roleid, List *tokens, List *tokens_re) { ListCell *cell; AuthToken *tok; + int re_num = 0; foreach(cell, tokens) { @@ -590,6 +595,23 @@ check_role(const char *role, Oid roleid, List *tokens) if (is_member(roleid, tok->string + 1)) return true; } + else if (!tok->quoted && tok->string[0] == '/') + { + /* + * When tok->string starts with a slash, treat it as a regular + * expression. + */ + ListCell *cell_re; + regex_t *re; + + cell_re = list_nth_cell(tokens_re, re_num); + re = lfirst(cell_re); + + if (token_regexec(role, re)) + return true; + + re_num++; + } else if (token_matches(tok, role) || token_is_keyword(tok, "all")) return true; @@ -601,10 +623,11 @@ check_role(const char *role, Oid roleid, List *tokens) * Check to see if db/role combination matches AuthToken list. */ static bool -check_db(const char *dbname, const char *role, Oid roleid, List *tokens) +check_db(const char *dbname, const char *role, Oid roleid, List *tokens, List *tokens_re) { ListCell *cell; AuthToken *tok; + int re_num = 0; foreach(cell, tokens) { @@ -633,6 +656,23 @@ check_db(const char *dbname, const char *role, Oid roleid, List *tokens) } else if (token_is_keyword(tok, "replication")) continue; /* never match this if not walsender */ + else if (!tok->quoted && tok->string[0] == '/') + { + /* + * When tok->string starts with a slash, treat it as a regular + * expression. + */ + ListCell *cell_re; + regex_t *re; + + cell_re = list_nth_cell(tokens_re, re_num); + re = lfirst(cell_re); + + if (token_regexec(dbname, re)) + return true; + + re_num++; + } else if (token_matches(tok, dbname)) return true; } @@ -681,7 +721,7 @@ hostname_match(const char *pattern, const char *actual_hostname) * Check to see if a connecting IP matches a given host name. */ static bool -check_hostname(hbaPort *port, const char *hostname) +check_hostname(hbaPort *port, const char *hostname, regex_t re) { struct addrinfo *gai_result, *gai; @@ -712,8 +752,17 @@ check_hostname(hbaPort *port, const char *hostname) port->remote_hostname = pstrdup(remote_hostname); } + if (hostname[0] == '/') + { + /* + * When hostname starts with a slash, treat it as a regular + * expression. + */ + if (!token_regexec(port->remote_hostname, &re)) + return false; + } /* Now see if remote host name matches this pg_hba line */ - if (!hostname_match(hostname, port->remote_hostname)) + else if (!hostname_match(hostname, port->remote_hostname)) return false; /* If we already verified the forward lookup, we're done */ @@ -939,13 +988,13 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) struct addrinfo *gai_result; struct addrinfo hints; int ret; - char *cidr_slash; char *unsupauth; ListCell *field; List *tokens; ListCell *tokencell; AuthToken *token; HbaLine *parsedline; + char *cidr_slash = NULL; /* keep compiler quiet */ parsedline = palloc0(sizeof(HbaLine)); parsedline->linenumber = line_num; @@ -1049,9 +1098,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) return NULL; } parsedline->databases = NIL; + parsedline->databases_re = NIL; tokens = lfirst(field); foreach(tokencell, tokens) { + AuthToken *tok = lfirst(tokencell); + + if (!tok->quoted && tok->string[0] == '/') + { + /* + * When tok->string starts with a slash, treat it as a regular + * expression. Pre-compile it. + */ + regex_t *re; + + re = (regex_t *) palloc(sizeof(regex_t)); + if (token_regcomp(re, tok->string + 1, HbaFileName, line_num, + err_msg, elevel)) + parsedline->databases_re = lappend(parsedline->databases_re, re); + else + return NULL; + } parsedline->databases = lappend(parsedline->databases, copy_auth_token(lfirst(tokencell))); } @@ -1069,9 +1136,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) return NULL; } parsedline->roles = NIL; + parsedline->roles_re = NIL; tokens = lfirst(field); foreach(tokencell, tokens) { + AuthToken *tok = lfirst(tokencell); + + if (!tok->quoted && tok->string[0] == '/') + { + /* + * When tok->string starts with a slash, treat it as a regular + * expression. Pre-compile it. + */ + regex_t *re; + + re = (regex_t *) palloc(sizeof(regex_t)); + if (token_regcomp(re, tok->string + 1, HbaFileName, line_num, + err_msg, elevel)) + parsedline->roles_re = lappend(parsedline->roles_re, re); + else + return NULL; + } parsedline->roles = lappend(parsedline->roles, copy_auth_token(lfirst(tokencell))); } @@ -1120,6 +1205,8 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) } else { + bool is_regexp = token->string[0] == '/' ? true : false; + /* IP and netmask are specified */ parsedline->ip_cmp_method = ipCmpMask; @@ -1127,9 +1214,12 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) str = pstrdup(token->string); /* Check if it has a CIDR suffix and if so isolate it */ - cidr_slash = strchr(str, '/'); - if (cidr_slash) - *cidr_slash = '\0'; + if (!is_regexp) + { + cidr_slash = strchr(str, '/'); + if (cidr_slash) + *cidr_slash = '\0'; + } /* Get the IP address either way */ hints.ai_flags = AI_NUMERICHOST; @@ -1168,7 +1258,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) pg_freeaddrinfo_all(hints.ai_family, gai_result); /* Get the netmask */ - if (cidr_slash) + if (cidr_slash && !is_regexp) { if (parsedline->hostname) { @@ -1199,7 +1289,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) parsedline->masklen = parsedline->addrlen; pfree(str); } - else if (!parsedline->hostname) + else if (!parsedline->hostname && !is_regexp) { /* Read the mask field. */ pfree(str); @@ -1261,6 +1351,18 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) return NULL; } } + else if (is_regexp) + { + /* + * When token->string starts with a slash, treat it as a + * regular expression. Pre-compile it. + */ + if (!token_regcomp(&parsedline->hostname_re, + token->string + 1, HbaFileName, + line_num, err_msg, elevel)) + return NULL; + parsedline->hostname = str; + } } } /* != ctLocal */ @@ -2135,7 +2237,8 @@ check_hba(hbaPort *port) if (hba->hostname) { if (!check_hostname(port, - hba->hostname)) + hba->hostname, hba->hostname_re)) + continue; } else @@ -2162,10 +2265,10 @@ check_hba(hbaPort *port) /* Check database and role */ if (!check_db(port->database_name, port->user_name, roleid, - hba->databases)) + hba->databases, hba->databases_re)) continue; - if (!check_role(port->user_name, roleid, hba->roles)) + if (!check_role(port->user_name, roleid, hba->roles, hba->roles_re)) continue; /* Found a record that matched! */ @@ -2342,34 +2445,9 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel) * When system username starts with a slash, treat it as a regular * expression. Pre-compile it. */ - int r; - pg_wchar *wstr; - int wlen; - - wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * sizeof(pg_wchar)); - wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1, - wstr, strlen(parsedline->ident_user + 1)); - - r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID); - if (r) - { - char errstr[100]; - - pg_regerror(r, &parsedline->re, errstr, sizeof(errstr)); - ereport(elevel, - (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), - errmsg("invalid regular expression \"%s\": %s", - parsedline->ident_user + 1, errstr), - errcontext("line %d of configuration file \"%s\"", - line_num, IdentFileName))); - - *err_msg = psprintf("invalid regular expression \"%s\": %s", - parsedline->ident_user + 1, errstr); - - pfree(wstr); + if (!token_regcomp(&parsedline->re, parsedline->ident_user + 1, + IdentFileName, line_num, err_msg, elevel)) return NULL; - } - pfree(wstr); } return parsedline; @@ -2706,3 +2784,68 @@ hba_authname(UserAuth auth_method) return UserAuthName[auth_method]; } + +/* + * Compile the regular expression "re" and return whether it compiles + * successfully or not. + * + * If not, the last 4 parameters are used to add extra details while reporting + * the error. + */ +static bool +token_regcomp(regex_t *re, char *string, char *filename, int line_num, + char **err_msg, int elevel) +{ + int r; + pg_wchar *wstr; + int wlen; + + wstr = palloc((strlen(string) + 1) * sizeof(pg_wchar)); + wlen = pg_mb2wchar_with_len(string, + wstr, strlen(string)); + + r = pg_regcomp(re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID); + if (r) + { + char errstr[100]; + + pg_regerror(r, re, errstr, sizeof(errstr)); + ereport(elevel, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression \"%s\": %s", + string, errstr), + errcontext("line %d of configuration file \"%s\"", + line_num, filename))); + + *err_msg = psprintf("invalid regular expression \"%s\": %s", + string, errstr); + + pfree(wstr); + return false; + } + + pfree(wstr); + return true; +} + +/* + * Return whether "match" is matching the regular expression "re" or not. + */ +static bool +token_regexec(const char *match, regex_t *re) +{ + pg_wchar *wmatchstr; + int wmatchlen; + + wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar)); + wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match)); + + if (pg_regexec(re, wmatchstr, wmatchlen, 0, NULL, 0, NULL, 0) == REG_OKAY) + { + pfree(wmatchstr); + return true; + } + + pfree(wmatchstr); + return false; +} diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 9e02697355..453dafffdb 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -81,6 +81,9 @@ $node->safe_psql( GRANT ALL ON sysuser_data TO md5_role;"); $ENV{"PGPASSWORD"} = 'pass'; +# Create a database to test regular expression +$node->safe_psql('postgres', "CREATE database regex_testdb;"); + # For "trust" method, all users should be able to connect. These users are not # considered to be authenticated. reset_pg_hba($node, 'local','all', 'all', 'trust'); @@ -200,4 +203,43 @@ append_to_file( test_conn($node, 'user=md5_role', 'password from pgpass', 0); +# Testing with regular expression for username. Note that the first regex +# matches in this case. +reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^md.*$', + 'password'); +test_conn($node, 'user=md5_role', 'password, matching regexp for username', 0); +# The third regex does not match anymore. +reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^m_d.*$', + 'password'); +test_conn($node, 'user=md5_role', 'password, non matching regexp for username', 2, + log_unlike => [qr/connection authenticated:/]); + +# Testing with regular expression for dbname. The third regex matches. +reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^regex_t.*b$', 'all', 'password'); +test_conn($node, 'user=md5_role dbname=regex_testdb', 'password, + matching regexp for dbname', 0); +# The third regex does not match anymore. +reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^regex_t.*ba$', 'all', 'password'); +test_conn($node, 'user=md5_role dbname=regex_testdb', 'password, non matching regexp for dbname', 2, + log_unlike => [qr/connection authenticated:/]); + +# Testing with regular expression for hostname +SKIP: +{ + # Being able to do a reverse lookup of a hostname on Windows for localhost + # is not guaranteed on all environments by default. + # So, skip the regular expression test for hostname on Windows. + skip "Regular expression for hostname not tested on Windows", 2 if ($windows_os); + + # XXX: this makes the test unsecure. + $node->append_conf('postgresql.conf', "listen_addresses = '127.0.0.1'\n"); + $node->restart; + + reset_pg_hba($node, 'host', 'all', 'all /^.*$', 'password'); + test_conn($node, 'user=md5_role host=localhost', 'password, matching regexp for hostname', 0); + + reset_pg_hba($node, 'host', 'all', 'all /^$', 'password'); + test_conn($node, 'user=md5_role host=localhost', 'password, non matching regexp for hostname', 2); +} + done_testing(); diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index c6f1b70fd3..28343445be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -235,8 +235,9 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl logical replication connections do specify it. Otherwise, this is the name of a specific <productname>PostgreSQL</productname> database. - Multiple database names can be supplied by separating them with - commas. A separate file containing database names can be specified by + Multiple database names and/or regular expressions preceded by <literal>/</literal> + can be supplied by separating them with commas. + A separate file containing database names can be specified by preceding the file name with <literal>@</literal>. </para> </listitem> @@ -249,7 +250,8 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl Specifies which database user name(s) this record matches. The value <literal>all</literal> specifies that it matches all users. Otherwise, this is either the name of a specific - database user, or a group name preceded by <literal>+</literal>. + database user, a regular expression preceded by <literal>/</literal> + or a group name preceded by <literal>+</literal>. (Recall that there is no real distinction between users and groups in <productname>PostgreSQL</productname>; a <literal>+</literal> mark really means <quote>match any of the roles that are directly or indirectly members @@ -258,7 +260,8 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl considered to be a member of a role if they are explicitly a member of the role, directly or indirectly, and not just by virtue of being a superuser. - Multiple user names can be supplied by separating them with commas. + Multiple user names and/or regular expressions preceded by <literal>/</literal> + can be supplied by separating them with commas. A separate file containing user names can be specified by preceding the file name with <literal>@</literal>. </para> @@ -270,8 +273,9 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl <listitem> <para> Specifies the client machine address(es) 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. + matches. This field can contain either a host name, a regular expression + preceded by <literal>/</literal> representing host names, an IP address range, + or one of the special key words mentioned below. </para> <para> @@ -785,16 +789,18 @@ host all all 192.168.12.10/32 gss # 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 +# If these are the only four lines for local connections, they will # allow local users to connect only to their own databases (databases -# with the same name as their database user name) except for administrators -# and members of role "support", who can connect to all databases. The file -# $PGDATA/admins contains a list of names of administrators. Passwords +# with the same name as their database user name) except for administrators, +# users finishing with "helpdesk" and members of role "support", +# who can connect to all databases. +# The file$PGDATA/admins contains a list of names of administrators. Passwords # are required in all cases. # # TYPE DATABASE USER ADDRESS METHOD local sameuser all md5 local all @admins md5 +local all /^.*helpdesk$ md5 local all +support md5 # The last two lines above can be combined into a single line: -- 2.37.3
signature.asc
Description: PGP signature