Over at [1] I speculated that it might be a good idea to allow +grouprole type user names in pg_ident.conf. The use case I have in mind is where the user authenticates to pgbouncer and then pgbouncer connects as the user using a client certificate. Without this mechanism that means that you need a mapping rule for each user in pg_ident.conf, which doesn't scale very well, but with this mechanism all you have to do is grant the specified role to users. So here's a small patch for that.
Comments welcome. cheers andrew [1] https://postgr.es/m/6912eb9c-4905-badb-ad87-eeca8ace1...@dunslane.net -- 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..f48bfce64a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -961,6 +961,12 @@ 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>. + </para> <tip> <para> diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index f5a2cc53be..8715eb1568 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2900,9 +2900,16 @@ 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] == '+') + { + 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 +2926,28 @@ 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) + 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();