diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 433759928b..f3d0be260c 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -249,7 +249,8 @@ hostnogssenc database userall specifies that it matches all users. Otherwise, this is either the name of a specific - database user, or a group name preceded by +. + database user, a regular expression preceded by / + or a group name preceded by +. (Recall that there is no real distinction between users and groups in PostgreSQL; a + mark really means match any of the roles that are directly or indirectly members @@ -787,16 +788,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: diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 1447588c4a..e5b95eebf6 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -574,7 +574,8 @@ 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) @@ -590,6 +591,56 @@ 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. + */ + int r; + pg_wchar *wstr; + int wlen; + regex_t re; + + wstr = palloc((strlen(tok->string + 1) + 1) * sizeof(pg_wchar)); + wlen = pg_mb2wchar_with_len(tok->string + 1, + wstr, strlen(tok->string + 1)); + + r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID); + if (r) + { + char errstr[100]; + + pg_regerror(r, &re, errstr, sizeof(errstr)); + ereport(LOG, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression \"%s\": %s", + tok->string + 1, errstr))); + + pfree(wstr); + return false; + } + else + { + pg_wchar *wrolestr; + int wrolelen; + + pfree(wstr); + wrolestr = palloc((strlen(role) + 1) * sizeof(pg_wchar)); + wrolelen = pg_mb2wchar_with_len(role, wrolestr, strlen(role)); + + if (pg_regexec(&re, wrolestr, wrolelen, 0, NULL, 0, NULL, 0) == REG_OKAY) + { + pfree(wrolestr); + return true; + } + else + { + pfree(wrolestr); + return false; + } + } + } else if (token_matches(tok, role) || token_is_keyword(tok, "all")) return true; diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 3e3079c824..09439fe8e4 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -23,12 +23,13 @@ if (!$use_unix_sockets) # and then execute a reload to refresh it. sub reset_pg_hba { - my $node = shift; - my $hba_method = shift; + my $node = shift; + my $hba_user_name = 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', "local all $hba_user_name\\\n $hba_method"); $node->reload; return; } @@ -76,14 +77,14 @@ $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'); +reset_pg_hba($node, 'all', 'trust'); test_role($node, 'scram_role', 'trust', 0, log_unlike => [qr/connection authenticated:/]); test_role($node, 'md5_role', 'trust', 0, log_unlike => [qr/connection authenticated:/]); # For plain "password" method, all users should also be able to connect. -reset_pg_hba($node, 'password'); +reset_pg_hba($node, 'all', 'password'); test_role($node, 'scram_role', 'password', 0, log_like => [qr/connection authenticated: identity="scram_role" method=password/]); @@ -92,7 +93,7 @@ test_role($node, 'md5_role', 'password', 0, [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'); +reset_pg_hba($node, 'all', 'scram-sha-256'); test_role( $node, 'scram_role', @@ -112,7 +113,7 @@ $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'); +reset_pg_hba($node, 'all', 'md5'); test_role($node, 'scram_role', 'md5', 0, log_like => [qr/connection authenticated: identity="scram_role" method=md5/]); @@ -122,11 +123,11 @@ test_role($node, 'md5_role', 'md5', 0, # 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, 'all', 'password'); $ENV{"PGCHANNELBINDING"} = 'require'; test_role($node, '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, 'all', 'scram-sha-256'); $ENV{"PGCHANNELBINDING"} = 'require'; test_role($node, 'scram_role', 'scram-sha-256', 2); @@ -145,7 +146,7 @@ append_to_file( !); chmod 0600, $pgpassfile or die; -reset_pg_hba($node, 'password'); +reset_pg_hba($node, 'all', 'password'); test_role($node, 'scram_role', 'password from pgpass', 0); test_role($node, 'md5_role', 'password from pgpass', 2); @@ -156,4 +157,8 @@ append_to_file( test_role($node, 'md5_role', 'password from pgpass', 0); +# Testing with regular expression for username +reset_pg_hba($node, '/^.*md.*$', 'password'); +test_role($node, 'md5_role', 'password from pgpass and regular expression for username', 0); + done_testing();