mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Add new clientcert hba option verify-full
This allows a login to require both that the cn of the certificate matches (like authentication type cert) *and* that another authentication method (such as password or kerberos) succeeds as well. The old value of clientcert=1 maps to the new clientcert=verify-ca, clientcert=0 maps to the new clientcert=no-verify, and the new option erify-full will add the validation of the CN. Author: Julian Markwort, Marius Timmer Reviewed by: Magnus Hagander, Thomas Munro
This commit is contained in:
@ -363,7 +363,7 @@ ClientAuthentication(Port *port)
|
||||
* current connection, so perform any verifications based on the hba
|
||||
* options field that should be done *before* the authentication here.
|
||||
*/
|
||||
if (port->hba->clientcert)
|
||||
if (port->hba->clientcert != clientCertOff)
|
||||
{
|
||||
/* If we haven't loaded a root certificate store, fail */
|
||||
if (!secure_loaded_verify_locations())
|
||||
@ -581,24 +581,32 @@ ClientAuthentication(Port *port)
|
||||
status = CheckLDAPAuth(port);
|
||||
#else
|
||||
Assert(false);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case uaCert:
|
||||
#ifdef USE_SSL
|
||||
status = CheckCertAuth(port);
|
||||
#else
|
||||
Assert(false);
|
||||
#endif
|
||||
break;
|
||||
case uaRADIUS:
|
||||
status = CheckRADIUSAuth(port);
|
||||
break;
|
||||
case uaCert:
|
||||
/* uaCert will be treated as if clientcert=verify-full (uaTrust) */
|
||||
case uaTrust:
|
||||
status = STATUS_OK;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
|
||||
|| port->hba->auth_method == uaCert)
|
||||
{
|
||||
/*
|
||||
* Make sure we only check the certificate if we use the cert method
|
||||
* or verify-full option.
|
||||
*/
|
||||
#ifdef USE_SSL
|
||||
status = CheckCertAuth(port);
|
||||
#else
|
||||
Assert(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ClientAuthentication_hook)
|
||||
(*ClientAuthentication_hook) (port, status);
|
||||
|
||||
@ -2788,6 +2796,8 @@ errdetail_for_ldap(LDAP *ldap)
|
||||
static int
|
||||
CheckCertAuth(Port *port)
|
||||
{
|
||||
int status_check_usermap = STATUS_ERROR;
|
||||
|
||||
Assert(port->ssl);
|
||||
|
||||
/* Make sure we have received a username in the certificate */
|
||||
@ -2800,8 +2810,23 @@ CheckCertAuth(Port *port)
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
/* Just pass the certificate CN to the usermap check */
|
||||
return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
|
||||
/* Just pass the certificate cn to the usermap check */
|
||||
status_check_usermap = check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
|
||||
if (status_check_usermap != STATUS_OK)
|
||||
{
|
||||
/*
|
||||
* If clientcert=verify-full was specified and the authentication
|
||||
* method is other than uaCert, log the reason for rejecting the
|
||||
* authentication.
|
||||
*/
|
||||
if (port->hba->clientcert == clientCertFull && port->hba->auth_method != uaCert)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": cn mismatch",
|
||||
port->user_name)));
|
||||
}
|
||||
}
|
||||
return status_check_usermap;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1609,7 +1609,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
|
||||
*/
|
||||
if (parsedline->auth_method == uaCert)
|
||||
{
|
||||
parsedline->clientcert = true;
|
||||
parsedline->clientcert = clientCertCA;
|
||||
}
|
||||
|
||||
return parsedline;
|
||||
@ -1675,23 +1675,38 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
|
||||
*err_msg = "clientcert can only be configured for \"hostssl\" rows";
|
||||
return false;
|
||||
}
|
||||
if (strcmp(val, "1") == 0)
|
||||
if (strcmp(val, "1") == 0
|
||||
|| strcmp(val, "verify-ca") == 0)
|
||||
{
|
||||
hbaline->clientcert = true;
|
||||
hbaline->clientcert = clientCertCA;
|
||||
}
|
||||
else
|
||||
else if (strcmp(val, "verify-full") == 0)
|
||||
{
|
||||
hbaline->clientcert = clientCertFull;
|
||||
}
|
||||
else if (strcmp(val, "0") == 0
|
||||
|| strcmp(val, "no-verify") == 0)
|
||||
{
|
||||
if (hbaline->auth_method == uaCert)
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
|
||||
errmsg("clientcert can not be set to \"no-verify\" when using \"cert\" authentication"),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, HbaFileName)));
|
||||
*err_msg = "clientcert can not be set to 0 when using \"cert\" authentication";
|
||||
*err_msg = "clientcert can not be set to \"no-verify\" when using \"cert\" authentication";
|
||||
return false;
|
||||
}
|
||||
hbaline->clientcert = false;
|
||||
hbaline->clientcert = clientCertOff;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("invalid value for clientcert: \"%s\"", val),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, HbaFileName)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strcmp(name, "pamservice") == 0)
|
||||
@ -2252,9 +2267,9 @@ gethba_options(HbaLine *hba)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum(psprintf("map=%s", hba->usermap));
|
||||
|
||||
if (hba->clientcert)
|
||||
if (hba->clientcert != clientCertOff)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum("clientcert=true");
|
||||
CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
|
||||
|
||||
if (hba->pamservice)
|
||||
options[noptions++] =
|
||||
|
@ -58,6 +58,13 @@ typedef enum ConnType
|
||||
ctHostNoSSL
|
||||
} ConnType;
|
||||
|
||||
typedef enum ClientCertMode
|
||||
{
|
||||
clientCertOff,
|
||||
clientCertCA,
|
||||
clientCertFull
|
||||
} ClientCertMode;
|
||||
|
||||
typedef struct HbaLine
|
||||
{
|
||||
int linenumber;
|
||||
@ -86,7 +93,7 @@ typedef struct HbaLine
|
||||
int ldapscope;
|
||||
char *ldapprefix;
|
||||
char *ldapsuffix;
|
||||
bool clientcert;
|
||||
ClientCertMode clientcert;
|
||||
char *krb_realm;
|
||||
bool include_realm;
|
||||
bool compat_realm;
|
||||
|
@ -13,7 +13,7 @@ use SSLServer;
|
||||
|
||||
if ($ENV{with_openssl} eq 'yes')
|
||||
{
|
||||
plan tests => 71;
|
||||
plan tests => 75;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -378,6 +378,27 @@ test_connect_fails(
|
||||
qr/SSL error/,
|
||||
"certificate authorization fails with revoked client cert");
|
||||
|
||||
# Check that connecting with auth-option verify-full in pg_hba:
|
||||
# works, iff username matches Common Name
|
||||
# fails, iff username doesn't match Common Name.
|
||||
$common_connstr =
|
||||
"sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR";
|
||||
|
||||
test_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");
|
||||
|
||||
test_connect_fails($common_connstr,
|
||||
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
|
||||
qr/FATAL/,
|
||||
"auth_option clientcert=verify-full fails with mismatching username and Common Name");
|
||||
|
||||
# Check that connecting with auth-optionverify-ca in pg_hba :
|
||||
# works, when username doesn't match Common Name
|
||||
test_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");
|
||||
|
||||
# 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');
|
||||
$common_connstr =
|
||||
|
@ -103,8 +103,10 @@ sub configure_test_server_for_ssl
|
||||
# Create test users and databases
|
||||
$node->psql('postgres', "CREATE USER ssltestuser");
|
||||
$node->psql('postgres', "CREATE USER anotheruser");
|
||||
$node->psql('postgres', "CREATE USER yetanotheruser");
|
||||
$node->psql('postgres', "CREATE DATABASE trustdb");
|
||||
$node->psql('postgres', "CREATE DATABASE certdb");
|
||||
$node->psql('postgres', "CREATE DATABASE verifydb");
|
||||
|
||||
# Update password of each user as needed.
|
||||
if (defined($password))
|
||||
@ -183,11 +185,17 @@ sub configure_hba_for_ssl
|
||||
# When connecting to certdb, also check the client certificate.
|
||||
open my $hba, '>', "$pgdata/pg_hba.conf";
|
||||
print $hba
|
||||
"# TYPE DATABASE USER ADDRESS METHOD\n";
|
||||
"# TYPE DATABASE USER ADDRESS METHOD OPTIONS\n";
|
||||
print $hba
|
||||
"hostssl trustdb all $serverhost/32 $authmethod\n";
|
||||
print $hba
|
||||
"hostssl trustdb all ::1/128 $authmethod\n";
|
||||
print $hba
|
||||
"hostssl verifydb ssltestuser $serverhost/32 $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb anotheruser $serverhost/32 $authmethod clientcert=verify-full\n";
|
||||
print $hba
|
||||
"hostssl verifydb yetanotheruser $serverhost/32 $authmethod clientcert=verify-ca\n";
|
||||
print $hba
|
||||
"hostssl certdb all $serverhost/32 cert\n";
|
||||
print $hba
|
||||
|
Reference in New Issue
Block a user