<listitem>
<para>
Causes each attempted connection to the server to be logged,
- as well as successful completion of client authentication.
+ as well as successful completion of both client authentication (if
+ necessary) and authorization.
Only superusers can change this parameter at session start,
and it cannot be changed at all within a session.
The default is <literal>off</literal>.
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
+#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
+static void set_authn_id(Port *port, const char *id);
/*----------------------------------------------------------------
}
+/*
+ * Sets the authenticated identity for the current user. The provided string
+ * will be copied into the TopMemoryContext. The ID will be logged if
+ * log_connections is enabled.
+ *
+ * Auth methods should call this routine exactly once, as soon as the user is
+ * successfully authenticated, even if they have reasons to know that
+ * authorization will fail later.
+ *
+ * The provided string will be copied into TopMemoryContext, to match the
+ * lifetime of the Port, so it is safe to pass a string that is managed by an
+ * external library.
+ */
+static void
+set_authn_id(Port *port, const char *id)
+{
+ Assert(id);
+
+ if (port->authn_id)
+ {
+ /*
+ * An existing authn_id should never be overwritten; that means two
+ * authentication providers are fighting (or one is fighting itself).
+ * Don't leak any authn details to the client, but don't let the
+ * connection continue, either.
+ */
+ ereport(FATAL,
+ (errmsg("connection was re-authenticated"),
+ errdetail_log("previous ID: \"%s\"; new ID: \"%s\"",
+ port->authn_id, id)));
+ }
+
+ port->authn_id = MemoryContextStrdup(TopMemoryContext, id);
+
+ if (Log_connections)
+ {
+ ereport(LOG,
+ errmsg("connection authenticated: identity=\"%s\" method=%s "
+ "(%s:%d)",
+ port->authn_id, hba_authname(port), HbaFileName,
+ port->hba->linenumber));
+ }
+}
+
+
/*
* Client authentication starts here. If there is an error, this
* function does not return and the backend process is terminated.
pfree(shadow_pass);
pfree(passwd);
+ if (result == STATUS_OK)
+ set_authn_id(port, port->user_name);
+
return result;
}
Assert(auth_result != STATUS_OK);
return STATUS_ERROR;
}
+
+ if (auth_result == STATUS_OK)
+ set_authn_id(port, port->user_name);
+
return auth_result;
}
/*
* Copy the original name of the authenticated principal into our backend
* memory for display later.
+ *
+ * This is also our authenticated identity. Set it now, rather than
+ * waiting for the usermap check below, because authentication has already
+ * succeeded and we want the log file to reflect that.
*/
port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
+ set_authn_id(port, gbuf.value);
/*
* Split the username at the realm separator
DWORD domainnamesize = sizeof(domainname);
SID_NAME_USE accountnameuse;
HMODULE secur32;
+ char *authn_id;
QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
return status;
}
+ /*
+ * We have all of the information necessary to construct the authenticated
+ * identity. Set it now, rather than waiting for check_usermap below,
+ * because authentication has already succeeded and we want the log file
+ * to reflect that.
+ */
+ if (port->hba->compat_realm)
+ {
+ /* SAM-compatible format. */
+ authn_id = psprintf("%s\\%s", domainname, accountname);
+ }
+ else
+ {
+ /* Kerberos principal format. */
+ authn_id = psprintf("%s@%s", accountname, domainname);
+ }
+
+ set_authn_id(port, authn_id);
+ pfree(authn_id);
+
/*
* Compare realm/domain if requested. In SSPI, always compare case
* insensitive.
pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
if (ident_return)
- /* Success! Check the usermap */
+ {
+ /*
+ * Success! Store the identity, then check the usermap. Note that
+ * setting the authenticated identity is done before checking the
+ * usermap, because at this point authentication has succeeded.
+ */
+ set_authn_id(port, ident_user);
return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
+ }
return STATUS_ERROR;
}
gid_t gid;
#ifndef WIN32
struct passwd *pw;
- char *peer_user;
int ret;
#endif
return STATUS_ERROR;
}
- /* Make a copy of static getpw*() result area. */
- peer_user = pstrdup(pw->pw_name);
-
- ret = check_usermap(port->hba->usermap, port->user_name, peer_user, false);
+ /*
+ * Make a copy of static getpw*() result area; this is our authenticated
+ * identity. Set it before calling check_usermap, because authentication
+ * has already succeeded and we want the log file to reflect that.
+ */
+ set_authn_id(port, pw->pw_name);
- pfree(peer_user);
+ ret = check_usermap(port->hba->usermap, port->user_name, port->authn_id, false);
return ret;
#else
pam_passwd = NULL; /* Unset pam_passwd */
+ if (retval == PAM_SUCCESS)
+ set_authn_id(port, user);
+
return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
}
#endif /* USE_PAM */
if (!retval)
return STATUS_ERROR;
+ set_authn_id(port, user);
return STATUS_OK;
}
#endif /* USE_BSD_AUTH */
return STATUS_ERROR;
}
+ /* Save the original bind DN as the authenticated identity. */
+ set_authn_id(port, fulluser);
+
ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
+ if (port->hba->auth_method == uaCert)
+ {
+ /*
+ * For cert auth, the client's Subject DN is always our authenticated
+ * identity, even if we're only using its CN for authorization. Set
+ * it now, rather than waiting for check_usermap() below, because
+ * authentication has already succeeded and we want the log file to
+ * reflect that.
+ */
+ if (!port->peer_dn)
+ {
+ /*
+ * This should not happen as both peer_dn and peer_cn should be
+ * set in this context.
+ */
+ ereport(LOG,
+ (errmsg("certificate authentication failed for user \"%s\": unable to retrieve subject DN",
+ port->user_name)));
+ return STATUS_ERROR;
+ }
+
+ set_authn_id(port, port->peer_dn);
+ }
+
/* Just pass the certificate cn/dn to the usermap check */
status_check_usermap = check_usermap(port->hba->usermap, port->user_name, peer_username, false);
if (status_check_usermap != STATUS_OK)
*/
if (ret == STATUS_OK)
{
+ set_authn_id(port, port->user_name);
+
pfree(passwd);
return STATUS_OK;
}
{
check_hba(port);
}
+
+
+/*
+ * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
+ *
+ * The return value is statically allocated (see the UserAuthName array) and
+ * should not be freed.
+ */
+const char *
+hba_authname(hbaPort *port)
+{
+ UserAuth auth_method;
+
+ Assert(port->hba);
+ auth_method = port->hba->auth_method;
+
+ if (auth_method < 0 || USER_AUTH_LAST < auth_method)
+ {
+ /* Should never happen. */
+ elog(FATAL, "port has out-of-bounds UserAuth: %d", auth_method);
+ }
+
+ return UserAuthName[auth_method];
+}
extern bool load_hba(void);
extern bool load_ident(void);
+extern const char *hba_authname(hbaPort *port);
extern void hba_getauthmethod(hbaPort *port);
extern int check_usermap(const char *usermap_name,
const char *pg_role, const char *auth_user,
*/
HbaLine *hba;
+ /*
+ * Authenticated identity. The meaning of this identifier is dependent on
+ * hba->auth_method; it is the identity (if any) that the user presented
+ * during the authentication cycle, before they were assigned a database
+ * role. (It is effectively the "SYSTEM-USERNAME" of a pg_ident usermap
+ * -- though the exact string in use may be different, depending on pg_hba
+ * options.)
+ *
+ * authn_id is NULL if the user has not actually been authenticated, for
+ * example if the "trust" auth method is in use.
+ */
+ const char *authn_id;
+
/*
* TCP keepalive and user timeout settings.
*
}
else
{
- plan tests => 13;
+ plan tests => 23;
}
return;
}
-# Test access for a single role, useful to wrap all tests into one.
+# 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
{
- my $node = shift;
- my $role = shift;
- my $method = shift;
- my $expected_res = shift;
+ my ($node, $role, $method, $expected_res, %params) = @_;
my $status_string = 'failed';
-
$status_string = 'success' if ($expected_res eq 0);
my $connstr = "user=$role";
if ($expected_res eq 0)
{
- $node->connect_ok($connstr, $testname);
+ $node->connect_ok($connstr, $testname, %params);
}
else
{
# No checks of the error message, only the status code.
- $node->connect_fails($connstr, $testname);
+ $node->connect_fails($connstr, $testname, %params);
}
}
# Initialize primary node
my $node = get_new_node('primary');
$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
$node->start;
# Create 3 roles with different password methods for each one. The same
);
$ENV{"PGPASSWORD"} = 'pass';
-# For "trust" method, all users should be able to connect.
+# 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);
-test_role($node, 'md5_role', 'trust', 0);
+test_role($node, 'scram_role', 'trust', 0,
+ log_unlike => [qr/connection authenticated:/]);
+test_role($node, '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);
-test_role($node, 'md5_role', 'password', 0);
+test_role($node, 'scram_role', 'password', 0,
+ log_like =>
+ [qr/connection authenticated: identity="scram_role" method=password/]);
+test_role($node, '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($node, 'scram_role', 'scram-sha-256', 0);
-test_role($node, 'md5_role', 'scram-sha-256', 2);
+test_role(
+ $node,
+ '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,
+ log_unlike => [qr/connection authenticated:/]);
+
+# Test that bad passwords are rejected.
+$ENV{"PGPASSWORD"} = 'badpass';
+test_role($node, '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);
-test_role($node, 'md5_role', 'md5', 0);
+test_role($node, 'scram_role', 'md5', 0,
+ log_like =>
+ [qr/connection authenticated: identity="scram_role" method=md5/]);
+test_role($node, '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
if ($ENV{with_gssapi} eq 'yes')
{
- plan tests => 32;
+ plan tests => 44;
}
else
{
sub test_access
{
my ($node, $role, $query, $expected_res, $gssencmode, $test_name,
- $expect_log_msg)
+ @expect_log_msgs)
= @_;
# need to connect over TCP/IP for Kerberos
my $connstr = $node->connstr('postgres')
. " user=$role host=$host hostaddr=$hostaddr $gssencmode";
+ my %params = (
+ sql => $query,
+ );
+
+ if (@expect_log_msgs)
+ {
+ # Match every message literally.
+ my @regexes = map { qr/\Q$_\E/ } @expect_log_msgs;
+
+ $params{log_like} = \@regexes;
+ }
+
if ($expected_res eq 0)
{
# The result is assumed to match "true", or "t", here.
- $node->connect_ok(
- $connstr, $test_name,
- sql => $query,
- expected_stdout => qr/^t$/);
+ $params{expected_stdout} = qr/^t$/;
+
+ $node->connect_ok($connstr, $test_name, %params);
}
else
{
- $node->connect_fails($connstr, $test_name);
+ $node->connect_fails($connstr, $test_name, %params);
}
-
- # Verify specified log message is logged in the log file.
- if ($expect_log_msg ne '')
- {
- my $first_logfile = slurp_file($node->logfile);
-
- like($first_logfile, qr/\Q$expect_log_msg\E/,
- 'found expected log file content');
- }
-
- # Clean up any existing contents in the node's log file so as
- # future tests don't step on each other's generated contents.
- truncate $node->logfile, 0;
- return;
}
# As above, but test for an arbitrary query result.
qq{host all all $hostaddr/32 gss map=mymap});
$node->restart;
-test_access($node, 'test1', 'SELECT true', 2, '', 'fails without ticket', '');
+test_access($node, 'test1', 'SELECT true', 2, '', 'fails without ticket');
run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?);
-test_access($node, 'test1', 'SELECT true', 2, '', 'fails without mapping', '');
+test_access(
+ $node,
+ 'test1',
+ 'SELECT true',
+ 2,
+ '',
+ 'fails without mapping',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
+ "no match in usermap \"mymap\" for user \"test1\"");
$node->append_conf('pg_ident.conf', qq{mymap /^(.*)\@$realm\$ \\1});
$node->restart;
0,
'',
'succeeds with mapping with default gssencmode and host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
0,
'gssencmode=prefer',
'succeeds with GSS-encrypted access preferred with host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
test_access(
0,
'gssencmode=require',
'succeeds with GSS-encrypted access required with host hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
0,
'gssencmode=prefer',
'succeeds with GSS-encrypted access preferred and hostgssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
test_access(
0,
'gssencmode=require',
'succeeds with GSS-encrypted access required and hostgssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=disable',
- 'fails with GSS encryption disabled and hostgssenc hba', '');
+ 'fails with GSS encryption disabled and hostgssenc hba');
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
0,
'gssencmode=prefer',
'succeeds with GSS-encrypted access preferred and hostnogssenc hba, but no encryption',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)"
);
test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=require',
- 'fails with GSS-encrypted access required and hostnogssenc hba', '');
+ 'fails with GSS-encrypted access required and hostnogssenc hba');
test_access(
$node,
'test1',
0,
'gssencmode=disable',
'succeeds with GSS encryption disabled and hostnogssenc hba',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=no, principal=test1\@$realm)"
);
0,
'',
'succeeds with include_realm=0 and defaults',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss",
"connection authorized: user=$username database=$dbname application_name=$application GSS (authenticated=yes, encrypted=yes, principal=test1\@$realm)"
);
+
+# Reset pg_hba.conf, and cause a usermap failure with an authentication
+# that has passed.
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{host all all $hostaddr/32 gss include_realm=0 krb_realm=EXAMPLE.ORG});
+$node->restart;
+
+test_access(
+ $node,
+ 'test1',
+ 'SELECT true',
+ 2,
+ '',
+ 'fails with wrong krb_realm, but still authenticates',
+ "connection authenticated: identity=\"test1\@$realm\" method=gss");
if ($ENV{with_ldap} eq 'yes')
{
- plan tests => 22;
+ plan tests => 28;
}
else
{
my $node = get_new_node('node');
$node->init;
+$node->append_conf('postgresql.conf', "log_connections = on\n");
$node->start;
$node->safe_psql('postgres', 'CREATE USER test0;');
sub test_access
{
- my ($node, $role, $expected_res, $test_name) = @_;
+ my ($node, $role, $expected_res, $test_name, %params) = @_;
my $connstr = "user=$role";
if ($expected_res eq 0)
{
- $node->connect_ok($connstr, $test_name);
+ $node->connect_ok($connstr, $test_name, %params);
}
else
{
# No checks of the error message, only the status code.
- $node->connect_fails($connstr, $test_name);
+ $node->connect_fails($connstr, $test_name, %params);
}
}
$node->restart;
$ENV{"PGPASSWORD"} = 'wrong';
-test_access($node, 'test0', 2,
- 'simple bind authentication fails if user not found in LDAP');
-test_access($node, 'test1', 2,
- 'simple bind authentication fails with wrong password');
+test_access(
+ $node, 'test0', 2,
+ 'simple bind authentication fails if user not found in LDAP',
+ log_unlike => [qr/connection authenticated:/]);
+test_access(
+ $node, 'test1', 2,
+ 'simple bind authentication fails with wrong password',
+ log_unlike => [qr/connection authenticated:/]);
+
$ENV{"PGPASSWORD"} = 'secret1';
-test_access($node, 'test1', 0, 'simple bind authentication succeeds');
+test_access(
+ $node, 'test1', 0,
+ 'simple bind authentication succeeds',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
note "search+bind";
test_access($node, 'test1', 2,
'search+bind authentication fails with wrong password');
$ENV{"PGPASSWORD"} = 'secret1';
-test_access($node, 'test1', 0, 'search+bind authentication succeeds');
+test_access(
+ $node, 'test1', 0,
+ 'search+bind authentication succeeds',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
note "multiple servers";
$node->restart;
$ENV{"PGPASSWORD"} = 'secret1';
-test_access($node, 'test1', 0, 'search filter finds by uid');
+test_access(
+ $node, 'test1', 0,
+ 'search filter finds by uid',
+ log_like => [
+ qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/
+ ],);
$ENV{"PGPASSWORD"} = 'secret2';
-test_access($node, 'test2@example.net', 0, 'search filter finds by mail');
+test_access(
+ $node,
+ 'test2@example.net',
+ 0,
+ 'search filter finds by mail',
+ log_like => [
+ qr/connection authenticated: identity="uid=test2,dc=example,dc=net" method=ldap/
+ ],);
note "search filters in LDAP URLs";
If this regular expression is set, matches it with the output generated.
+=item log_like => [ qr/required message/ ]
+
+If given, it must be an array reference containing a list of regular
+expressions that must match against the server log, using
+C<Test::More::like()>.
+
+=item log_unlike => [ qr/prohibited message/ ]
+
+If given, it must be an array reference containing a list of regular
+expressions that must NOT match against the server log. They will be
+passed to C<Test::More::unlike()>.
+
=back
=cut
$sql = "SELECT \$\$connected with $connstr\$\$";
}
+ my (@log_like, @log_unlike);
+ if (defined($params{log_like}))
+ {
+ @log_like = @{ $params{log_like} };
+ }
+ if (defined($params{log_unlike}))
+ {
+ @log_unlike = @{ $params{log_unlike} };
+ }
+
+ if (@log_like or @log_unlike)
+ {
+ # Don't let previous log entries match for this connection.
+ truncate $self->logfile, 0;
+ }
+
# Never prompt for a password, any callers of this routine should
# have set up things properly, and this should not block.
my ($ret, $stdout, $stderr) = $self->psql(
{
like($stdout, $params{expected_stdout}, "$test_name: matches");
}
+ if (@log_like or @log_unlike)
+ {
+ my $log_contents = TestLib::slurp_file($self->logfile);
+
+ while (my $regex = shift @log_like)
+ {
+ like($log_contents, $regex, "$test_name: log matches");
+ }
+ while (my $regex = shift @log_unlike)
+ {
+ unlike($log_contents, $regex, "$test_name: log does not match");
+ }
+ }
}
=pod
If this regular expression is set, matches it with the output generated.
+=item log_like => [ qr/required message/ ]
+
+=item log_unlike => [ qr/prohibited message/ ]
+
+See C<connect_ok(...)>, above.
+
=back
=cut
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($self, $connstr, $test_name, %params) = @_;
+ my (@log_like, @log_unlike);
+ if (defined($params{log_like}))
+ {
+ @log_like = @{ $params{log_like} };
+ }
+ if (defined($params{log_unlike}))
+ {
+ @log_unlike = @{ $params{log_unlike} };
+ }
+
+ if (@log_like or @log_unlike)
+ {
+ # Don't let previous log entries match for this connection.
+ truncate $self->logfile, 0;
+ }
+
# Never prompt for a password, any callers of this routine should
# have set up things properly, and this should not block.
my ($ret, $stdout, $stderr) = $self->psql(
{
like($stderr, $params{expected_stderr}, "$test_name: matches");
}
+
+ if (@log_like or @log_unlike)
+ {
+ my $log_contents = TestLib::slurp_file($self->logfile);
+
+ while (my $regex = shift @log_like)
+ {
+ like($log_contents, $regex, "$test_name: log matches");
+ }
+ while (my $regex = shift @log_unlike)
+ {
+ unlike($log_contents, $regex, "$test_name: log does not match");
+ }
+ }
}
=pod
}
else
{
- plan tests => 103;
+ plan tests => 110;
}
#### Some configuration
$node->connect_ok(
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with DN mapping");
+ "certificate authorization succeeds with DN mapping",
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
# same thing but with a regex
$dn_connstr = "$common_connstr dbname=certdb_dn_re";
$node->connect_ok(
"$dn_connstr user=ssltestuser sslcert=ssl/client-dn.crt sslkey=ssl/client-dn_tmp.key",
- "certificate authorization succeeds with CN mapping");
+ "certificate authorization succeeds with CN mapping",
+ # the full DN should still be used as the authenticated identity
+ log_like => [
+ qr/connection authenticated: identity="CN=ssltestuser-dn,OU=Testing,OU=Engineering,O=PGDG" method=cert/
+ ],);
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"certificate authorization fails with client cert belonging to another user",
expected_stderr =>
- qr/certificate authentication failed for user "anotheruser"/);
+ qr/certificate authentication failed for user "anotheruser"/,
+ # certificate authentication should be logged even on failure
+ log_like =>
+ [qr/connection authenticated: identity="CN=ssltestuser" method=cert/],);
# revoked client cert
$node->connect_fails(
"$common_connstr user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
"certificate authorization fails with revoked client cert",
- expected_stderr => qr/SSL error: sslv3 alert certificate revoked/);
+ expected_stderr => qr/SSL error: sslv3 alert certificate revoked/,
+ # revoked certificates should not authenticate the user
+ log_unlike => [qr/connection authenticated:/],);
# Check that connecting with auth-option verify-full in pg_hba:
# works, iff username matches Common Name
$node->connect_ok(
"$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- "auth_option clientcert=verify-full succeeds with matching username and Common Name"
-);
+ "auth_option clientcert=verify-full succeeds with matching username and Common Name",
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
$node->connect_fails(
"$common_connstr user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
"auth_option clientcert=verify-full fails with mismatching username and Common Name",
expected_stderr =>
- qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,);
+ qr/FATAL: .* "trust" authentication failed for user "anotheruser"/,
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
# Check that connecting with auth-optionverify-ca in pg_hba :
# works, when username doesn't match Common Name
$node->connect_ok(
"$common_connstr user=yetanotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- "auth_option clientcert=verify-ca succeeds with mismatching username and Common Name"
-);
+ "auth_option clientcert=verify-ca succeeds with mismatching username and Common Name",
+ # verify-full does not provide authentication
+ log_unlike => [qr/connection authenticated:/],);
# intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file
switch_server_cert($node, 'server-cn-only', 'root_ca');
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
-my $number_of_tests = $supports_tls_server_end_point ? 9 : 10;
+my $number_of_tests = $supports_tls_server_end_point ? 11 : 12;
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
qr/channel binding required, but server authenticated client without channel binding/
);
+# Certificate verification at the connection level should still work fine.
+$node->connect_ok(
+ "sslcert=ssl/client.crt sslkey=$client_tmp_key sslrootcert=invalid hostaddr=$SERVERHOSTADDR dbname=verifydb user=ssltestuser channel_binding=require",
+ "SCRAM with clientcert=verify-full and channel_binding=require",
+ log_like => [
+ qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/
+ ]);
+
# clean up
unlink($client_tmp_key);