Hi,

On 9/12/22 9:55 AM, Drouvot, Bertrand wrote:
Hi,

On 9/10/22 1:21 AM, Jacob Champion wrote:
On 8/19/22 01:12, Drouvot, Bertrand wrote:
+           wstr = palloc((strlen(tok->string + 1) + 1) * sizeof(pg_wchar));
+           wlen = pg_mb2wchar_with_len(tok->string + 1,
+                                       wstr, strlen(tok->string + 1));
The (tok->string + 1) construction comes up often enough that I think it
should be put in a `regex` variable or similar. That would help my eyes
with the (strlen(tok->string + 1) + 1) construction, especially.

I noticed that for pg_ident, we precompile the regexes per-line and
reuse those in child processes. Whereas here we're compiling, using, and
then discarding the regex for each check. I think the example set by the
pg_ident code is probably the one to follow, unless you have a good
reason not to.

Thanks for the feedback.

Yeah fully agree. I'll provide a new version that follow the same logic as the pg_ident code.

+# Testing with regular expression for username
+reset_pg_hba($node, '/^.*md.*$', 'password');
+test_role($node, 'md5_role', 'password from pgpass and regular expression for 
username', 0);
+
IMO the coverage for this patch needs to be filled out. Negative test
cases are more important than positive ones for security-related code.

Agree, will do.

Other than that, and Tom's note on potentially expanding this to other
areas,

I'll add regexp usage for the database column and also the for the address one when non CIDR is provided (so host name(s)) (I think it also makes sense specially as we don't allow multiple values for this column).



Please find attached v2 addressing the comments mentioned above.

v2 also provides regular expression usage for the database and the address columns (when a host name is being used).

Remark:

The CF bot is failing for Windows (all other tests are green) and only for the new tap test related to the regular expression on the host name (the ones on database and role are fine).

The issue is not related to the patch. The issue is that the Windows Cirrus test does not like when a host name is provided for a "host" entry in pg_hba.conf (while it works fine when a CIDR is provided).

You can see an example in [1] where the only change is to replace the CIDR by "localhost" in 002_scram.pl. As you can see the Cirrus tests are failing on Windows only (its log file is here [2]).

I'll look at this "Windows" related issue but would appreciate any guidance/help if someone has experience in this area on windows.




[1]: https://github.com/bdrouvot/postgres/branches on branch “host_non_cidr”

[2]: https://api.cirrus-ci.com/v1/artifact/task/6507279833890816/log/src/test/ssl/tmp_check/log/002_scram_primary.log

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c6f1b70fd3..28343445be 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>
@@ -785,16 +789,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 finishing 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.c
index 4637426d62..200de8d7d8 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -117,6 +117,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,13 +577,15 @@ 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)
+check_role(const char *role, Oid roleid, List *tokens, List *tokens_re)
 {
        ListCell   *cell;
        AuthToken  *tok;
+       int                     re_num = 0;
 
        foreach(cell, tokens)
        {
@@ -590,6 +595,23 @@ check_role(const char *role, Oid roleid, List *tokens)
                        if (is_member(roleid, tok->string + 1))
                                return true;
                }
+               else if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression.
+                        */
+                       ListCell   *cell_re;
+                       regex_t    *re;
+
+                       cell_re = list_nth_cell(tokens_re, re_num);
+                       re = lfirst(cell_re);
+
+                       if (token_regexec(role, re))
+                               return true;
+
+                       re_num++;
+               }
                else if (token_matches(tok, role) ||
                                 token_is_keyword(tok, "all"))
                        return true;
@@ -601,10 +623,11 @@ check_role(const char *role, Oid roleid, List *tokens)
  * Check to see if db/role combination matches AuthToken list.
  */
 static bool
-check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
+check_db(const char *dbname, const char *role, Oid roleid, List *tokens, List 
*tokens_re)
 {
        ListCell   *cell;
        AuthToken  *tok;
+       int                     re_num = 0;
 
        foreach(cell, tokens)
        {
@@ -633,6 +656,23 @@ check_db(const char *dbname, const char *role, Oid roleid, 
List *tokens)
                }
                else if (token_is_keyword(tok, "replication"))
                        continue;                       /* never match this if 
not walsender */
+               else if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression.
+                        */
+                       ListCell   *cell_re;
+                       regex_t    *re;
+
+                       cell_re = list_nth_cell(tokens_re, re_num);
+                       re = lfirst(cell_re);
+
+                       if (token_regexec(dbname, re))
+                               return true;
+
+                       re_num++;
+               }
                else if (token_matches(tok, dbname))
                        return true;
        }
@@ -681,7 +721,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 char *hostname, regex_t re)
 {
        struct addrinfo *gai_result,
                           *gai;
@@ -712,8 +752,17 @@ check_hostname(hbaPort *port, const char *hostname)
                port->remote_hostname = pstrdup(remote_hostname);
        }
 
+       if (hostname[0] == '/')
+       {
+               /*
+                * When hostname starts with a slash, treat it as a regular
+                * expression.
+                */
+               if (!token_regexec(port->remote_hostname, &re))
+                       return false;
+       }
        /* Now see if remote host name matches this pg_hba line */
-       if (!hostname_match(hostname, port->remote_hostname))
+       else if (!hostname_match(hostname, port->remote_hostname))
                return false;
 
        /* If we already verified the forward lookup, we're done */
@@ -939,13 +988,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;
@@ -1049,9 +1098,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                return NULL;
        }
        parsedline->databases = NIL;
+       parsedline->databases_re = NIL;
        tokens = lfirst(field);
        foreach(tokencell, tokens)
        {
+               AuthToken  *tok = lfirst(tokencell);
+
+               if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression. Pre-compile it.
+                        */
+                       regex_t    *re;
+
+                       re = (regex_t *) palloc(sizeof(regex_t));
+                       if (token_regcomp(re, tok->string + 1, HbaFileName, 
line_num,
+                                                         err_msg, elevel))
+                               parsedline->databases_re = 
lappend(parsedline->databases_re, re);
+                       else
+                               return NULL;
+               }
                parsedline->databases = lappend(parsedline->databases,
                                                                                
copy_auth_token(lfirst(tokencell)));
        }
@@ -1069,9 +1136,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                return NULL;
        }
        parsedline->roles = NIL;
+       parsedline->roles_re = NIL;
        tokens = lfirst(field);
        foreach(tokencell, tokens)
        {
+               AuthToken  *tok = lfirst(tokencell);
+
+               if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression. Pre-compile it.
+                        */
+                       regex_t    *re;
+
+                       re = (regex_t *) palloc(sizeof(regex_t));
+                       if (token_regcomp(re, tok->string + 1, HbaFileName, 
line_num,
+                                                         err_msg, elevel))
+                               parsedline->roles_re = 
lappend(parsedline->roles_re, re);
+                       else
+                               return NULL;
+               }
                parsedline->roles = lappend(parsedline->roles,
                                                                        
copy_auth_token(lfirst(tokencell)));
        }
@@ -1120,6 +1205,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 +1214,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;
@@ -1168,7 +1258,7 @@ 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)
                                {
@@ -1199,7 +1289,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                                parsedline->masklen = parsedline->addrlen;
                                pfree(str);
                        }
-                       else if (!parsedline->hostname)
+                       else if (!parsedline->hostname && !is_regexp)
                        {
                                /* Read the mask field. */
                                pfree(str);
@@ -1261,6 +1351,18 @@ 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.
+                                */
+                               if (!token_regcomp(&parsedline->hostname_re,
+                                                                  
token->string + 1, HbaFileName,
+                                                                  line_num, 
err_msg, elevel))
+                                       return NULL;
+                               parsedline->hostname = str;
+                       }
                }
        }                                                       /* != ctLocal */
 
@@ -2135,7 +2237,8 @@ check_hba(hbaPort *port)
                                        if (hba->hostname)
                                        {
                                                if (!check_hostname(port,
-                                                                               
        hba->hostname))
+                                                                               
        hba->hostname, hba->hostname_re))
+
                                                        continue;
                                        }
                                        else
@@ -2162,10 +2265,10 @@ check_hba(hbaPort *port)
 
                /* Check database and role */
                if (!check_db(port->database_name, port->user_name, roleid,
-                                         hba->databases))
+                                         hba->databases, hba->databases_re))
                        continue;
 
