diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/libpq/hba.c | 98 | ||||
-rw-r--r-- | src/test/authentication/t/003_peer.pl | 160 |
2 files changed, 220 insertions, 38 deletions
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 029b8e44838..adedbd31280 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -73,8 +73,10 @@ typedef struct } tokenize_error_callback_arg; #define token_has_regexp(t) (t->regex != NULL) +#define token_is_member_check(t) (!t->quoted && t->string[0] == '+') #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0) #define token_matches(t, k) (strcmp(t->string, k) == 0) +#define token_matches_insensitive(t,k) (pg_strcasecmp(t->string, k) == 0) /* * Memory context holding the list of TokenizedAuthLines when parsing @@ -995,10 +997,11 @@ is_member(Oid userid, const char *role) * * Each AuthToken listed is checked one-by-one. Keywords are processed * first (these cannot have regular expressions), followed by regular - * expressions (if any) and the exact match. + * expressions (if any), the case-insensitive match (if requested) and + * the exact match. */ static bool -check_role(const char *role, Oid roleid, List *tokens) +check_role(const char *role, Oid roleid, List *tokens, bool case_insensitive) { ListCell *cell; AuthToken *tok; @@ -1006,7 +1009,7 @@ check_role(const char *role, Oid roleid, List *tokens) foreach(cell, tokens) { tok = lfirst(cell); - if (!tok->quoted && tok->string[0] == '+') + if (token_is_member_check(tok)) { if (is_member(roleid, tok->string + 1)) return true; @@ -1018,6 +1021,11 @@ check_role(const char *role, Oid roleid, List *tokens) if (regexec_auth_token(role, tok, 0, NULL) == REG_OKAY) return true; } + else if (case_insensitive) + { + if (token_matches_insensitive(tok, role)) + return true; + } else if (token_matches(tok, role)) return true; } @@ -2614,7 +2622,7 @@ check_hba(hbaPort *port) hba->databases)) continue; - if (!check_role(port->user_name, roleid, hba->roles)) + if (!check_role(port->user_name, roleid, hba->roles, false)) continue; /* Found a record that matched! */ @@ -2804,7 +2812,7 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel) /* * Now that the field validation is done, compile a regex from the user - * token, if necessary. + * tokens, if necessary. */ if (regcomp_auth_token(parsedline->system_user, file_name, line_num, err_msg, elevel)) @@ -2813,6 +2821,13 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel) return NULL; } + if (regcomp_auth_token(parsedline->pg_user, file_name, line_num, + err_msg, elevel)) + { + /* err_msg includes the error to report */ + return NULL; + } + return parsedline; } @@ -2827,6 +2842,8 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, const char *pg_user, const char *system_user, bool case_insensitive, bool *found_p, bool *error_p) { + Oid roleid; + *found_p = false; *error_p = false; @@ -2834,6 +2851,9 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, /* Line does not match the map name we're looking for, so just abort */ return; + /* Get the target role's OID. Note we do not error out for bad role. */ + roleid = get_role_oid(pg_user, true); + /* Match? */ if (token_has_regexp(identLine->system_user)) { @@ -2845,7 +2865,8 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, int r; regmatch_t matches[2]; char *ofs; - char *expanded_pg_user; + AuthToken *expanded_pg_user_token; + bool created_temporary_token = false; r = regexec_auth_token(system_user, identLine->system_user, 2, matches); if (r) @@ -2865,8 +2886,16 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, return; } - if ((ofs = strstr(identLine->pg_user->string, "\\1")) != NULL) + /* + * Replace \1 with the first captured group unless the field already + * has some special meaning, like a group membership or a regexp-based + * check. + */ + if (!token_is_member_check(identLine->pg_user) && + !token_has_regexp(identLine->pg_user) && + (ofs = strstr(identLine->pg_user->string, "\\1")) != NULL) { + char *expanded_pg_user; int offset; /* substitution of the first argument requested */ @@ -2891,46 +2920,53 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, system_user + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); strcat(expanded_pg_user, ofs + 2); - } - else - { - /* no substitution, so copy the match */ - expanded_pg_user = pstrdup(identLine->pg_user->string); - } - /* - * now check if the username actually matched what the user is trying - * to connect as - */ - if (case_insensitive) - { - if (pg_strcasecmp(expanded_pg_user, pg_user) == 0) - *found_p = true; + /* + * Mark the token as quoted, so it will only be compared literally + * and not for some special meaning, such as "all" or a group + * membership check. + */ + expanded_pg_user_token = make_auth_token(expanded_pg_user, true); + created_temporary_token = true; + pfree(expanded_pg_user); } else { - if (strcmp(expanded_pg_user, pg_user) == 0) - *found_p = true; + expanded_pg_user_token = identLine->pg_user; } - pfree(expanded_pg_user); + + /* check the Postgres user */ + *found_p = check_role(pg_user, roleid, + list_make1(expanded_pg_user_token), + case_insensitive); + + if (created_temporary_token) + free_auth_token(expanded_pg_user_token); return; } else { - /* Not regular expression, so make complete match */ + /* + * Not a regular expression, so make a complete match. If the system + * user does not match, just leave. + */ if (case_insensitive) { - if (pg_strcasecmp(identLine->pg_user->string, pg_user) == 0 && - pg_strcasecmp(identLine->system_user->string, system_user) == 0) - *found_p = true; + if (!token_matches_insensitive(identLine->system_user, + system_user)) + return; } else { - if (strcmp(identLine->pg_user->string, pg_user) == 0 && - strcmp(identLine->system_user->string, system_user) == 0) - *found_p = true; + if (!token_matches(identLine->system_user, system_user)) + return; } + + /* check the Postgres user */ + *found_p = check_role(pg_user, roleid, + list_make1(identLine->pg_user), + case_insensitive); } } diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl index e6f5fdba165..a6be651ea7f 100644 --- a/src/test/authentication/t/003_peer.pl +++ b/src/test/authentication/t/003_peer.pl @@ -98,8 +98,13 @@ if (find_in_log( plan skip_all => 'peer authentication is not supported on this platform'; } -# Add a database role, to use for the user name map. +# Add a database role and a group, to use for the user name map. $node->safe_psql('postgres', qq{CREATE ROLE testmapuser LOGIN}); +$node->safe_psql('postgres', "CREATE ROLE testmapgroup NOLOGIN"); +$node->safe_psql('postgres', "GRANT testmapgroup TO testmapuser"); +# Note the double quotes here. +$node->safe_psql('postgres', 'CREATE ROLE "testmapgroupliteral\\1" LOGIN'); +$node->safe_psql('postgres', 'GRANT "testmapgroupliteral\\1" TO testmapuser'); # Extract as well the system user for the user name map. my $system_user = @@ -123,12 +128,56 @@ test_role($node, qq{testmapuser}, 'peer', 0, 'with user name map', log_like => [qr/connection authenticated: identity="$system_user" method=peer/]); +# Tests with the "all" keyword. +reset_pg_ident($node, 'mypeermap', $system_user, 'all'); + +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'with keyword "all" as database user in user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +# Tests with the "all" keyword, but quoted (no effect here). +reset_pg_ident($node, 'mypeermap', $system_user, '"all"'); + +test_role( + $node, + qq{testmapuser}, + 'peer', + 2, + 'with quoted keyword "all" as database user in user name map', + log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]); + +# Success as the regexp of the database user matches +reset_pg_ident($node, 'mypeermap', $system_user, qq{/^testm.*\$}); +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'with regexp of database user in user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +# Failure as the regexp of the database user does not match. +reset_pg_ident($node, 'mypeermap', $system_user, qq{/^doesnotmatch.*\$}); +test_role( + $node, + qq{testmapuser}, + 'peer', + 2, + 'with bad regexp of database user in user name map', + log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]); + # Test with regular expression in user name map. # Extract the last 3 characters from the system_user # or the entire system_user (if its length is <= -3). my $regex_test_string = substr($system_user, -3); -# Success as the regular expression matches. +# Success as the system user regular expression matches. reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, 'testmapuser'); test_role( @@ -136,10 +185,33 @@ test_role( qq{testmapuser}, 'peer', 0, - 'with regular expression in user name map', + 'with regexp of system user in user name map', log_like => [qr/connection authenticated: identity="$system_user" method=peer/]); +# Success as both regular expressions match. +reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, + qq{/^testm.*\$}); +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'with regexps for both system and database user in user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +# Success as the regular expression matches and database role is the "all" +# keyword. +reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, 'all'); +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'with regexp of system user and keyword "all" in user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); # Success as the regular expression matches and \1 is replaced in the given # subexpression. @@ -179,17 +251,91 @@ test_role( ]); # Concatenate system_user to system_user. -$regex_test_string = $system_user . $system_user; +my $bad_regex_test_string = $system_user . $system_user; -# Failure as the regular expression does not match. -reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, +# Failure as the regexp of system user does not match. +reset_pg_ident($node, 'mypeermap', qq{/^.*$bad_regex_test_string\$}, 'testmapuser'); test_role( $node, qq{testmapuser}, 'peer', 2, - 'with regular expression in user name map', + 'with regexp of system user in user name map', + log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]); + +# Test using a group role match for the database user. +reset_pg_ident($node, 'mypeermap', $system_user, '+testmapgroup'); + +test_role($node, qq{testmapuser}, 'peer', 0, 'plain user with group', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +test_role( + $node, qq{testmapgroup}, 'peer', 2, + 'group user with group', + log_like => [qr/role "testmapgroup" is not permitted to log in/]); + +# Now apply quotes to the group match, nullifying its effect. +reset_pg_ident($node, 'mypeermap', $system_user, '"+testmapgroup"'); +test_role( + $node, + qq{testmapuser}, + 'peer', + 2, + 'plain user with quoted group name', log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]); +# Test using a regexp for the system user, with a group membership +# check for the database user. +reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, + '+testmapgroup'); + +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'regexp of system user as group member', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +test_role( + $node, + qq{testmapgroup}, + 'peer', + 2, + 'regexp of system user as non-member of group', + log_like => [qr/role "testmapgroup" is not permitted to log in/]); + +# Test that membership checks and regexes will use literal \1 instead of +# replacing it, as subexpression replacement is not allowed in this case. +reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string(.*)\$}, + '+testmapgroupliteral\\1'); + +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'membership check with literal \1', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +# Do the same with a quoted regular expression for the database user this +# time. No replacement of \1 is done. +reset_pg_ident( + $node, 'mypeermap', + qq{/^.*$regex_test_string(.*)\$}, + '"/^testmapgroupliteral\\\\1$"'); + +test_role( + $node, + 'testmapgroupliteral\\\\1', + 'peer', + 0, + 'regexp of database user with literal \1', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + done_testing(); |