From 1dc94e92f6f7d7d1e5ce5f4ec84f0c268c36f424 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 2 Jan 2026 21:13:11 -0800 Subject: [PATCH] fix(sts): make secret access key deterministic based on sessionId CRITICAL FIX: The secret access key was being randomly generated, causing signature verification failures when the same session token was used twice: 1. AssumeRoleWithWebIdentity generates random secret key X 2. Client signs request using secret key X 3. Server validates token, regenerates credentials via ToSessionInfo() 4. ToSessionInfo() calls generateSecretAccessKey(), which generates random key Y 5. Server tries to verify signature using key Y, but signature was made with X 6. Signature verification fails (SignatureDoesNotMatch) Solution: Make generateSecretAccessKey() deterministic by using SHA256 hash of 'secret-key:' + sessionId, just like generateAccessKeyId() already does. This ensures: - AssumeRoleWithWebIdentity generates deterministic secret key from sessionId - ToSessionInfo() regenerates the same secret key from the same sessionId - Client signature verification succeeds because keys match Fixes: AWS SDK v2 CORS tests failing with 'ExpiredToken' errors Affected files: - weed/iam/sts/token_utils.go: Updated generateSecretAccessKey() signature and implementation to be deterministic - Updated GenerateTemporaryCredentials() to pass sessionId parameter Tests: All 54 STS tests pass with this fix --- weed/iam/sts/token_utils.go | 22 +++++++++++----------- weed/s3api/auth_signature_v4.go | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/weed/iam/sts/token_utils.go b/weed/iam/sts/token_utils.go index 3091ac519..6ba7196e4 100644 --- a/weed/iam/sts/token_utils.go +++ b/weed/iam/sts/token_utils.go @@ -149,7 +149,7 @@ func (c *CredentialGenerator) GenerateTemporaryCredentials(sessionId string, exp return nil, fmt.Errorf("failed to generate access key ID: %w", err) } - secretAccessKey, err := c.generateSecretAccessKey() + secretAccessKey, err := c.generateSecretAccessKey(sessionId) if err != nil { return nil, fmt.Errorf("failed to generate secret access key: %w", err) } @@ -174,16 +174,16 @@ func (c *CredentialGenerator) generateAccessKeyId(sessionId string) (string, err return "AKIA" + hex.EncodeToString(hash[:8]), nil // AWS format: AKIA + 16 chars } -// generateSecretAccessKey generates a random secret access key -func (c *CredentialGenerator) generateSecretAccessKey() (string, error) { - // Generate 32 random bytes for secret key - secretBytes := make([]byte, 32) - _, err := rand.Read(secretBytes) - if err != nil { - return "", err - } - - return base64.StdEncoding.EncodeToString(secretBytes), nil +// generateSecretAccessKey generates a deterministic secret access key based on sessionId +// This ensures the same secret key is regenerated from the JWT claims during signature verification +func (c *CredentialGenerator) generateSecretAccessKey(sessionId string) (string, error) { + // Create deterministic secret key based on session ID (not random!) + // This is critical for STS because: + // 1. AssumeRoleWithWebIdentity generates the secret key once + // 2. During signature verification, ToSessionInfo() regenerates credentials from JWT + // 3. Both must generate the same secret key for signature verification to succeed + hash := sha256.Sum256([]byte("secret-key:" + sessionId)) + return base64.StdEncoding.EncodeToString(hash[:]), nil } // generateSessionTokenId generates a session token identifier diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index 32121511f..36c070e7e 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -233,7 +233,8 @@ func (iam *IdentityAccessManagement) verifyV4Signature(r *http.Request, shouldCh glog.Warningf("InvalidAccessKeyId: attempted key '%s' not found. Available keys: %d, Auth enabled: %v", authInfo.AccessKey, len(availableKeys), iam.isAuthEnabled) - return nil, nil, "", nil, s3err.ErrInvalidAccessKeyID } + return nil, nil, "", nil, s3err.ErrInvalidAccessKeyID + } // Check service account expiration if cred.isCredentialExpired() {