From: Drouvot, Bertrand <bertranddrouvot.pg@gmail.com>
To: Michael Paquier <michael@paquier.xyz>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Jacob Champion <jchampion@timescale.com>
Cc: PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Subject: Re: Patch proposal: make use of regular expressions for the username in pg_hba.conf
Date: Mon, 10 Oct 2022 09:00:06 +0200
Message-ID: <e346f82f-4a6b-22f7-d6c1-cef16d472de9@gmail.com> (raw)
In-Reply-To: <Yz0xO0emJ+mxtj2a@paquier.xyz>
References: <fff0d7c1-8ad4-76a1-9db3-0ab6ec338bf7@amazon.com>
<CAAWbhmhifLJfCGk1zMjWNHg+uGo9SohdSEqsFo0wa2rBNCB85g@mail.gmail.com>
<4177827.1662684400@sss.pgh.pa.us>
<CAAWbhmjxPcvUAKhpb1mu8PvszaiL7yFi_0ZUQVbfiowsFVaW4A@mail.gmail.com>
<Yyk5rfYUTylD8XRg@paquier.xyz>
<572017.1663646973@sss.pgh.pa.us>
<YylB+vmF9dti1Alq@paquier.xyz>
<e3091081-dc98-7f2b-829f-c247b9337653@gmail.com>
<Yz0xO0emJ+mxtj2a@paquier.xyz>
Hi,
On 10/5/22 9:24 AM, Michael Paquier wrote:
> On Tue, Sep 20, 2022 at 01:33:09PM +0200, Drouvot, Bertrand wrote:
> Anyway, I have looked at the patch.
>
> + List *roles_re;
> + List *databases_re;
> + regex_t hostname_re;
> I am surprised by the approach of using separate lists for the regular
> expressions and the raw names. Wouldn't it be better to store
> everything in a single list but assign an entry type? In this case it
> would be either regex or plain string. This would minimize the
> footprint of the changes (no extra arguments *_re in the routines
> checking for a match on the roles, databases or hosts). And it seems
> to me that this would make unnecessary the use of re_num here and
> there.
Please find attached v5 addressing this. I started with an union but it
turns out that we still need the plain string when a regex is used. This
is not needed for the authentication per say but for fill_hba_line(). So
I ended up creating a new struct without union in v5.
> The hostname is different, of course, requiring only an extra
> field for its type, or something like that.
I'm using the same new struct as described above for the hostname.
>
> Perhaps the documentation would gain in clarity if there were more
> examples, like a set of comma-separated examples (mix of regex and raw
> strings for example, for all the field types that gain support for
> regexes)?
>
Right, I added more examples in v5.
> -$node->append_conf('postgresql.conf', "log_connections = on\n");
> +$node->append_conf(
> + 'postgresql.conf', qq{
> +listen_addresses = '127.0.0.1'
> +log_connections = on
> +});
> Hmm. I think that we may need to reconsider the location of the tests
> for the regexes with the host name, as the "safe" regression tests
> should not switch listen_addresses. One location where we already do
> that is src/test/ssl/, so these could be moved there.
Good point, I moved the hostname related tests in src/test/ssl.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.comAttachments:v5-0001-hba_with_regexp.patch (text/plain, 25.4K) downloaddiff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgmlindex c6f1b70fd3..406628ef35 100644--- a/doc/src/sgml/client-auth.sgml+++ b/doc/src/sgml/client-auth.sgml@@ -235,8 +235,9 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
logical replication connections do specify it.
Otherwise, this is the name of
a specific <productname>PostgreSQL</productname> database.
- Multiple database names can be supplied by separating them with- commas. A separate file containing database names can be specified by+ Multiple database names and/or regular expressions preceded by <literal>/</literal>+ can be supplied by separating them with commas.+ A separate file containing database names can be specified by
preceding the file name with <literal>@</literal>.
</para>
</listitem>
@@ -249,7 +250,8 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
Specifies which database user name(s) this record
matches. The value <literal>all</literal> specifies that it
matches all users. Otherwise, this is either the name of a specific
- database user, or a group name preceded by <literal>+</literal>.+ database user, a regular expression preceded by <literal>/</literal>+ or a group name preceded by <literal>+</literal>.
(Recall that there is no real distinction between users and groups
in <productname>PostgreSQL</productname>; a <literal>+</literal> mark really means
<quote>match any of the roles that are directly or indirectly members
@@ -258,7 +260,8 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
considered to be a member of a role if they are explicitly a member
of the role, directly or indirectly, and not just by virtue of
being a superuser.
- Multiple user names can be supplied by separating them with commas.+ Multiple user names and/or regular expressions preceded by <literal>/</literal>+ can be supplied by separating them with commas.
A separate file containing user names can be specified by preceding the
file name with <literal>@</literal>.
</para>
@@ -270,8 +273,9 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl
<listitem>
<para>
Specifies the client machine address(es) that this record
- matches. This field can contain either a host name, an IP- address range, or one of the special key words mentioned below.+ matches. This field can contain either a host name, a regular expression+ preceded by <literal>/</literal> representing host names, an IP address range,+ or one of the special key words mentioned below.
</para>
<para>
@@ -739,6 +743,24 @@ host all all ::1/128 trust
# TYPE DATABASE USER ADDRESS METHOD
host all all localhost trust
+# The same using a regular expression for host name, which allows connection for+# host name ending with "test".+#+# TYPE DATABASE USER ADDRESS METHOD+host all all /^.*test$ trust++# The same using regular expression for DATABASE, which allows connection to the+# db1 and testdb databases and any database with a name ending with "test".+#+# TYPE DATABASE USER ADDRESS METHOD+local db1,/^.*test$,testdb all /^.*test$ trust++# The same using regular expression for USER, which allows connection to the+# user1 and testuser users and any user with a name ending with "test".+#+# TYPE DATABASE USER ADDRESS METHOD+local db1,/^.*test$,testdb user1,/^.*test$,testuser /^.*test$ trust+
# Allow any user from any host with IP address 192.168.93.x to connect
# to database "postgres" as the same user name that ident reports for
# the connection (typically the operating system user name).
@@ -785,16 +807,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 ending 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.cindex 4637426d62..6fe8c40098 100644--- a/src/backend/libpq/hba.c+++ b/src/backend/libpq/hba.c@@ -66,6 +66,7 @@ typedef struct check_network_data
} check_network_data;
+#define token_is_regexp(t) (t->is_regex)
#define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
#define token_matches(t, k) (strcmp(t->string, k) == 0)
@@ -117,6 +118,9 @@ static List *tokenize_inc_file(List *tokens, const char *outer_filename,
const char *inc_filename, int elevel, char **err_msg);
static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
int elevel, char **err_msg);
+static bool token_regcomp(regex_t *re, char *string, char *filename,+ int line_num, char **err_msg, int elevel);+static bool token_regexec(const char *match, regex_t *re);
/*
@@ -574,24 +578,30 @@ 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)
{
ListCell *cell;
- AuthToken *tok;+ AuthTokenOrRegex *tokreg;
foreach(cell, tokens)
{
- tok = lfirst(cell);- if (!tok->quoted && tok->string[0] == '+')+ tokreg = lfirst(cell);+ if (!token_is_regexp(tokreg))
{
- if (is_member(roleid, tok->string + 1))+ if (!tokreg->authtoken->quoted && tokreg->authtoken->string[0] == '+')+ {+ if (is_member(roleid, tokreg->authtoken->string + 1))+ return true;+ }+ else if (token_matches(tokreg->authtoken, role) ||+ token_is_keyword(tokreg->authtoken, "all"))
return true;
}
- else if (token_matches(tok, role) ||- token_is_keyword(tok, "all"))+ else if (token_regexec(role, tokreg->regex))
return true;
}
return false;
@@ -604,36 +614,41 @@ static bool
check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
{
ListCell *cell;
- AuthToken *tok;+ AuthTokenOrRegex *tokreg;
foreach(cell, tokens)
{
- tok = lfirst(cell);- if (am_walsender && !am_db_walsender)- {- /*- * physical replication walsender connections can only match- * replication keyword- */- if (token_is_keyword(tok, "replication"))- return true;- }- else if (token_is_keyword(tok, "all"))- return true;- else if (token_is_keyword(tok, "sameuser"))+ tokreg = lfirst(cell);+ if (!token_is_regexp(tokreg))
{
- if (strcmp(dbname, role) == 0)+ if (am_walsender && !am_db_walsender)+ {+ /*+ * physical replication walsender connections can only match+ * replication keyword+ */+ if (token_is_keyword(tokreg->authtoken, "replication"))+ return true;+ }+ else if (token_is_keyword(tokreg->authtoken, "all"))
return true;
- }- else if (token_is_keyword(tok, "samegroup") ||- token_is_keyword(tok, "samerole"))- {- if (is_member(roleid, dbname))+ else if (token_is_keyword(tokreg->authtoken, "sameuser"))+ {+ if (strcmp(dbname, role) == 0)+ return true;+ }+ else if (token_is_keyword(tokreg->authtoken, "samegroup") ||+ token_is_keyword(tokreg->authtoken, "samerole"))+ {+ if (is_member(roleid, dbname))+ return true;+ }+ else if (token_is_keyword(tokreg->authtoken, "replication"))+ continue; /* never match this if not walsender */+ else if (token_matches(tokreg->authtoken, dbname))
return true;
}
- else if (token_is_keyword(tok, "replication"))- continue; /* never match this if not walsender */- else if (token_matches(tok, dbname))+ else if (token_regexec(dbname, tokreg->regex))
return true;
}
return false;
@@ -681,7 +696,7 @@ hostname_match(const char *pattern, const char *actual_hostname)
* Check to see if a connecting IP matches a given host name.
*/
static bool
-check_hostname(hbaPort *port, const char *hostname)+check_hostname(hbaPort *port, const AuthTokenOrRegex *tok_hostname)
{
struct addrinfo *gai_result,
*gai;
@@ -712,8 +727,13 @@ check_hostname(hbaPort *port, const char *hostname)
port->remote_hostname = pstrdup(remote_hostname);
}
+ if (token_is_regexp(tok_hostname))+ {+ if (!token_regexec(port->remote_hostname, tok_hostname->regex))+ return false;+ }
/* Now see if remote host name matches this pg_hba line */
- if (!hostname_match(hostname, port->remote_hostname))+ else if (!hostname_match(tok_hostname->authtoken->string, port->remote_hostname))
return false;
/* If we already verified the forward lookup, we're done */
@@ -761,7 +781,7 @@ check_hostname(hbaPort *port, const char *hostname)
if (!found)
elog(DEBUG2, "pg_hba.conf host name \"%s\" rejected because address resolution did not return a match with IP address of client",
- hostname);+ tok_hostname->authtoken->string);
port->remote_hostname_resolv = found ? +1 : -1;
@@ -939,13 +959,13 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
struct addrinfo *gai_result;
struct addrinfo hints;
int ret;
- char *cidr_slash;
char *unsupauth;
ListCell *field;
List *tokens;
ListCell *tokencell;
AuthToken *token;
HbaLine *parsedline;
+ char *cidr_slash = NULL; /* keep compiler quiet */
parsedline = palloc0(sizeof(HbaLine));
parsedline->linenumber = line_num;
@@ -1052,8 +1072,31 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
tokens = lfirst(field);
foreach(tokencell, tokens)
{
- parsedline->databases = lappend(parsedline->databases,- copy_auth_token(lfirst(tokencell)));+ AuthTokenOrRegex *tokreg;+ AuthToken *tok = lfirst(tokencell);++ tokreg = (AuthTokenOrRegex *) palloc0(sizeof(AuthTokenOrRegex));+ tokreg->authtoken = copy_auth_token(lfirst(tokencell));+ if (tok->string[0] == '/')+ {+ /*+ * When tok->string starts with a slash, treat it as a regular+ * expression. Pre-compile it.+ */+ regex_t *re;++ tokreg->is_regex = true;+ re = (regex_t *) palloc(sizeof(regex_t));+ if (token_regcomp(re, tok->string + 1, HbaFileName, line_num,+ err_msg, elevel))+ tokreg->regex = re;+ else+ return NULL;+ }+ else+ tokreg->is_regex = false;++ parsedline->databases = lappend(parsedline->databases, tokreg);
}
/* Get the roles. */
@@ -1072,8 +1115,31 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
tokens = lfirst(field);
foreach(tokencell, tokens)
{
- parsedline->roles = lappend(parsedline->roles,- copy_auth_token(lfirst(tokencell)));+ AuthTokenOrRegex *tokreg;+ AuthToken *tok = lfirst(tokencell);++ tokreg = (AuthTokenOrRegex *) palloc0(sizeof(AuthTokenOrRegex));+ tokreg->authtoken = copy_auth_token(lfirst(tokencell));+ if (tok->string[0] == '/')+ {+ /*+ * When tok->string starts with a slash, treat it as a regular+ * expression. Pre-compile it.+ */+ regex_t *re;++ tokreg->is_regex = true;+ re = (regex_t *) palloc(sizeof(regex_t));+ if (token_regcomp(re, tok->string + 1, HbaFileName, line_num,+ err_msg, elevel))+ tokreg->regex = re;+ else+ return NULL;+ }+ else+ tokreg->is_regex = false;++ parsedline->roles = lappend(parsedline->roles, tokreg);
}
if (parsedline->conntype != ctLocal)
@@ -1120,6 +1186,8 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
}
else
{
+ bool is_regexp = token->string[0] == '/' ? true : false;+
/* IP and netmask are specified */
parsedline->ip_cmp_method = ipCmpMask;
@@ -1127,9 +1195,12 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
str = pstrdup(token->string);
/* Check if it has a CIDR suffix and if so isolate it */
- cidr_slash = strchr(str, '/');- if (cidr_slash)- *cidr_slash = '\0';+ if (!is_regexp)+ {+ cidr_slash = strchr(str, '/');+ if (cidr_slash)+ *cidr_slash = '\0';+ }
/* Get the IP address either way */
hints.ai_flags = AI_NUMERICHOST;
@@ -1149,7 +1220,16 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
parsedline->addrlen = gai_result->ai_addrlen;
}
else if (ret == EAI_NONAME)
- parsedline->hostname = str;+ {+ parsedline->tok_hostname.is_regex = false;++ /*+ * This is ok to copy the token->string and not str here, as+ * we'll error and report "specifying both host name and CIDR+ * mask is invalid" below should they differ.+ */+ parsedline->tok_hostname.authtoken = copy_auth_token(token);+ }
else
{
ereport(elevel,
@@ -1168,9 +1248,9 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
pg_freeaddrinfo_all(hints.ai_family, gai_result);
/* Get the netmask */
- if (cidr_slash)+ if (cidr_slash && !is_regexp)
{
- if (parsedline->hostname)+ if (parsedline->tok_hostname.authtoken)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
@@ -1199,7 +1279,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
parsedline->masklen = parsedline->addrlen;
pfree(str);
}
- else if (!parsedline->hostname)+ else if (!parsedline->tok_hostname.authtoken && !is_regexp)
{
/* Read the mask field. */
pfree(str);
@@ -1261,9 +1341,25 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
return NULL;
}
}
+ else if (is_regexp)+ {+ /*+ * When token->string starts with a slash, treat it as a+ * regular expression. Pre-compile it.+ */+ regex_t *re;++ re = (regex_t *) palloc(sizeof(regex_t));+ parsedline->tok_hostname.is_regex = true;+ if (!token_regcomp(re,+ token->string + 1, HbaFileName,+ line_num, err_msg, elevel))+ return NULL;++ parsedline->tok_hostname.regex = re;+ }
}
} /* != ctLocal */
-
/* Get the authentication method */
field = lnext(tok_line->fields, field);
if (!field)
@@ -2132,10 +2228,11 @@ check_hba(hbaPort *port)
switch (hba->ip_cmp_method)
{
case ipCmpMask:
- if (hba->hostname)+ if (hba->tok_hostname.authtoken || hba->tok_hostname.is_regex)
{
if (!check_hostname(port,
- hba->hostname))+ &hba->tok_hostname))+
continue;
}
else
@@ -2342,34 +2439,9 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
* When system username starts with a slash, treat it as a regular
* expression. Pre-compile it.
*/
- int r;- pg_wchar *wstr;- int wlen;-- wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * sizeof(pg_wchar));- wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,- wstr, strlen(parsedline->ident_user + 1));-- r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);- if (r)- {- char errstr[100];-- pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));- ereport(elevel,- (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),- errmsg("invalid regular expression \"%s\": %s",- parsedline->ident_user + 1, errstr),- errcontext("line %d of configuration file \"%s\"",- line_num, IdentFileName)));-- *err_msg = psprintf("invalid regular expression \"%s\": %s",- parsedline->ident_user + 1, errstr);-- pfree(wstr);+ if (!token_regcomp(&parsedline->re, parsedline->ident_user + 1,+ IdentFileName, line_num, err_msg, elevel))
return NULL;
- }- pfree(wstr);
}
return parsedline;
@@ -2706,3 +2778,68 @@ hba_authname(UserAuth auth_method)
return UserAuthName[auth_method];
}
++/*+ * Compile the regular expression "re" and return whether it compiles+ * successfully or not.+ *+ * If not, the last 4 parameters are used to add extra details while reporting+ * the error.+ */+static bool+token_regcomp(regex_t *re, char *string, char *filename, int line_num,+ char **err_msg, int elevel)+{+ int r;+ pg_wchar *wstr;+ int wlen;++ wstr = palloc((strlen(string) + 1) * sizeof(pg_wchar));+ wlen = pg_mb2wchar_with_len(string,+ wstr, strlen(string));++ r = pg_regcomp(re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);+ if (r)+ {+ char errstr[100];++ pg_regerror(r, re, errstr, sizeof(errstr));+ ereport(elevel,+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),+ errmsg("invalid regular expression \"%s\": %s",+ string, errstr),+ errcontext("line %d of configuration file \"%s\"",+ line_num, filename)));++ *err_msg = psprintf("invalid regular expression \"%s\": %s",+ string, errstr);++ pfree(wstr);+ return false;+ }++ pfree(wstr);+ return true;+}++/*+ * Return whether "match" is matching the regular expression "re" or not.+ */+static bool+token_regexec(const char *match, regex_t *re)+{+ pg_wchar *wmatchstr;+ int wmatchlen;++ wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));+ wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));++ if (pg_regexec(re, wmatchstr, wmatchlen, 0, NULL, 0, NULL, 0) == REG_OKAY)+ {+ pfree(wmatchstr);+ return true;+ }++ pfree(wmatchstr);+ return false;+}diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.cindex 9e5794071c..2b7ab0bd63 100644--- a/src/backend/utils/adt/hbafuncs.c+++ b/src/backend/utils/adt/hbafuncs.c@@ -242,9 +242,9 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
foreach(lc, hba->databases)
{
- AuthToken *tok = lfirst(lc);+ AuthTokenOrRegex *tok = lfirst(lc);- names = lappend(names, tok->string);+ names = lappend(names, tok->authtoken->string);
}
values[index++] = PointerGetDatum(strlist_to_textarray(names));
}
@@ -259,9 +259,9 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
foreach(lc, hba->roles)
{
- AuthToken *tok = lfirst(lc);+ AuthTokenOrRegex *tok = lfirst(lc);- roles = lappend(roles, tok->string);+ roles = lappend(roles, tok->authtoken->string);
}
values[index++] = PointerGetDatum(strlist_to_textarray(roles));
}
@@ -274,10 +274,8 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
switch (hba->ip_cmp_method)
{
case ipCmpMask:
- if (hba->hostname)- {- addrstr = hba->hostname;- }+ if (hba->tok_hostname.authtoken)+ addrstr = hba->tok_hostname.authtoken->string;
else
{
/*
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.hindex d06da81806..7a53a31771 100644--- a/src/include/libpq/hba.h+++ b/src/include/libpq/hba.h@@ -77,6 +77,32 @@ typedef enum ClientCertName
clientCertDN
} ClientCertName;
+/*+ * A single string token lexed from an authentication configuration file+ * (pg_ident.conf or pg_hba.conf), together with whether the token has+ * been quoted.+ */+typedef struct AuthToken+{+ char *string;+ bool quoted;+} AuthToken;++/*+ * Distinguish the case a token has to be treated as a regular+ * expression or not.+ */+typedef struct AuthTokenOrRegex+{+ bool is_regex;++ /*+ * Not an union as we still need the token string for fill_hba_line().+ */+ AuthToken *authtoken;+ regex_t *regex;+} AuthTokenOrRegex;+
typedef struct HbaLine
{
int linenumber;
@@ -89,7 +115,7 @@ typedef struct HbaLine
struct sockaddr_storage mask;
int masklen; /* zero if we don't have a valid mask */
IPCompareMethod ip_cmp_method;
- char *hostname;+ AuthTokenOrRegex tok_hostname;
UserAuth auth_method;
char *usermap;
char *pamservice;
@@ -132,17 +158,6 @@ typedef struct IdentLine
regex_t re;
} IdentLine;
-/*- * A single string token lexed from an authentication configuration file- * (pg_ident.conf or pg_hba.conf), together with whether the token has- * been quoted.- */-typedef struct AuthToken-{- char *string;- bool quoted;-} AuthToken;-
/*
* TokenizedAuthLine represents one line lexed from an authentication
* configuration file. Each item in the "fields" list is a sub-list of
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.plindex 93df77aa4e..5063a2601c 100644--- a/src/test/authentication/t/001_password.pl+++ b/src/test/authentication/t/001_password.pl@@ -81,6 +81,14 @@ $node->safe_psql(
GRANT ALL ON sysuser_data TO md5_role;");
$ENV{"PGPASSWORD"} = 'pass';
+# Create a role that contains a comma to stress the parsing.+$node->safe_psql('postgres',+ q{SET password_encryption='md5'; CREATE ROLE "md5,role" LOGIN PASSWORD 'pass';}+);++# Create a database to test regular expression.+$node->safe_psql('postgres', "CREATE database regex_testdb;");+
# For "trust" method, all users should be able to connect. These users are not
# considered to be authenticated.
reset_pg_hba($node, 'all', 'all', 'trust');
@@ -200,4 +208,37 @@ append_to_file(
test_conn($node, 'user=md5_role', 'password from pgpass', 0);
+# Testing with regular expression for username. Note that the third regex+# matches in this case.+reset_pg_hba($node, 'all', '/^.*nomatch.*$, baduser, /^md.*$', 'password');+test_conn($node, 'user=md5_role', 'password, matching regexp for username',+ 0);++# The third regex does not match anymore.+reset_pg_hba($node, 'all', '/^.*nomatch.*$, baduser, /^m_d.*$', 'password');+test_conn($node, 'user=md5_role',+ 'password, non matching regexp for username',+ 2, log_unlike => [qr/connection authenticated:/]);++# test with a comma in the regular expression+reset_pg_hba($node, 'all', '"/^.*5,.*e$"', 'password');+test_conn($node, 'user=md5,role', 'password', 'matching regexp for username',+ 0);++# Testing with regular expression for dbname. The third regex matches.+reset_pg_hba($node, '/^.*nomatch.*$, baddb, /^regex_t.*b$', 'all',+ 'password');+test_conn(+ $node, 'user=md5_role dbname=regex_testdb', 'password,+ matching regexp for dbname', 0);++# The third regex does not match anymore.+reset_pg_hba($node, '/^.*nomatch.*$, baddb, /^regex_t.*ba$',+ 'all', 'password');+test_conn(+ $node,+ 'user=md5_role dbname=regex_testdb',+ 'password, non matching regexp for dbname',+ 2, log_unlike => [qr/connection authenticated:/]);+
done_testing();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.plindex deaa4aa086..b575557b37 100644--- a/src/test/ssl/t/002_scram.pl+++ b/src/test/ssl/t/002_scram.pl@@ -22,7 +22,8 @@ if ($ENV{with_ssl} ne 'openssl')
}
elsif ($ENV{PG_TEST_EXTRA} !~ /\bssl\b/)
{
- plan skip_all => 'Potentially unsafe test SSL not enabled in PG_TEST_EXTRA';+ plan skip_all =>+ 'Potentially unsafe test SSL not enabled in PG_TEST_EXTRA';
}
my $ssl_server = SSL::Server->new();
@@ -37,6 +38,20 @@ sub switch_server_cert
$ssl_server->switch_server_cert(@_);
}
+# Delete pg_hba.conf from the given node, add a new entry to it+# and then execute a reload to refresh it.+sub reset_pg_hba+{+ my $node = shift;+ my $hostname = shift;++ unlink($node->data_dir . '/pg_hba.conf');+ # just for testing purposes, use a continuation line+ $node->append_conf('pg_hba.conf', "host all all $hostname scram-sha-256");+ $node->reload;+ return;+}+
# This is the hostname used to connect to the server.
my $SERVERHOSTADDR = '127.0.0.1';
@@ -136,4 +151,25 @@ $node->connect_ok(
qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
]);
+# Testing with regular expression for hostname+SKIP:+{+ # Being able to do a reverse lookup of a hostname on Windows for localhost+ # is not guaranteed on all environments by default.+ # So, skip the regular expression test for hostname on Windows.+ skip "Regular expression for hostname not tested on Windows", 2+ if ($windows_os);++ # Test regular expression on hostname, this one matches any host.+ reset_pg_hba($node, '/^.*$');+ $node->connect_ok("$common_connstr user=ssltestuser",+ "Basic SCRAM authentication with SSL matching regexp on hostname");+ # Test regular expression on hostname, this one does not match.+ reset_pg_hba($node, '/^$');+ $node->connect_fails(+ "$common_connstr user=ssltestuser",+ "Basic SCRAM authentication with SSL non matching regexp on hostname",+ log_like => [ qr/no pg_hba.conf entry for host/ ]);+}+
done_testing();
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: bertranddrouvot.pg@gmail.com, michael@paquier.xyz, tgl@sss.pgh.pa.us, jchampion@timescale.com, pgsql-hackers@lists.postgresql.org
Subject: Re: Patch proposal: make use of regular expressions for the username in pg_hba.conf
In-Reply-To: <e346f82f-4a6b-22f7-d6c1-cef16d472de9@gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox