-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sql,pgwire,server: factor the password expiry code (scram 6/10) #74846
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @knz and @otan)
pkg/sql/user.go, line 117 at r1 (raw file):
}) if validUntil != nil { if validUntil.Time.Sub(timeutil.Now()) < 0 {
i don't believe we need this here -- as per the comment a few lines below, the root user cannot have password expiry.
pkg/sql/pgwire/auth_methods.go, line 157 at r1 (raw file):
if expired { c.LogAuthFailed(ctx, eventpb.AuthFailReason_CREDENTIALS_EXPIRED, nil) // TODO(knz): Consider omitting the error return here, and let the
what's the reasoning for using a different error? i thought we would not want to reveal the cause for security reasons (which is how other password auth errors are treated)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @otan and @rafiss)
pkg/sql/user.go, line 117 at r1 (raw file):
Previously, rafiss (Rafi Shamim) wrote…
i don't believe we need this here -- as per the comment a few lines below, the root user cannot have password expiry.
Nice catch. Fixed.
pkg/sql/pgwire/auth_methods.go, line 157 at r1 (raw file):
Previously, rafiss (Rafi Shamim) wrote…
what's the reasoning for using a different error? i thought we would not want to reveal the cause for security reasons (which is how other password auth errors are treated)
You're right.
What we really need is two different error paths:
- one error return, that results in a
log.Info
in the session log, so that an operator can troubleshoot failed logins. - separately, a "client error" to return to the client, that's the same regardless of the precise failure reason.
I think for the 2nd point it's better to concentrate that client error logic in the .Authenticate
call in auth.go
. After all, the other code paths above here do return err
and are also misbehaving in that sense.
Are you OK with me handling this in a separate PR?
Also IMHO this TODO was bogus, in the simple password case there's already no distinction of cause. So I'm removing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @knz, @otan, and @rafiss)
pkg/sql/pgwire/auth_methods.go, line 157 at r1 (raw file):
Previously, knz (kena) wrote…
You're right.
What we really need is two different error paths:
- one error return, that results in a
log.Info
in the session log, so that an operator can troubleshoot failed logins.- separately, a "client error" to return to the client, that's the same regardless of the precise failure reason.
I think for the 2nd point it's better to concentrate that client error logic in the
.Authenticate
call inauth.go
. After all, the other code paths above here doreturn err
and are also misbehaving in that sense.
Are you OK with me handling this in a separate PR?Also IMHO this TODO was bogus, in the simple password case there's already no distinction of cause. So I'm removing it.
yes a separate PR for the client error handling sounds good
Followup issue to do the error handling better: #47595 |
NB: I'm holding off merging this PR until all the PRs in the sequence are ready (otherwise, the entire chain will be auto-closed by github). |
To understand this commit, it is perhaps useful to remember that password credentials, in Postgres's SQL dialect, have their own expiration timestamp, set via the VALID UNTIL role option. The timestamp is attached to the password, not the user account, such that a user can still log in using a separate authentication method even when their password is expired. Additionally, it is perhaps useful to understand that throughout the authentication stack, we distinguish the phase where we determine whether a principal *exists* and is allowed to log in (i.e. it has the LOGIN privilege); and the phase where we load its password credentials from storage (or from an in-RAM cache). In particular, we are keen to skip the loading of the password credentials in the case where the client is authenticating using another method than passwords (for example TLS client certs, which is still pretty common). In this context, this commit performs two changes. The first change is an API cleanup. Prior to this commit, we were loading the password expiration timestamp in the first phase (checking that the principal exists), rather than the second phase (loading the credentials). This was a misdesign because, as explained above, the expiry timestamp is a property of the password, and thus needs not be looked at when passwords are not used. We fix this in this commit by making the expiration timestamp part of the second phase, as it should have been from the start. The second change in this commit is a reduction of security risk. Prior to this commit, the password expiration timestamp was checked (compared to the current time) again and again in each of the call points for the password retrieval function. It was the responsibility of each caller to perform this check. This constituted a security risk because a new caller could forget to add the expiry check and bypass the expiration mechanism entirely. We fix this by performing the expiry validation in a central location, in the common function that retrieves the credentials from storage (or cache). If the password is expired, the function also hides the password hash by returning the new "password missing" pseudo-hash, so that the caller cannot any more mistakenly attempt to validate a password despite the expiration. Release note: None
74301: sql,server: support SCRAM authentication for SQL sessions r=rafiss,JeffSwenson,bdarnell,aaron-crl,kpatron-cockroachlabs a=knz Fixes #42519. Fixes #74511. Informs #65117. Epic CRDB-5349 **tldr:** this PR adds support for the SCRAM-SHA-256 authentication method, as defined by IETF RFCs [5802](https://datatracker.ietf.org/doc/html/rfc5802) and [7677](https://datatracker.ietf.org/doc/html/rfc7677) and as supported by PostgreSQL. This offers [multiple security benefits](#scram-benefits) over CockroachDB's current use of cleartext password authentication. To use this feature, 1) the stored password hashes must use the SCRAM encoding (this requires a migration away from crdb's bcrypt-based hashes); and 2) one of the SCRAM authentication methods must be enabled explictly via the HBA configuration (cluster setting `server.host_based_authentication.configuration`). ### How to review this work The addition of the feature goes as follows: 1. adding HBA syntax and authentication method hooks for `scram-sha-256` and `cert-scram-sha-256`. These are gated behind a new cluster version, so that previous-version nodes do not get confused by the new syntax. Split into separate PR: #74842 2. extending ALTER/CREATE USER/ROLE WITH PASSWORD to recognize SCRAM hashes. This allows operators to use these SQL statements to populate SCRAM hashes. (This should also be seen as the more desirable way to configure SQL user accounts: it ensures that the CockroachDB node never ever sees the cleartext password of an account. Even if we later support computing the SCRAM hash server-side, pre-computing the hash client-side should be seen and documented as the superior approach.) Split into separate PR: #74843 3. extending the existing cleartext methods inside CockroachDB (used for the HTTP interface and the `password`-based methods in SQL) to compare a cleartext password to a pre-computed SCRAM hash. This ensures that the existing password mechanisms that are cleartext-based continue to work even after the stored credentials start using the SCRAM encoding. Split into separate PRs: #74844 and #74845 4. extending the code created at step 1 to actually use the stored SCRAM credentials for client authentication. This achieves the main goal. Split into separate PRs: #74846 and #74847 5. making the hash method used to encode cleartext passwords passed to ALTER/CREATE USER/ROLE WITH PASSWORD configurable, using a new cluster setting `server.user_login.password_encryption`, and exposing its value via the pg-compatible session variable `password_encryption`. This is a convenience feature - properly secured deployments should not need this, as they should use pre-hashing client-side instead (see note at point 2 above). Split into separate PR: #74848 6. making crdb auto-select SCRAM authn if the stored password uses SCRAM. This is one step to ensuring that previous-version clusters are automatically opted into SCRAM. Split into separate PR: #74849 7. Auto-upgrade the stored credentials from bcrypt to SCRAM, when enabled and possible. Split into separate PR: #74850 The reviewer is invited to consider each PR in turn, read its commit message(s) to understand the goal of that PR, then review the PR to ascertain that the implementation (and relevant tests) match the goals announced in the commit message. Co-authored-by: Raphael 'kena' Poss <[email protected]>
(PR peeled away from #74301; previous PR in series #74845; next PR in series: #74847)
Epic CRDB-5349
To understand this commit, it is perhaps useful to remember that
password credentials, in Postgres's SQL dialect, have their own
expiration timestamp, set via the VALID UNTIL role option. The
timestamp is attached to the password, not the user account, such that
a user can still log in using a separate authentication method even
when their password is expired.
Additionally, it is perhaps useful to understand that throughout the
authentication stack, we distinguish the phase where we determine
whether a principal exists and is allowed to log in (i.e. it has the
LOGIN privilege); and the phase where we load its password credentials
from storage (or from an in-RAM cache). In particular, we are keen to
skip the loading of the password credentials in the case where the
client is authenticating using another method than passwords (for
example TLS client certs, which is still pretty common).
In this context, this commit performs two changes.
The first change is an API cleanup.
Prior to this commit, we were loading the password expiration
timestamp in the first phase (checking that the principal exists),
rather than the second phase (loading the credentials). This was a
misdesign because, as explained above, the expiry timestamp is a
property of the password, and thus needs not be looked at when
passwords are not used.
We fix this in this commit by making the expiration timestamp part of
the second phase, as it should have been from the start.
The second change in this commit is a reduction of security risk.
Prior to this commit, the password expiration timestamp was checked
(compared to the current time) again and again in each of the call
points for the password retrieval function. It was the responsibility
of each caller to perform this check. This constituted a security risk
because a new caller could forget to add the expiry check and bypass
the expiration mechanism entirely.
We fix this by performing the expiry validation in a central location,
in the common function that retrieves the credentials from storage (or
cache). If the password is expired, the function also hides the
password hash by returning the new "password missing" pseudo-hash, so
that the caller cannot any more mistakenly attempt to validate a
password despite the expiration.
Release note: None