diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 3ccd8ff942..3706d349ab 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1220,6 +1220,111 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + require_auth + + + Specifies the authentication method that the client requires from the + server. If the server does not use the required method to authenticate + the client, or if the authentication handshake is not fully completed by + the server, the connection will fail. A comma-separated list of methods + may also be provided, of which the server must use exactly one in order + for the connection to succeed. By default, any authentication method is + accepted, and the server is free to skip authentication altogether. + + + Methods may be negated with the addition of a ! + prefix, in which case the server must not attempt + the listed method; any other method is accepted, and the server is free + not to authenticate the client at all. If a comma-separated list is + provided, the server may not attempt any of the + listed negated methods. Negated and non-negated forms may not be + combined in the same setting. + + + As a final special case, the none method requires the + server not to use an authentication challenge. (It may also be negated, + to require some form of authentication.) + + + The following methods may be specified: + + + + password + + + The server must request plaintext password authentication. + + + + + + md5 + + + The server must request MD5 hashed password authentication. + + + + + + gss + + + The server must either request a Kerberos handshake via + GSSAPI or establish a + GSS-encrypted channel (see also + ). + + + + + + sspi + + + The server must request Windows SSPI + authentication. + + + + + + scram-sha-256 + + + The server must successfully complete a SCRAM-SHA-256 authentication + exchange with the client. + + + + + + creds + + + The server must request SCM credential authentication (deprecated + as of PostgreSQL 9.1). + + + + + + none + + + The server must not prompt the client for an authentication + exchange. (This does not prohibit client certificate authentication + via TLS, nor GSS authentication via its encrypted transport.) + + + + + + + + channel_binding @@ -7774,6 +7879,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) + + + + PGREQUIREAUTH + + PGREQUIREAUTH behaves the same as the connection parameter. + + + diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 66ba359390..5268d442ab 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -123,6 +123,7 @@ extern PGDLLIMPORT bool Db_user_namespace; #define AUTH_REQ_SASL 10 /* Begin SASL authentication */ #define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ #define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ +#define AUTH_REQ_MAX AUTH_REQ_SASL_FIN /* maximum AUTH_REQ_* value */ typedef uint32 AuthRequest; diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index 12c3d0bc33..277f72b280 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -282,6 +282,7 @@ scram_exchange(void *opaq, char *input, int inputlen, } *done = true; state->state = FE_SCRAM_FINISHED; + state->conn->client_finished_auth = true; break; default: diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index ab454e6cd0..a3b80dc550 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -136,7 +136,10 @@ pg_GSS_continue(PGconn *conn, int payloadlen) } if (maj_stat == GSS_S_COMPLETE) + { + conn->client_finished_auth = true; gss_release_name(&lmin_s, &conn->gtarg_nam); + } return STATUS_OK; } @@ -321,6 +324,9 @@ pg_SSPI_continue(PGconn *conn, int payloadlen) FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); } + if (r == SEC_E_OK) + conn->client_finished_auth = true; + /* Cleanup is handled by the code in freePGconn() */ return STATUS_OK; } @@ -735,6 +741,8 @@ pg_local_sendauth(PGconn *conn) strerror_r(errno, sebuf, sizeof(sebuf))); return STATUS_ERROR; } + + conn->client_finished_auth = true; return STATUS_OK; #else libpq_append_conn_error(conn, "SCM_CRED authentication method not supported"); @@ -805,6 +813,41 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) return ret; } +/* + * Translate a disallowed AuthRequest code into an error message. + */ +static const char * +auth_method_description(AuthRequest areq) +{ + switch (areq) + { + case AUTH_REQ_PASSWORD: + return libpq_gettext("server requested a cleartext password"); + case AUTH_REQ_MD5: + return libpq_gettext("server requested a hashed password"); + case AUTH_REQ_GSS: + case AUTH_REQ_GSS_CONT: + return libpq_gettext("server requested GSSAPI authentication"); + case AUTH_REQ_SSPI: + return libpq_gettext("server requested SSPI authentication"); + case AUTH_REQ_SCM_CREDS: + return libpq_gettext("server requested UNIX socket credentials"); + case AUTH_REQ_SASL: + case AUTH_REQ_SASL_CONT: + case AUTH_REQ_SASL_FIN: + return libpq_gettext("server requested SASL authentication"); + } + + return libpq_gettext("server requested an unknown authentication type"); +} + +/* + * Convenience macro for checking the allowed_auth_methods bitmask. Caller + * must ensure that type is not greater than 31 (high bit of the bitmask). + */ +#define auth_method_allowed(conn, type) \ + (((conn)->allowed_auth_methods & (1 << (type))) != 0) + /* * Verify that the authentication request is expected, given the connection * parameters. This is especially important when the client wishes to @@ -814,6 +857,99 @@ static bool check_expected_areq(AuthRequest areq, PGconn *conn) { bool result = true; + const char *reason = NULL; + + StaticAssertDecl((sizeof(conn->allowed_auth_methods) * CHAR_BIT) > AUTH_REQ_MAX, + "AUTH_REQ_MAX overflows the allowed_auth_methods bitmask"); + + /* + * If the user required a specific auth method, or specified an allowed + * set, then reject all others here, and make sure the server actually + * completes an authentication exchange. + */ + if (conn->require_auth) + { + switch (areq) + { + case AUTH_REQ_OK: + + /* + * Check to make sure we've actually finished our exchange (or + * else that the user has allowed an authentication-less + * connection). + * + * If the user has allowed both SCRAM and unauthenticated + * (trust) connections, then this check will silently accept + * partial SCRAM exchanges, where a misbehaving server does + * not provide its verifier before sending an OK. This is + * consistent with historical behavior, but it may be a point + * to revisit in the future, since it could allow a server + * that doesn't know the user's password to silently harvest + * material for a brute force attack. + */ + if (!conn->auth_required || conn->client_finished_auth) + break; + + /* + * No explicit authentication request was made by the server + * -- or perhaps it was made and not completed, in the case of + * SCRAM -- but there is one special case to check. If the + * user allowed "gss", then a GSS-encrypted channel also + * satisfies the check. + */ +#ifdef ENABLE_GSS + if (auth_method_allowed(conn, AUTH_REQ_GSS) && conn->gssenc) + { + /* + * If implicit GSS auth has already been performed via GSS + * encryption, we don't need to have performed an + * AUTH_REQ_GSS exchange. This allows require_auth=gss to + * be combined with gssencmode, since there won't be an + * explicit authentication request in that case. + */ + } + else +#endif + { + reason = libpq_gettext("server did not complete authentication"); + result = false; + } + + break; + + case AUTH_REQ_PASSWORD: + case AUTH_REQ_MD5: + case AUTH_REQ_GSS: + case AUTH_REQ_GSS_CONT: + case AUTH_REQ_SSPI: + case AUTH_REQ_SCM_CREDS: + case AUTH_REQ_SASL: + case AUTH_REQ_SASL_CONT: + case AUTH_REQ_SASL_FIN: + + /* + * We don't handle these with the default case, to avoid + * bit-shifting past the end of the allowed_auth_methods mask + * if the server sends an unexpected AuthRequest. + */ + result = auth_method_allowed(conn, areq); + break; + + default: + result = false; + break; + } + } + + if (!result) + { + if (!reason) + reason = auth_method_description(areq); + + libpq_append_conn_error(conn, "auth method \"%s\" requirement failed: %s", + conn->require_auth, reason); + return result; + } /* * When channel_binding=require, we must protect against two cases: (1) we @@ -1008,6 +1144,9 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn) "fe_sendauth: error sending password authentication\n"); return STATUS_ERROR; } + + /* We expect no further authentication requests. */ + conn->client_finished_auth = true; break; } diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5638b223cb..dd4b98e099 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -307,6 +307,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + {"require_auth", "PGREQUIREAUTH", NULL, NULL, + "Require-Auth", "", 14, /* sizeof("scram-sha-256") == 14 */ + offsetof(struct pg_conn, require_auth)}, + {"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", "TLSv1.2", NULL, "SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */ offsetof(struct pg_conn, ssl_min_protocol_version)}, @@ -595,6 +599,7 @@ pqDropServerData(PGconn *conn) /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; conn->auth_req_received = false; + conn->client_finished_auth = false; conn->password_needed = false; conn->write_failed = false; free(conn->write_err_msg); @@ -1237,6 +1242,170 @@ connectOptions2(PGconn *conn) } } + /* + * parse and validate require_auth option + */ + if (conn->require_auth && conn->require_auth[0]) + { + char *s = conn->require_auth; + bool first, + more; + bool negated = false; + + /* + * By default, start from an empty set of allowed options and add to + * it. + */ + conn->auth_required = true; + conn->allowed_auth_methods = 0; + + for (first = true, more = true; more; first = false) + { + char *method, + *part; + uint32 bits; + + part = parse_comma_separated_list(&s, &more); + if (part == NULL) + goto oom_error; + + /* + * Check for negation, e.g. '!password'. If one element is + * negated, they all have to be. + */ + method = part; + if (*method == '!') + { + if (first) + { + /* + * Switch to a permissive set of allowed options, and + * subtract from it. + */ + conn->auth_required = false; + conn->allowed_auth_methods = -1; + } + else if (!negated) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "negative require_auth method \"%s\" cannot be mixed with non-negative methods", + method); + + free(part); + return false; + } + + negated = true; + method++; + } + else if (negated) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "require_auth method \"%s\" cannot be mixed with negative methods", + method); + + free(part); + return false; + } + + if (strcmp(method, "password") == 0) + { + bits = (1 << AUTH_REQ_PASSWORD); + } + else if (strcmp(method, "md5") == 0) + { + bits = (1 << AUTH_REQ_MD5); + } + else if (strcmp(method, "gss") == 0) + { + bits = (1 << AUTH_REQ_GSS); + bits |= (1 << AUTH_REQ_GSS_CONT); + } + else if (strcmp(method, "sspi") == 0) + { + bits = (1 << AUTH_REQ_SSPI); + bits |= (1 << AUTH_REQ_GSS_CONT); + } + else if (strcmp(method, "scram-sha-256") == 0) + { + /* This currently assumes that SCRAM is the only SASL method. */ + bits = (1 << AUTH_REQ_SASL); + bits |= (1 << AUTH_REQ_SASL_CONT); + bits |= (1 << AUTH_REQ_SASL_FIN); + } + else if (strcmp(method, "creds") == 0) + { + bits = (1 << AUTH_REQ_SCM_CREDS); + } + else if (strcmp(method, "none") == 0) + { + /* + * Special case: let the user explicitly allow (or disallow) + * connections where the server does not send an explicit + * authentication challenge, such as "trust" and "cert" auth. + */ + if (negated) /* "!none" */ + { + if (conn->auth_required) + goto duplicate; + + conn->auth_required = true; + } + else /* "none" */ + { + if (!conn->auth_required) + goto duplicate; + + conn->auth_required = false; + } + + free(part); + continue; /* avoid the bitmask manipulation below */ + } + else + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid require_auth method: \"%s\"", + method); + + free(part); + return false; + } + + /* Update the bitmask. */ + if (negated) + { + if ((conn->allowed_auth_methods & bits) == 0) + goto duplicate; + + conn->allowed_auth_methods &= ~bits; + } + else + { + if ((conn->allowed_auth_methods & bits) == bits) + goto duplicate; + + conn->allowed_auth_methods |= bits; + } + + free(part); + continue; + + duplicate: + + /* + * A duplicated method probably indicates a typo in a setting + * where typos are extremely risky. + */ + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "require_auth method \"%s\" is specified more than once", + part); + + free(part); + return false; + } + } + /* * validate channel_binding option */ @@ -4055,6 +4224,7 @@ freePGconn(PGconn *conn) free(conn->sslcompression); free(conn->sslsni); free(conn->requirepeer); + free(conn->require_auth); free(conn->ssl_min_protocol_version); free(conn->ssl_max_protocol_version); free(conn->gssencmode); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index d7ec5ed429..1dc264fe54 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -396,6 +396,7 @@ struct pg_conn char *ssl_min_protocol_version; /* minimum TLS protocol version */ char *ssl_max_protocol_version; /* maximum TLS protocol version */ char *target_session_attrs; /* desired session properties */ + char *require_auth; /* name of the expected auth method */ /* Optional file to write trace info to */ FILE *Pfdebug; @@ -457,6 +458,14 @@ struct pg_conn bool write_failed; /* have we had a write failure on sock? */ char *write_err_msg; /* write error message, or NULL if OOM */ + bool auth_required; /* require an authentication challenge from + * the server? */ + uint32 allowed_auth_methods; /* bitmask of acceptable AuthRequest + * codes */ + bool client_finished_auth; /* have we finished our half of the + * authentication exchange? */ + + /* Transient state needed while establishing connection */ PGTargetServerType target_server_type; /* desired session properties */ bool try_next_addr; /* time to advance to next address/host? */ diff --git a/src/test/authentication/meson.build b/src/test/authentication/meson.build index 3fe279fc10..e501041785 100644 --- a/src/test/authentication/meson.build +++ b/src/test/authentication/meson.build @@ -10,6 +10,7 @@ tests += { 't/002_saslprep.pl', 't/003_peer.pl', 't/004_file_inclusion.pl', + 't/005_sspi.pl', ], }, } diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index a2fde1408b..cba5d7d648 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -115,6 +115,114 @@ is($res, 't', "users with trust authentication use SYSTEM_USER = NULL in parallel workers" ); +# Explicitly specifying an empty require_auth (the default) should always +# succeed. +$node->connect_ok("user=scram_role require_auth=", + "empty require_auth succeeds"); + +# All these values of require_auth should fail, as trust is expected. +$node->connect_fails( + "user=scram_role require_auth=gss", + "GSS authentication required, fails with trust auth", + expected_stderr => + qr/auth method "gss" requirement failed: server did not complete authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=sspi", + "SSPI authentication required, fails with trust auth", + expected_stderr => + qr/auth method "sspi" requirement failed: server did not complete authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=password", + "password authentication required, fails with trust auth", + expected_stderr => + qr/auth method "password" requirement failed: server did not complete authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=md5", + "MD5 authentication required, fails with trust auth", + expected_stderr => + qr/auth method "md5" requirement failed: server did not complete authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=scram-sha-256", + "SCRAM authentication required, fails with trust auth", + expected_stderr => + qr/auth method "scram-sha-256" requirement failed: server did not complete authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=password,scram-sha-256", + "password and SCRAM authentication required, fails with trust auth", + expected_stderr => + qr/auth method "password,scram-sha-256" requirement failed: server did not complete authentication/ +); + +# These negative patterns of require_auth should succeed. +$node->connect_ok("user=scram_role require_auth=!gss", + "GSS authentication can be forbidden, succeeds with trust auth"); +$node->connect_ok("user=scram_role require_auth=!sspi", + "SSPI authentication can be forbidden, succeeds with trust auth"); +$node->connect_ok("user=scram_role require_auth=!password", + "password authentication can be forbidden, succeeds with trust auth"); +$node->connect_ok("user=scram_role require_auth=!md5", + "md5 authentication can be forbidden, succeeds with trust auth"); +$node->connect_ok("user=scram_role require_auth=!scram-sha-256", + "SCRAM authentication can be forbidden, succeeds with trust auth"); +$node->connect_ok( + "user=scram_role require_auth=!password,!scram-sha-256", + "multiple authentication types forbidden, succeeds with trust auth"); + +# require_auth=[!]none should interact correctly with trust auth. +$node->connect_ok("user=scram_role require_auth=none", + "all authentication types forbidden, succeeds with trust auth"); +$node->connect_fails( + "user=scram_role require_auth=!none", + "any authentication types required, fails with trust auth", + expected_stderr => qr/server did not complete authentication/); + +# Negative and positive require_auth methods can't be mixed. +$node->connect_fails( + "user=scram_role require_auth=scram-sha-256,!md5", + "negative require_auth methods cannot be mixed with positive ones", + expected_stderr => + qr/negative require_auth method "!md5" cannot be mixed with non-negative methods/ +); +$node->connect_fails( + "user=scram_role require_auth=!password,!none,scram-sha-256", + "positive require_auth methods cannot be mixed with negative one", + expected_stderr => + qr/require_auth method "scram-sha-256" cannot be mixed with negative methods/ +); + +# require_auth methods cannot have duplicated values. +$node->connect_fails( + "user=scram_role require_auth=password,md5,password", + "require_auth methods cannot include duplicates, positive case", + expected_stderr => + qr/require_auth method "password" is specified more than once/); +$node->connect_fails( + "user=scram_role require_auth=!password,!md5,!password", + "require_auth methods cannot be duplicated, negative case", + expected_stderr => + qr/require_auth method "!password" is specified more than once/); +$node->connect_fails( + "user=scram_role require_auth=none,md5,none", + "require_auth methods cannot be duplicated, none case", + expected_stderr => + qr/require_auth method "none" is specified more than once/); +$node->connect_fails( + "user=scram_role require_auth=!none,!md5,!none", + "require_auth methods cannot be duplicated, !none case", + expected_stderr => + qr/require_auth method "!none" is specified more than once/); + +# Unknown value defined in require_auth. +$node->connect_fails( + "user=scram_role require_auth=none,abcdefg", + "unknown require_auth methods are rejected", + expected_stderr => qr/invalid require_auth method: "abcdefg"/); + # For plain "password" method, all users should also be able to connect. reset_pg_hba($node, 'all', 'all', 'password'); test_conn($node, 'user=scram_role', 'password', 0, @@ -124,6 +232,47 @@ test_conn($node, 'user=md5_role', 'password', 0, log_like => [qr/connection authenticated: identity="md5_role" method=password/]); +# require_auth succeeds here with a plaintext password. +$node->connect_ok("user=scram_role require_auth=password", + "password authentication required, works with password auth"); +$node->connect_ok("user=scram_role require_auth=!none", + "any authentication required, works with password auth"); +$node->connect_ok( + "user=scram_role require_auth=scram-sha-256,password,md5", + "multiple authentication types required, works with password auth"); + +# require_auth fails for other authentication types. +$node->connect_fails( + "user=scram_role require_auth=md5", + "md5 authentication required, fails with password auth", + expected_stderr => + qr/auth method "md5" requirement failed: server requested a cleartext password/ +); +$node->connect_fails( + "user=scram_role require_auth=scram-sha-256", + "SCRAM authentication required, fails with password auth", + expected_stderr => + qr/auth method "scram-sha-256" requirement failed: server requested a cleartext password/ +); +$node->connect_fails( + "user=scram_role require_auth=none", + "all authentication forbidden, fails with password auth", + expected_stderr => + qr/auth method "none" requirement failed: server requested a cleartext password/ +); + +# Disallowing password authentication fails, even if requested by server. +$node->connect_fails( + "user=scram_role require_auth=!password", + "password authentication forbidden, fails with password auth", + expected_stderr => qr/server requested a cleartext password/); +$node->connect_fails( + "user=scram_role require_auth=!password,!md5,!scram-sha-256", + "multiple authentication types forbidden, fails with password auth", + expected_stderr => + qr/ method "!password,!md5,!scram-sha-256" requirement failed: server requested a cleartext password/ +); + # For "scram-sha-256" method, user "scram_role" should be able to connect. reset_pg_hba($node, 'all', 'all', 'scram-sha-256'); test_conn( @@ -137,6 +286,46 @@ test_conn( test_conn($node, 'user=md5_role', 'scram-sha-256', 2, log_unlike => [qr/connection authenticated:/]); +# require_auth should succeeds with SCRAM when it is required. +$node->connect_ok( + "user=scram_role require_auth=scram-sha-256", + "SCRAM authentication required, works with SCRAM auth"); +$node->connect_ok("user=scram_role require_auth=!none", + "any authentication required, works with SCRAM auth"); +$node->connect_ok( + "user=scram_role require_auth=password,scram-sha-256,md5", + "multiple authentication types required, works with SCRAM auth"); + +# Authentication fails for other authentication types. +$node->connect_fails( + "user=scram_role require_auth=password", + "password authentication required, fails with SCRAM auth", + expected_stderr => + qr/auth method "password" requirement failed: server requested SASL authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=md5", + "md5 authentication required, fails with SCRAM auth", + expected_stderr => + qr/auth method "md5" requirement failed: server requested SASL authentication/ +); +$node->connect_fails( + "user=scram_role require_auth=none", + "all authentication forbidden, fails with SCRAM auth", + expected_stderr => + qr/auth method "none" requirement failed: server requested SASL authentication/ +); + +# Authentication fails if SCRAM authentication is forbidden. +$node->connect_fails( + "user=scram_role require_auth=!scram-sha-256", + "SCRAM authentication forbidden, fails with SCRAM auth", + expected_stderr => qr/server requested SASL authentication/); +$node->connect_fails( + "user=scram_role require_auth=!password,!md5,!scram-sha-256", + "multiple authentication types forbidden, fails with SCRAM auth", + expected_stderr => qr/server requested SASL authentication/); + # Test that bad passwords are rejected. $ENV{"PGPASSWORD"} = 'badpass'; test_conn($node, 'user=scram_role', 'scram-sha-256', 2, @@ -153,6 +342,49 @@ test_conn($node, 'user=md5_role', 'md5', 0, log_like => [qr/connection authenticated: identity="md5_role" method=md5/]); +# require_auth succeeds with MD5 required. +$node->connect_ok("user=md5_role require_auth=md5", + "MD5 authentication required, works with MD5 auth"); +$node->connect_ok("user=md5_role require_auth=!none", + "any authentication required, works with MD5 auth"); +$node->connect_ok( + "user=md5_role require_auth=md5,scram-sha-256,password", + "multiple authentication types required, works with MD5 auth"); + +# Authentication fails if other types are required. +$node->connect_fails( + "user=md5_role require_auth=password", + "password authentication required, fails with MD5 auth", + expected_stderr => + qr/auth method "password" requirement failed: server requested a hashed password/ +); +$node->connect_fails( + "user=md5_role require_auth=scram-sha-256", + "SCRAM authentication required, fails with MD5 auth", + expected_stderr => + qr/auth method "scram-sha-256" requirement failed: server requested a hashed password/ +); +$node->connect_fails( + "user=md5_role require_auth=none", + "all authentication types forbidden, fails with MD5 auth", + expected_stderr => + qr/auth method "none" requirement failed: server requested a hashed password/ +); + +# Authentication fails if MD5 is forbidden. +$node->connect_fails( + "user=md5_role require_auth=!md5", + "password authentication forbidden, fails with MD5 auth", + expected_stderr => + qr/auth method "!md5" requirement failed: server requested a hashed password/ +); +$node->connect_fails( + "user=md5_role require_auth=!password,!md5,!scram-sha-256", + "multiple authentication types forbidden, fails with MD5 auth", + expected_stderr => + qr/auth method "!password,!md5,!scram-sha-256" requirement failed: server requested a hashed password/ +); + # Test SYSTEM_USER <> NULL with parallel workers. $node->safe_psql( 'postgres', diff --git a/src/test/authentication/t/005_sspi.pl b/src/test/authentication/t/005_sspi.pl new file mode 100644 index 0000000000..eed0fb125e --- /dev/null +++ b/src/test/authentication/t/005_sspi.pl @@ -0,0 +1,41 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Tests targeting SSPI on Windows. + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +if (!$windows_os || $use_unix_sockets) +{ + plan skip_all => + "SSPI tests require Windows (without PG_TEST_USE_UNIX_SOCKETS)"; +} + +# Initialize primary node +my $node = PostgreSQL::Test::Cluster->new('primary'); +$node->init; +$node->append_conf('postgresql.conf', "log_connections = on\n"); +$node->start; + +# SSPI is set up by default. Make sure it interacts correctly with +# require_auth. +$node->connect_ok("require_auth=sspi", + "SSPI authentication required, works with SSPI auth"); +$node->connect_fails( + "require_auth=!sspi", + "SSPI authentication forbidden, fails with SSPI auth", + expected_stderr => + qr/auth method "!sspi" requirement failed: server requested SSPI authentication/ +); +$node->connect_fails( + "require_auth=scram-sha-256", + "SCRAM authentication required, fails with SSPI auth", + expected_stderr => + qr/auth method "scram-sha-256" requirement failed: server requested SSPI authentication/ +); + +done_testing(); diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl index 3bc4ad7dd3..a0ed3a0a0b 100644 --- a/src/test/kerberos/t/001_auth.pl +++ b/src/test/kerberos/t/001_auth.pl @@ -337,6 +337,32 @@ test_query( 'gssencmode=require', 'sending 100K lines works'); +# require_auth=gss succeeds if required. +$node->connect_ok( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=disable require_auth=gss", + "GSS authentication requested, works with non-encyrpted GSS"); +$node->connect_ok( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require require_auth=gss", + "GSS authentication requested, works with encrypted GSS auth"); + +# require_auth=sspi fails if required. +$node->connect_fails( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=disable require_auth=sspi", + "SSPI authentication requested, fails with non-encrypted GSS", + expected_stderr => + qr/auth method "sspi" requirement failed: server requested GSSAPI authentication/ +); +$node->connect_fails( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require require_auth=sspi", + "SSPI authentication requested, fails with encrypted GSS", + expected_stderr => + qr/auth method "sspi" requirement failed: server did not complete authentication/ +); + # Test that SYSTEM_USER works. test_query($node, 'test1', 'SELECT SYSTEM_USER;', qr/^gss:test1\@$realm$/s, 'gssencmode=require', 'testing system_user'); @@ -382,6 +408,16 @@ test_access( test_access($node, 'test1', 'SELECT true', 2, 'gssencmode=disable', 'fails with GSS encryption disabled and hostgssenc hba'); +# require_auth=gss succeeds if required. +$node->connect_ok( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require require_auth=gss", + "GSS authentication requested, works with GSS encryption"); +$node->connect_ok( + $node->connstr('postgres') + . " user=test1 host=$host hostaddr=$hostaddr gssencmode=require require_auth=gss,scram-sha-256", + "multiple authentication types requested, works with GSS encryption"); + unlink($node->data_dir . '/pg_hba.conf'); $node->append_conf('pg_hba.conf', qq{hostnogssenc all all $hostaddr/32 gss map=mymap}); diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl index f3ed806ec2..1e027ced01 100644 --- a/src/test/ldap/t/001_auth.pl +++ b/src/test/ldap/t/001_auth.pl @@ -101,6 +101,12 @@ test_access( qr/connection authenticated: identity="uid=test1,dc=example,dc=net" method=ldap/ ],); +# require_auth=password should complete successfully; other methods should fail. +$node->connect_ok("user=test1 require_auth=password", + "password authentication required, works with ldap auth"); +$node->connect_fails("user=test1 require_auth=scram-sha-256", + "SCRAM authentication required, fails with ldap auth"); + note "search+bind"; unlink($node->data_dir . '/pg_hba.conf'); diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index 1d3905d3a1..8038135697 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -140,6 +140,34 @@ $node->connect_ok( qr/connection authenticated: identity="ssltestuser" method=scram-sha-256/ ]); +# channel_binding should continue to work independently of require_auth. +$node->connect_ok( + "$common_connstr user=ssltestuser channel_binding=disable require_auth=scram-sha-256", + "SCRAM with SSL, channel_binding=disable, and require_auth=scram-sha-256" +); +$node->connect_fails( + "$common_connstr user=md5testuser require_auth=md5 channel_binding=require", + "channel_binding can fail even when require_auth succeeds", + expected_stderr => + qr/channel binding required but not supported by server's authentication request/ +); +if ($supports_tls_server_end_point) +{ + $node->connect_ok( + "$common_connstr user=ssltestuser channel_binding=require require_auth=scram-sha-256", + "SCRAM with SSL, channel_binding=require, and require_auth=scram-sha-256" + ); +} +else +{ + $node->connect_fails( + "$common_connstr user=ssltestuser channel_binding=require require_auth=scram-sha-256", + "SCRAM with SSL, channel_binding=require, and require_auth=scram-sha-256", + expected_stderr => + qr/channel binding is required, but server did not offer an authentication method that supports channel binding/ + ); +} + # Now test with a server certificate that uses the RSA-PSS algorithm. # This checks that the certificate can be loaded and that channel binding # works. (see bug #17760)