diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c6f1b70fd3..406628ef35 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -235,8 +235,9 @@ hostnogssenc database userPostgreSQL 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 /
+ can be supplied by separating them with commas.
+ A separate file containing database names can be specified by
preceding the file name with @.
@@ -249,7 +250,8 @@ hostnogssenc database userall specifies that it
matches all users. Otherwise, this is either the name of a specific
- database user, or a group name preceded by +.
+ database user, a regular expression preceded by /
+ or a group name preceded by +.
(Recall that there is no real distinction between users and groups
in PostgreSQL; a + mark really means
match any of the roles that are directly or indirectly members
@@ -258,7 +260,8 @@ hostnogssenc database user/
+ can be supplied by separating them with commas.
A separate file containing user names can be specified by preceding the
file name with @.
@@ -270,8 +273,9 @@ hostnogssenc database user
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 / representing host names, an IP address range,
+ or one of the special key words mentioned below.
@@ -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.c
index 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.c
index 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.h
index 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.pl
index 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.pl
index 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();