diff options
author | Michael Paquier <michael@paquier.xyz> | 2023-03-14 14:00:05 +0900 |
---|---|---|
committer | Michael Paquier <michael@paquier.xyz> | 2023-03-14 14:00:05 +0900 |
commit | 3a465cc6783f586096d9f885c3fc544d82eb8f19 (patch) | |
tree | adc19ac1449e6759680d652bff0bd4cbc4136fd3 /src/interfaces/libpq/fe-connect.c | |
parent | 727400994d5a27f2859361f82d0ec9ac2b23385c (diff) | |
download | postgresql-3a465cc6783f586096d9f885c3fc544d82eb8f19.tar.gz postgresql-3a465cc6783f586096d9f885c3fc544d82eb8f19.zip |
libpq: Add support for require_auth to control authorized auth methods
The new connection parameter require_auth allows a libpq client to
define a list of comma-separated acceptable authentication types for use
with the server. There is no negotiation: if the server does not
present one of the allowed authentication requests, the connection
attempt done by the client fails.
The following keywords can be defined in the list:
- password, for AUTH_REQ_PASSWORD.
- md5, for AUTH_REQ_MD5.
- gss, for AUTH_REQ_GSS[_CONT].
- sspi, for AUTH_REQ_SSPI and AUTH_REQ_GSS_CONT.
- scram-sha-256, for AUTH_REQ_SASL[_CONT|_FIN].
- creds, for AUTH_REQ_SCM_CREDS (perhaps this should be removed entirely
now).
- none, to control unauthenticated connections.
All the methods that can be defined in the list can be negated, like
"!password", in which case the server must NOT use the listed
authentication type. The special method "none" allows/disallows the use
of unauthenticated connections (but it does not govern transport-level
authentication via TLS or GSSAPI).
Internally, the patch logic is tied to check_expected_areq(), that was
used for channel_binding, ensuring that an incoming request is
compatible with conn->require_auth. It also introduces a new flag,
conn->client_finished_auth, which is set by various authentication
routines when the client side of the handshake is finished. This
signals to check_expected_areq() that an AUTH_REQ_OK from the server is
expected, and allows the client to complain if the server bypasses
authentication entirely, with for example the reception of a too-early
AUTH_REQ_OK message.
Regression tests are added in authentication TAP tests for all the
keywords supported (except "creds", because it is around only for
compatibility reasons). A new TAP script has been added for SSPI, as
there was no script dedicated to it yet. It relies on SSPI being the
default authentication method on Windows, as set by pg_regress.
Author: Jacob Champion
Reviewed-by: Peter Eisentraut, David G. Johnston, Michael Paquier
Discussion: https://postgr.es/m/9e5a8ccddb8355ea9fa4b75a1e3a9edc88a70cd3.camel@vmware.com
Diffstat (limited to 'src/interfaces/libpq/fe-connect.c')
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5638b223cb4..dd4b98e0998 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); @@ -1238,6 +1243,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 */ if (conn->channel_binding) @@ -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); |