-               if (!check_role(port->user_name, roleid, hba->roles))
+               if (!check_role(port->user_name, roleid, hba->roles, 
hba->roles_re))
                        continue;
 
                /* Found a record that matched! */
@@ -2342,34 +2445,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 +2784,58 @@ hba_authname(UserAuth auth_method)
 
        return UserAuthName[auth_method];
 }
+
+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;
+}
+
+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/include/libpq/hba.h b/src/include/libpq/hba.h
index 90036f7bcd..c24f9166cb 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -120,6 +120,9 @@ typedef struct HbaLine
        char       *radiusidentifiers_s;
        List       *radiusports;
        char       *radiusports_s;
+       List       *roles_re;
+       List       *databases_re;
+       regex_t    hostname_re;
 } HbaLine;
 
 typedef struct IdentLine
diff --git a/src/test/authentication/t/001_password.pl 
b/src/test/authentication/t/001_password.pl
index 3e3079c824..049ad1cc89 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -23,29 +23,32 @@ if (!$use_unix_sockets)
 # and then execute a reload to refresh it.
 sub reset_pg_hba
 {
-       my $node       = shift;
-       my $hba_method = shift;
+       my $node                = shift;
+       my $host                = shift;
+       my $database    = shift;
+       my $role                = shift;
+       my $method              = shift;
 
        unlink($node->data_dir . '/pg_hba.conf');
        # just for testing purposes, use a continuation line
-       $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+       $node->append_conf('pg_hba.conf', "$host $database $role\\\n $method");
        $node->reload;
        return;
 }
 
 # Test access for a single role, useful to wrap all tests into one.  Extra
 # named parameters are passed to connect_ok/fails as-is.
