diff --git a/pkg/security/password.go b/pkg/security/password.go index fb29cb66ae9d..f596fa8c9ad8 100644 --- a/pkg/security/password.go +++ b/pkg/security/password.go @@ -190,6 +190,16 @@ func (s *scramHash) Size() int { return int(unsafe.Sizeof(*s)) + len(s.bytes) + len(s.decoded.Salt) + len(s.decoded.StoredKey) + len(s.decoded.ServerKey) } +// GetSCRAMStoredCredentials retrieves the SCRAM credential parts. +// The caller is responsible for ensuring the hash has method SCRAM-SHA-256. +func GetSCRAMStoredCredentials(hash PasswordHash) (ok bool, creds scram.StoredCredentials) { + h, ok := hash.(*scramHash) + if ok { + return ok, h.decoded + } + return false, creds +} + // LoadPasswordHash decodes a password hash loaded as bytes from a credential store. func LoadPasswordHash(ctx context.Context, storedHash []byte) (res PasswordHash) { res = invalidHash(storedHash) diff --git a/pkg/sql/pgwire/auth_methods.go b/pkg/sql/pgwire/auth_methods.go index 8d02949aa481..e5a2920da197 100644 --- a/pkg/sql/pgwire/auth_methods.go +++ b/pkg/sql/pgwire/auth_methods.go @@ -14,7 +14,6 @@ import ( "bytes" "context" "crypto/tls" - "encoding/base64" "fmt" "math" @@ -195,37 +194,10 @@ func authScram( clientConnection bool, pwRetrieveFn PasswordRetrievalFn, ) error { - // This scram auth credential was generated on pg with: - // create user abc with password 'abc' - // const testPassword = "SCRAM-SHA-256$4096:pAlYy62NTdETKb291V/Wow==$OXMAj9oD53QucEYVMBcdhRnjg2/S/iZY/88ShZnputA=:r8l4c1pk9bmDi+8an059l/nt9Bg1zb1ikkg+DeRv4UQ=" - salt, err := base64.StdEncoding.DecodeString("pAlYy62NTdETKb291V/Wow==") - if err != nil { - panic(err) - } - storedKey, err := base64.StdEncoding.DecodeString("OXMAj9oD53QucEYVMBcdhRnjg2/S/iZY/88ShZnputA=") - if err != nil { - panic(err) - } - serverKey, err := base64.StdEncoding.DecodeString("r8l4c1pk9bmDi+8an059l/nt9Bg1zb1ikkg+DeRv4UQ=") - if err != nil { - panic(err) - } - scramServer, _ := scram.SHA256.NewServer(func(user string) (scram.StoredCredentials, error) { - if user == "abc" { - return scram.StoredCredentials{ - KeyFactors: scram.KeyFactors{ - Salt: string(salt), - Iters: 4096, - }, - StoredKey: storedKey, - ServerKey: serverKey, - }, nil - } - return scram.StoredCredentials{}, errors.New("no scram cookie for you") - }) - - handshake := scramServer.NewConversation() - + // First step: send a SCRAM authentication request to the client. + // We do this with an auth request with the request type SASL, + // and a payload containing the list of supported SCRAM methods. + // // NB: SCRAM-SHA-256-PLUS is not supported, see // https://github.com/cockroachdb/cockroach/issues/74300 // There is one nul byte to terminate the first string, @@ -235,6 +207,44 @@ func authScram( return err } + // While waiting for the client response, concurrently + // load the credentials from storage (or cache). + // Note: if this fails, we can't return the error right away, + // because we need to consume the client response first. This + // will be handled below. + expired, hashedPassword, pwRetrievalErr := pwRetrieveFn(ctx) + + scramServer, _ := scram.SHA256.NewServer(func(user string) (creds scram.StoredCredentials, err error) { + // NB: the username passed in the SCRAM exchange (the user + // parameter in this callback) is ignored by PostgreSQL servers; + // see auth-scram.c, read_client_first_message(). + // + // Therefore, we can't assume that SQL client drivers populate anything + // useful there. So we ignore it too. + + // We still need to check whether the credentials loaded above + // are valid. We place this check in this callback because it + // only needs to happen after the SCRAM handshake actually needs + // to know the credentials. + if expired { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_CREDENTIALS_EXPIRED, nil) + return creds, errExpiredPassword + } else if hashedPassword.Method() != security.HashSCRAMSHA256 { + const credentialsNotSCRAM = "user password hash not in SCRAM format" + c.LogAuthInfof(ctx, credentialsNotSCRAM) + return creds, errors.New(credentialsNotSCRAM) + } + + // The method check above ensures this cast is always valid. + ok, creds := security.GetSCRAMStoredCredentials(hashedPassword) + if !ok { + return creds, errors.AssertionFailedf("programming error: hash method is SCRAM but no stored credentials") + } + return creds, nil + }) + + handshake := scramServer.NewConversation() + initial := true for { if handshake.Done() { @@ -244,8 +254,18 @@ func authScram( // Receive a response from the client. resp, err := c.GetPwdData() if err != nil { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_PRE_HOOK_ERROR, err) + if pwRetrievalErr != nil { + return errors.CombineErrors(err, pwRetrievalErr) + } return err } + // Now process the password retrieval error, if any. + if pwRetrievalErr != nil { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_USER_RETRIEVAL_ERROR, pwRetrievalErr) + return pwRetrievalErr + } + var input []byte if initial { // Quoth postgres, backend/auth.go: @@ -258,6 +278,7 @@ func authScram( rb := pgwirebase.ReadBuffer{Msg: resp} reqMethod, err := rb.GetString() if err != nil { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_PRE_HOOK_ERROR, err) return err } if reqMethod != "SCRAM-SHA-256" { @@ -271,6 +292,7 @@ func authScram( } inputLen, err := rb.GetUint32() if err != nil { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_PRE_HOOK_ERROR, err) return err } // PostgreSQL ignores input from clients that pass -1 as length, @@ -278,6 +300,7 @@ func authScram( if inputLen < math.MaxUint32 { input, err = rb.GetBytes(int(inputLen)) if err != nil { + c.LogAuthFailed(ctx, eventpb.AuthFailReason_PRE_HOOK_ERROR, err) return err } } @@ -304,12 +327,11 @@ func authScram( } } - // Was the authentication for the right username? - // Did authentication succeed? if !handshake.Valid() { return security.NewErrPasswordUserAuthFailed(systemIdentity) } + return nil // auth success! }) return b, nil diff --git a/pkg/sql/pgwire/testdata/auth/scram b/pkg/sql/pgwire/testdata/auth/scram index 27bc88bb697f..1b7c6421df8a 100644 --- a/pkg/sql/pgwire/testdata/auth/scram +++ b/pkg/sql/pgwire/testdata/auth/scram @@ -3,7 +3,7 @@ config secure sql CREATE USER foo WITH PASSWORD 'abc'; -CREATE USER abc WITH PASSWORD 'abc'; +CREATE USER abc WITH PASSWORD 'SCRAM-SHA-256$4096:pAlYy62NTdETKb291V/Wow==$OXMAj9oD53QucEYVMBcdhRnjg2/S/iZY/88ShZnputA=:r8l4c1pk9bmDi+8an059l/nt9Bg1zb1ikkg+DeRv4UQ='; ---- ok @@ -82,15 +82,16 @@ connect user=foo password=abc ---- ERROR: password authentication failed for user foo (SQLSTATE 28000) -authlog 6 +authlog 7 .*client_connection_end ---- 15 {"EventType":"client_connection_start","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} 16 {"EventType":"client_authentication_info","Info":"HBA rule: host all foo all scram-sha-256","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl"} -17 {"EventType":"client_authentication_info","Info":"scram handshake error: no scram cookie for you","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl","User":"foo"} -18 {"Detail":"password authentication failed for user foo","EventType":"client_authentication_failed","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","Reason":6,"RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl","User":"foo"} -19 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} -20 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +17 {"EventType":"client_authentication_info","Info":"user password hash not in SCRAM format","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl","User":"foo"} +18 {"EventType":"client_authentication_info","Info":"scram handshake error: user password hash not in SCRAM format","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl","User":"foo"} +19 {"Detail":"password authentication failed for user foo","EventType":"client_authentication_failed","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","Reason":6,"RemoteAddress":"XXX","SystemIdentity":"foo","Timestamp":"XXX","Transport":"hostssl","User":"foo"} +20 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +21 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} # User abc has SCRAM credentials, but 'mistake' is not its password. # Expect authn error. @@ -101,12 +102,12 @@ ERROR: password authentication failed for user abc (SQLSTATE 28000) authlog 6 .*client_connection_end ---- -21 {"EventType":"client_connection_start","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} -22 {"EventType":"client_authentication_info","Info":"HBA rule: host all abc all scram-sha-256","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl"} -23 {"EventType":"client_authentication_info","Info":"scram handshake error: challenge proof invalid","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} -24 {"Detail":"password authentication failed for user abc","EventType":"client_authentication_failed","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","Reason":6,"RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} -25 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} -26 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +22 {"EventType":"client_connection_start","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +23 {"EventType":"client_authentication_info","Info":"HBA rule: host all abc all scram-sha-256","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl"} +24 {"EventType":"client_authentication_info","Info":"scram handshake error: challenge proof invalid","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} +25 {"Detail":"password authentication failed for user abc","EventType":"client_authentication_failed","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","Reason":6,"RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} +26 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +27 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} connect user=abc password=abc @@ -116,11 +117,11 @@ ok defaultdb authlog 5 .*client_connection_end ---- -27 {"EventType":"client_connection_start","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} -28 {"EventType":"client_authentication_info","Info":"HBA rule: host all abc all scram-sha-256","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl"} -29 {"EventType":"client_authentication_ok","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} -30 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} -31 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +28 {"EventType":"client_connection_start","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +29 {"EventType":"client_authentication_info","Info":"HBA rule: host all abc all scram-sha-256","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl"} +30 {"EventType":"client_authentication_ok","InstanceID":1,"Method":"scram-sha-256","Network":"tcp","RemoteAddress":"XXX","SystemIdentity":"abc","Timestamp":"XXX","Transport":"hostssl","User":"abc"} +31 {"Duration":"NNN","EventType":"client_session_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} +32 {"Duration":"NNN","EventType":"client_connection_end","InstanceID":1,"Network":"tcp","RemoteAddress":"XXX","Timestamp":"XXX"} subtest end