-
Notifications
You must be signed in to change notification settings - Fork 0
Intraprocess credential caching #21
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
base: v1.17.0+dbt
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -339,7 +339,7 @@ func getHeaders() map[string]string { | |||||
| // Used to authenticate the user with Snowflake. | ||||||
| func authenticate( | ||||||
| ctx context.Context, | ||||||
| lease *Lease, | ||||||
| optionalLease *Lease, | ||||||
| sc *snowflakeConn, | ||||||
| samlResponse []byte, | ||||||
| proofKey []byte, | ||||||
|
|
@@ -403,7 +403,7 @@ func authenticate( | |||||
| sessionParameters[clientStoreTemporaryCredential] = true | ||||||
| } | ||||||
| bodyCreator := func() ([]byte, error) { | ||||||
| return createRequestBody(sc, lease, sessionParameters, clientEnvironment, proofKey, samlResponse) | ||||||
| return createRequestBody(sc, optionalLease, sessionParameters, clientEnvironment, proofKey, samlResponse) | ||||||
| } | ||||||
|
|
||||||
| params := &url.Values{} | ||||||
|
|
@@ -431,13 +431,13 @@ func authenticate( | |||||
| logger.WithContext(ctx).Errorln("Authentication FAILED") | ||||||
| sc.rest.TokenAccessor.SetTokens("", "", -1) | ||||||
| if sessionParameters[clientRequestMfaToken] == true { | ||||||
| credentialsStorage.deleteCredential(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(optionalLease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| } | ||||||
| if sessionParameters[clientStoreTemporaryCredential] == true && sc.cfg.Authenticator == AuthTypeExternalBrowser { | ||||||
| credentialsStorage.deleteCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(optionalLease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| } | ||||||
| if sessionParameters[clientStoreTemporaryCredential] == true && sc.cfg.Authenticator.isOauthNativeFlow() { | ||||||
| credentialsStorage.deleteCredential(lease, newOAuthAccessTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(optionalLease, newOAuthAccessTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| } | ||||||
| code, err := strconv.Atoi(respd.Code) | ||||||
| if err != nil { | ||||||
|
|
@@ -454,15 +454,15 @@ func authenticate( | |||||
|
|
||||||
| if sessionParameters[clientRequestMfaToken] == true && sc.cfg.Authenticator == AuthTypeUsernamePasswordMFA { | ||||||
| token := respd.Data.MfaToken | ||||||
| credentialsStorage.setCredential(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User), token) | ||||||
| _ = credentialsStorage.setCredential(optionalLease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User), token) | ||||||
| } | ||||||
|
|
||||||
| if sessionParameters[clientStoreTemporaryCredential] == true && sc.cfg.Authenticator == AuthTypeExternalBrowser { | ||||||
| token := respd.Data.IDToken | ||||||
| // XXX: for some reason, token is empty here some times and we | ||||||
| // don't want to clear the cache, so let's skip it if it's empty | ||||||
| if token != "" { | ||||||
| credentialsStorage.setCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User), token) | ||||||
| _ = credentialsStorage.setCredential(optionalLease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User), token) | ||||||
| } | ||||||
| } | ||||||
| return &respd.Data, nil | ||||||
|
|
@@ -634,7 +634,7 @@ func authenticateByAuthorizationCode(sc *snowflakeConn, lease *Lease) (string, e | |||||
| valueAwaiter := valueAwaitHolder.get(lockKey) | ||||||
| defer valueAwaiter.resumeOne() | ||||||
| token, err := awaitValue(valueAwaiter, func() (string, error) { | ||||||
| return credentialsStorage.getCredential(lease, newOAuthAccessTokenSpec(oauthClient.tokenURL(), sc.cfg.User)) | ||||||
| return credentialsStorage.getCredentialRelaxed(lease, newOAuthAccessTokenSpec(oauthClient.tokenURL(), sc.cfg.User)) | ||||||
| }, func(s string, err error) bool { | ||||||
| return s != "" | ||||||
| }, func() string { | ||||||
|
|
@@ -734,6 +734,13 @@ func extBrowserBackoffKey(host, user string) string { | |||||
|
|
||||||
| // Authenticate with sc.cfg | ||||||
| func authenticateWithConfig(sc *snowflakeConn) error { | ||||||
| return doAuthenticateWithConfig(sc, nil) | ||||||
| } | ||||||
|
|
||||||
| // Perform authentication and consult caches | ||||||
| // First, attempt a relaxed read without a lease | ||||||
| // If this is unsuccessful, recurse once with a lease | ||||||
| func doAuthenticateWithConfig(sc *snowflakeConn, optionalLease *Lease) error { | ||||||
| var authData *authResponseMain | ||||||
| var samlResponse []byte | ||||||
| var proofKey []byte | ||||||
|
|
@@ -742,11 +749,16 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| mfaTokenLockKey := newMfaTokenLockKey(sc.cfg.Host, sc.cfg.User) | ||||||
| idTokenLockKey := newIDTokenLockKey(sc.cfg.Host, sc.cfg.User) | ||||||
|
|
||||||
| lease, err := credentialsStorage.acquireLease() | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| defer lease.Release() | ||||||
| // Lease for coordination - may be provided or acquired locally | ||||||
| var lease *Lease = optionalLease | ||||||
| leaseAcquiredLocally := false | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The lease being |
||||||
|
|
||||||
| // Release lease at the end if we acquired it locally | ||||||
| defer func() { | ||||||
| if leaseAcquiredLocally && lease != nil { | ||||||
| lease.Release() | ||||||
| } | ||||||
| }() | ||||||
|
|
||||||
| key := extBrowserBackoffKey(sc.cfg.Host, sc.cfg.User) | ||||||
|
|
||||||
|
|
@@ -762,7 +774,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| sc.cfg.IDToken, _ = awaitValue( | ||||||
| valueAwaiter, | ||||||
| func() (string, error) { | ||||||
| tok, _ := credentialsStorage.getCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| tok, _ := credentialsStorage.getCredentialRelaxed(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| return tok, nil | ||||||
| }, | ||||||
| func(s string, err error) bool { | ||||||
|
|
@@ -774,7 +786,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| ) | ||||||
|
|
||||||
| } else if sc.cfg.ClientStoreTemporaryCredential == ConfigBoolTrue { | ||||||
| tok, _ := credentialsStorage.getCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| tok, _ := credentialsStorage.getCredentialRelaxed(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| sc.cfg.IDToken = tok | ||||||
| } | ||||||
| // Disable console login by default | ||||||
|
|
@@ -793,7 +805,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| sc.cfg.MfaToken, _ = awaitValue( | ||||||
| valueAwaiter, | ||||||
| func() (string, error) { | ||||||
| tok, err := credentialsStorage.getCredential(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| tok, err := credentialsStorage.getCredentialRelaxed(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| if err != nil { | ||||||
| logger.WithContext(sc.ctx).Warnf("failed to get MFA token from credential storage: %v", err) | ||||||
| } | ||||||
|
|
@@ -805,7 +817,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| }, | ||||||
| ) | ||||||
| } else if sc.cfg.ClientRequestMfaToken == ConfigBoolTrue { | ||||||
| tok, err := credentialsStorage.getCredential(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| tok, err := credentialsStorage.getCredentialRelaxed(lease, newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| if err != nil { | ||||||
| logger.WithContext(sc.ctx).Warnf("failed to get MFA token from credential storage: %v", err) | ||||||
| } | ||||||
|
|
@@ -829,6 +841,18 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| lastFail.Delete(key) | ||||||
| } | ||||||
|
|
||||||
| // External browser path requires a lease for renewals | ||||||
| // When using parallel login coordination, we need the lease but cannot recurse | ||||||
| // due to the valueAwaiter | ||||||
| if lease == nil { | ||||||
| lease, err = credentialsStorage.acquireLease() | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You acquire a lease here and proceed with the authentication process, but since your decision to proceed with auth comes from a relaxed read, you can't be sure the credentials cache is still empty. Between the relaxed read and this lease acquisition, some other process might have populated the credentials cache. That's why the double-checked locking pattern requires two checks: one relaxed check that is fast and then another check after the lease/lock acquisition. |
||||||
| if err != nil { | ||||||
| sc.cleanup() | ||||||
| return err | ||||||
| } | ||||||
| leaseAcquiredLocally = true | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| } | ||||||
|
|
||||||
| samlResponse, proofKey, err = authenticateByExternalBrowser( | ||||||
| sc.ctx, | ||||||
| lease, | ||||||
|
|
@@ -845,6 +869,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| authData, err = authenticate( | ||||||
| sc.ctx, | ||||||
| lease, | ||||||
|
|
@@ -862,7 +887,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| switch { | ||||||
| // Case 1: cached ID token failed -> clear + try one interactive refresh | ||||||
| case sc.cfg.Authenticator == AuthTypeExternalBrowser && sc.cfg.IDToken != "": | ||||||
| credentialsStorage.deleteCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(lease, newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't delete in the filesystem if the lease is nil. As an invariant, no mutation should derive from a relaxed read of the cached credentials. |
||||||
| sc.cfg.IDToken = "" | ||||||
|
|
||||||
| // NOTE on tabstorms: | ||||||
|
|
@@ -874,6 +899,15 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| // considered low probability and low impact | ||||||
|
|
||||||
| // reenter interactive flow | ||||||
| if lease == nil { | ||||||
| lease, err = credentialsStorage.acquireLease() | ||||||
| if err != nil { | ||||||
| // let SAML-phase failure remain retryable; do not set backoff | ||||||
| sc.cleanup() | ||||||
| return err | ||||||
| } | ||||||
| leaseAcquiredLocally = true | ||||||
| } | ||||||
| samlResponse, proofKey, err = authenticateByExternalBrowser( | ||||||
| sc.ctx, lease, sc.rest, sc.cfg.Authenticator.String(), | ||||||
| sc.cfg.Application, sc.cfg.Account, sc.cfg.User, | ||||||
|
|
@@ -891,9 +925,9 @@ func authenticateWithConfig(sc *snowflakeConn) error { | |||||
| } | ||||||
| fallthrough | ||||||
|
|
||||||
| // Case 2: still failing, but could be an OAuth refreshable error | ||||||
| // Case 2: still failing, but could be an OAuth refreshable error | ||||||
| case errors.As(err, &se) && slices.Contains(refreshOAuthTokenErrorCodes, strconv.Itoa(se.Number)): | ||||||
| credentialsStorage.deleteCredential(lease, newOAuthAccessTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(lease, newOAuthAccessTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
|
|
||||||
| if sc.cfg.Authenticator == AuthTypeOAuthAuthorizationCode { | ||||||
| doRefreshTokenWithLease(sc, lease) | ||||||
|
|
@@ -941,7 +975,7 @@ func doRefreshTokenWithLock(sc *snowflakeConn, lease *Lease) { | |||||
| if _, err = getValueWithLock(chooseLockerForAuth(sc.cfg), lockKey, func() (string, error) { | ||||||
| if err = oauthClient.refreshToken(lease); err != nil { | ||||||
| logger.Warnf("cannot refresh token. %v", err) | ||||||
| credentialsStorage.deleteCredential(lease, newOAuthRefreshTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(lease, newOAuthRefreshTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| return "", err | ||||||
| } | ||||||
| return "", nil | ||||||
|
|
@@ -957,7 +991,7 @@ func doRefreshTokenWithLease(sc *snowflakeConn, lease *Lease) { | |||||
| } else { | ||||||
| if rfErr := oauthClient.refreshToken(lease); rfErr != nil { | ||||||
| logger.Warnf("cannot refresh token. %v", rfErr) | ||||||
| credentialsStorage.deleteCredential(lease, newOAuthRefreshTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| _ = credentialsStorage.deleteCredential(lease, newOAuthRefreshTokenSpec(sc.cfg.OauthTokenRequestURL, sc.cfg.User)) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
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.
We can keep mutating
optionalLease. You have a variable namedleasethat is just as optional asoptionalLease