-sub test_role
+sub test_conn
 {
        local $Test::Builder::Level = $Test::Builder::Level + 1;
 
-       my ($node, $role, $method, $expected_res, %params) = @_;
+       my ($node, $conn, $method, $expected_res, %params) = @_;
        my $status_string = 'failed';
        $status_string = 'success' if ($expected_res eq 0);
 
-       my $connstr = "user=$role";
+       my $connstr = "$conn";
        my $testname =
-         "authentication $status_string for method $method, role $role";
+         "authentication $status_string for method $method, conn $conn";
 
        if ($expected_res eq 0)
        {
@@ -61,7 +64,11 @@ sub test_role
 # Initialize primary node
 my $node = PostgreSQL::Test::Cluster->new('primary');
 $node->init;
-$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->append_conf(
+    'postgresql.conf', qq{
+listen_addresses = '127.0.0.1'
+log_connections = on
+});
 $node->start;
 
 # Create 3 roles with different password methods for each one. The same
@@ -74,61 +81,66 @@ $node->safe_psql('postgres',
 );
 $ENV{"PGPASSWORD"} = 'pass';
 
+# Create a database to test regular expression
+$node->safe_psql('postgres',
+       "CREATE database testdb;"
+);
+
 # For "trust" method, all users should be able to connect. These users are not
 # considered to be authenticated.
-reset_pg_hba($node, 'trust');
-test_role($node, 'scram_role', 'trust', 0,
+reset_pg_hba($node, 'local','all', 'all', 'trust');
+test_conn($node, 'user=scram_role', 'trust', 0,
        log_unlike => [qr/connection authenticated:/]);
-test_role($node, 'md5_role', 'trust', 0,
+test_conn($node, 'user=md5_role', 'trust', 0,
        log_unlike => [qr/connection authenticated:/]);
 
 # For plain "password" method, all users should also be able to connect.
-reset_pg_hba($node, 'password');
-test_role($node, 'scram_role', 'password', 0,
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
+test_conn($node, 'user=scram_role', 'password', 0,
        log_like =>
          [qr/connection authenticated: identity="scram_role" 
method=password/]);
-test_role($node, 'md5_role', 'password', 0,
+test_conn($node, 'user=md5_role', 'password', 0,
        log_like =>
          [qr/connection authenticated: identity="md5_role" method=password/]);
 
 # For "scram-sha-256" method, user "scram_role" should be able to connect.
-reset_pg_hba($node, 'scram-sha-256');
-test_role(
+reset_pg_hba($node, 'local', 'all', 'all', 'scram-sha-256');
+test_conn(
        $node,
-       'scram_role',
+       'user=scram_role',
        'scram-sha-256',
        0,
        log_like => [
                qr/connection authenticated: identity="scram_role" 
method=scram-sha-256/
        ]);
-test_role($node, 'md5_role', 'scram-sha-256', 2,
+test_conn($node, 'user=md5_role', 'scram-sha-256', 2,
        log_unlike => [qr/connection authenticated:/]);
 
 # Test that bad passwords are rejected.
 $ENV{"PGPASSWORD"} = 'badpass';
-test_role($node, 'scram_role', 'scram-sha-256', 2,
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2,
        log_unlike => [qr/connection authenticated:/]);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "md5" method, all users should be able to connect (SCRAM
 # authentication will be performed for the user with a SCRAM secret.)
-reset_pg_hba($node, 'md5');
-test_role($node, 'scram_role', 'md5', 0,
+reset_pg_hba($node, 'local', 'all', 'all', 'md5');
+test_conn($node, 'user=scram_role', 'md5', 0,
        log_like =>
          [qr/connection authenticated: identity="scram_role" method=md5/]);
-test_role($node, 'md5_role', 'md5', 0,
+test_conn($node, 'user=md5_role', 'md5', 0,
        log_like =>
          [qr/connection authenticated: identity="md5_role" method=md5/]);
 
 # Tests for channel binding without SSL.
 # Using the password authentication method; channel binding can't work
-reset_pg_hba($node, 'password');
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
 $ENV{"PGCHANNELBINDING"} = 'require';
-test_role($node, 'scram_role', 'scram-sha-256', 2);
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2);
 # SSL not in use; channel binding still can't work
-reset_pg_hba($node, 'scram-sha-256');
+reset_pg_hba($node, 'local', 'all', 'all', 'scram-sha-256');
 $ENV{"PGCHANNELBINDING"} = 'require';
-test_role($node, 'scram_role', 'scram-sha-256', 2);
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2);
 
 # Test .pgpass processing; but use a temp file, don't overwrite the real one!
 my $pgpassfile = "${PostgreSQL::Test::Utils::tmp_check}/pgpass";
@@ -145,15 +157,38 @@ append_to_file(
 !);
 chmod 0600, $pgpassfile or die;
 
-reset_pg_hba($node, 'password');
-test_role($node, 'scram_role', 'password from pgpass', 0);
-test_role($node, 'md5_role',   'password from pgpass', 2);
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
+test_conn($node, 'user=scram_role', 'password from pgpass', 0);
+test_conn($node, 'user=md5_role',   'password from pgpass', 2);
 
 append_to_file(
        $pgpassfile, qq!
 *:*:*:md5_role:p\\ass
 !);
 
-test_role($node, 'md5_role', 'password from pgpass', 0);
+test_conn($node, 'user=md5_role', 'password from pgpass', 0);
+
+# Testing with regular expression for username
+reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^.*md.*$', 
'password');
+test_conn($node, 'user=md5_role', 'password, matching regexp for username', 0);
+
+reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^.*m_d.*$', 
'password');
+test_conn($node, 'user=md5_role', 'password, non matching regexp for 
username', 2,
+               log_unlike => [qr/connection authenticated:/]);
+
+# Testing with regular expression for dbname
+reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^t.*b$', 'all', 
'password');
+test_conn($node, 'user=md5_role dbname=testdb', 'password, matching regexp for 
dbname', 0);
+
+reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^t.*ba$', 'all', 
'password');
+test_conn($node, 'user=md5_role dbname=testdb', 'password, non matching regexp 
for dbname', 2,
+               log_unlike => [qr/connection authenticated:/]);
+
+# Testing with regular expression for hostname
+reset_pg_hba($node, 'host', 'all', 'all  /^.*$', 'password');
+test_conn($node, 'user=md5_role host=localhost', 'password, matching regexp 
for hostname', 0);
+
+reset_pg_hba($node, 'host', 'all', 'all  /^$', 'password');
+test_conn($node, 'user=md5_role host=localhost', 'password, non matching 
regexp for hostname', 2);
 
 done_testing();

Reply via email to