On 2023-01-09 Mo 13:24, Nathan Bossart wrote: > On Mon, Jan 09, 2023 at 08:00:26AM -0500, Andrew Dunstan wrote: >> + If the <replaceable>database-username</replaceable> begins with a >> + <literal>+</literal> character, then the operating system user can login >> as >> + any user belonging to that role, similarly to how user names beginning >> with >> + <literal>+</literal> are treated in <literal>pg_hba.conf</literal>. > I would ѕuggest making it clear that this means role membership and not > privileges via INHERIT.
I've adapted a sentence from the pg_hba.conf documentation so we stay consistent. >> - if (case_insensitive) >> + if (regexp_pgrole[0] == '+') >> + { >> + Oid roleid = get_role_oid(pg_role, true); >> + if (is_member(roleid, regexp_pgrole +1)) >> + *found_p = true; >> + } >> + else if (case_insensitive) > It looks like the is_member() check will always be case-sensitive. Should > it respect the value of case_insensitive? If not, I think there should be > a brief comment explaining why. It's not really relevant. We're not comparing role names here; rather we look up two roles and then ask if one is a member of the other. I've added a comment. Thanks for the review (I take it you're generally in favor). cheers andrew -- Andrew Dunstan EDB: https://www.enterprisedb.com
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index cc8c59206c..a1c9a2eefc 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -961,6 +961,15 @@ mymap /^(.*)@otherdomain\.com$ guest <literal>@mydomain.com</literal>, and allow any user whose system name ends with <literal>@otherdomain.com</literal> to log in as <literal>guest</literal>. </para> + <para> + If the <replaceable>database-username</replaceable> begins with a + <literal>+</literal> character, then the operating system user can login as + any user belonging to that role, similarly to how user names beginning with + <literal>+</literal> are treated in <literal>pg_hba.conf</literal>. + Thus, a <literal>+</literal> mark means <quote>match any of the roles that + are directly or indirectly members of this role</quote>, while a name + without a <literal>+</literal> mark matches only that specific role. + </para> <tip> <para> diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index f5a2cc53be..2d186639b1 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2900,9 +2900,20 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, /* * now check if the username actually matched what the user is trying - * to connect as + * to connect as. First check if it's a member of a specified group + * role. */ - if (case_insensitive) + if (regexp_pgrole[0] == '+') + { + /* + * Since we're not comparing role names here, use of case + * insensitive matching doesn't really apply. + */ + Oid roleid = get_role_oid(pg_role, true); + if (is_member(roleid, regexp_pgrole +1)) + *found_p = true; + } + else if (case_insensitive) { if (pg_strcasecmp(regexp_pgrole, pg_role) == 0) *found_p = true; @@ -2919,16 +2930,29 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, else { /* Not regular expression, so make complete match */ - if (case_insensitive) + + char *map_role = identLine->pg_role; + + if (case_insensitive ? + (pg_strcasecmp(identLine->token->string, ident_user) != 0) : + (strcmp(identLine->token->string, ident_user) != 0)) + return; + + if (map_role[0] == '+') { - if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 && - pg_strcasecmp(identLine->token->string, ident_user) == 0) + /* see comment above re case insensitive matching in this case */ + Oid roleid = get_role_oid(pg_role, true); + if (is_member(roleid, ++map_role)) + *found_p = true; + } + else if (case_insensitive) + { + if (pg_strcasecmp(map_role, pg_role) == 0) *found_p = true; } else { - if (strcmp(identLine->pg_role, pg_role) == 0 && - strcmp(identLine->token->string, ident_user) == 0) + if (strcmp(map_role, pg_role) == 0) *found_p = true; } } diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl index 24cefd14e0..93ef2b8f68 100644 --- a/src/test/authentication/t/003_peer.pl +++ b/src/test/authentication/t/003_peer.pl @@ -100,6 +100,8 @@ if (find_in_log( # Add a database role, 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"); # Extract as well the system user for the user name map. my $system_user = @@ -142,17 +144,59 @@ 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\$}, +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 bad regular expression in user name map', log_like => [qr/no match in usermap "mypeermap" for user "testmapuser"/]); +# test using a group role match +# first with a plain 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/]); + +# ... then with a regex user +reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, '+testmapgroup'); + +test_role( + $node, + qq{testmapuser}, + 'peer', + 0, + 'regex user with group', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + +test_role( + $node, + qq{testmapgroup}, + 'peer', + 2, + 'regex group user with group', + log_like => + [qr/role "testmapgroup" is not permitted to log in/]); + done_testing();