From 0ea0feb4c332564b18b424ab142ad1dac4e188a3 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Tue, 27 Dec 2022 15:51:18 +0100 Subject: [PATCH v1] Support using all for the db user in pg_ident.conf While pg_hba.conf has supported the "all" keyword since a very long time, pg_ident.conf doesn't have this same functionality. This changes permission checking in pg_ident.conf to handle "all" differently from any other value in the database-username column. If "all" is specified and the system-user matches the identifier, then the user is allowed to authenticate no matter what user it tries to authenticate as. This change makes it much easier to have a certain database administrator peer or cert authentication, that allows connecting as any user. Without this change you would need to add a line to pg_ident.conf for every user that is in the database. --- doc/src/sgml/client-auth.sgml | 5 ++- src/backend/libpq/hba.c | 54 +++++++++++++++++---------- src/backend/utils/adt/hbafuncs.c | 4 +- src/include/libpq/hba.h | 4 +- src/test/authentication/t/003_peer.pl | 21 +++++++++++ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index cc8c59206c..64c7713057 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -941,7 +941,10 @@ local db1,db2,@demodbs all md5 implying that they are equivalent. The connection will be allowed if there is any map entry that pairs the user name obtained from the external authentication system with the database user name that the - user has requested to connect as. + user has requested to connect as. The value all + can be used as the database-username to specify + that all users are matched. Quoting all makes the keyword + lose its special meaning. If the system-username field starts with a slash (/), diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 870b907697..6a0e7034f9 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2792,7 +2792,7 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel) token = linitial(tokens); /* Copy the ident user token */ - parsedline->token = copy_auth_token(token); + parsedline->systemuser = copy_auth_token(token); /* Get the PG rolename token */ field = lnext(tok_line->fields, field); @@ -2800,13 +2800,13 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel) tokens = lfirst(field); IDENT_MULTI_VALUE(tokens); token = linitial(tokens); - parsedline->pg_role = pstrdup(token->string); + parsedline->dbuser = copy_auth_token(token); /* * Now that the field validation is done, compile a regex from the user * token, if necessary. */ - if (regcomp_auth_token(parsedline->token, file_name, line_num, + if (regcomp_auth_token(parsedline->systemuser, file_name, line_num, err_msg, elevel)) { /* err_msg includes the error to report */ @@ -2835,7 +2835,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, return; /* Match? */ - if (token_has_regexp(identLine->token)) + if (token_has_regexp(identLine->systemuser)) { /* * Process the system username as a regular expression that returns @@ -2847,7 +2847,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, char *ofs; char *regexp_pgrole; - r = regexec_auth_token(ident_user, identLine->token, 2, matches); + r = regexec_auth_token(ident_user, identLine->systemuser, 2, matches); if (r) { char errstr[100]; @@ -2855,17 +2855,26 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, if (r != REG_NOMATCH) { /* REG_NOMATCH is not an error, everything else is */ - pg_regerror(r, identLine->token->regex, errstr, sizeof(errstr)); + pg_regerror(r, identLine->systemuser->regex, errstr, sizeof(errstr)); ereport(LOG, (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), errmsg("regular expression match for \"%s\" failed: %s", - identLine->token->string + 1, errstr))); + identLine->systemuser->string + 1, errstr))); *error_p = true; } return; } - if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL) + /* + * We can return early if the dbuser is the all keyword + */ + if (token_is_keyword(identLine->dbuser, "all")) + { + *found_p = true; + return; + } + + if ((ofs = strstr(identLine->dbuser->string, "\\1")) != NULL) { int offset; @@ -2875,7 +2884,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, ereport(LOG, (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"", - identLine->token->string + 1, identLine->pg_role))); + identLine->systemuser->string + 1, identLine->dbuser->string))); *error_p = true; return; } @@ -2884,9 +2893,9 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, * length: original length minus length of \1 plus length of match * plus null terminator */ - regexp_pgrole = palloc0(strlen(identLine->pg_role) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1); - offset = ofs - identLine->pg_role; - memcpy(regexp_pgrole, identLine->pg_role, offset); + regexp_pgrole = palloc0(strlen(identLine->dbuser->string) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1); + offset = ofs - identLine->dbuser->string; + memcpy(regexp_pgrole, identLine->dbuser->string, offset); memcpy(regexp_pgrole + offset, ident_user + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); @@ -2895,7 +2904,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, else { /* no substitution, so copy the match */ - regexp_pgrole = pstrdup(identLine->pg_role); + regexp_pgrole = pstrdup(identLine->dbuser->string); } /* @@ -2918,17 +2927,20 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name, } else { - /* Not regular expression, so make complete match */ if (case_insensitive) { - if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 && - pg_strcasecmp(identLine->token->string, ident_user) == 0) + if ( + (token_is_keyword(identLine->dbuser, "all") || + pg_strcasecmp(identLine->dbuser->string, pg_role) == 0) && + pg_strcasecmp(identLine->systemuser->string, ident_user) == 0) *found_p = true; } else { - if (strcmp(identLine->pg_role, pg_role) == 0 && - strcmp(identLine->token->string, ident_user) == 0) + if ( + (token_is_keyword(identLine->dbuser, "all") || + strcmp(identLine->dbuser->string, pg_role) == 0) && + strcmp(identLine->systemuser->string, ident_user) == 0) *found_p = true; } } @@ -3073,7 +3085,8 @@ load_ident(void) foreach(parsed_line_cell, new_parsed_lines) { newline = (IdentLine *) lfirst(parsed_line_cell); - free_auth_token(newline->token); + free_auth_token(newline->systemuser); + free_auth_token(newline->dbuser); } MemoryContextDelete(ident_context); return false; @@ -3085,7 +3098,8 @@ load_ident(void) foreach(parsed_line_cell, parsed_ident_lines) { newline = (IdentLine *) lfirst(parsed_line_cell); - free_auth_token(newline->token); + free_auth_token(newline->systemuser); + free_auth_token(newline->dbuser); } } if (parsed_ident_context != NULL) diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index 633eda30d3..80a8b02b88 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -492,8 +492,8 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, if (ident != NULL) { values[index++] = CStringGetTextDatum(ident->usermap); - values[index++] = CStringGetTextDatum(ident->token->string); - values[index++] = CStringGetTextDatum(ident->pg_role); + values[index++] = CStringGetTextDatum(ident->systemuser->string); + values[index++] = CStringGetTextDatum(ident->dbuser->string); } else { diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 90c51ad6fa..3214a27e66 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -142,8 +142,8 @@ typedef struct IdentLine int linenumber; char *usermap; - char *pg_role; - AuthToken *token; + AuthToken *systemuser; + AuthToken *dbuser; } IdentLine; /* diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl index 26c34d05d3..f6d57f77f0 100644 --- a/src/test/authentication/t/003_peer.pl +++ b/src/test/authentication/t/003_peer.pl @@ -123,11 +123,32 @@ 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'); + +# Success as the database role is the "all" keyword +test_role($node, qq{testmapuser}, 'peer', 0, 'with user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + # 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 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 regular expression in user name map', + log_like => + [qr/connection authenticated: identity="$system_user" method=peer/]); + # Success as the regular expression matches. reset_pg_ident($node, 'mypeermap', qq{/^.*$regex_test_string\$}, 'testmapuser'); -- 2.34.1