Browse Source

Fix STS temporary credentials to use ASIA prefix instead of AKIA (#8326)

Temporary credentials from STS AssumeRole were using "AKIA" prefix
(permanent IAM user credentials) instead of "ASIA" prefix (temporary
security credentials). This violates AWS conventions and may cause
compatibility issues with AWS SDKs that validate credential types.

Changes:
- Rename generateAccessKeyId to generateTemporaryAccessKeyId for clarity
- Update function to use ASIA prefix for temporary credentials
- Add unit tests to verify ASIA prefix format (weed/iam/sts/credential_prefix_test.go)
- Add integration test to verify ASIA prefix in S3 API (test/s3/iam/s3_sts_credential_prefix_test.go)
- Ensure AWS-compatible credential format (ASIA + 16 hex chars)

The credentials are already deterministic (SHA256-based from session ID)
and the SessionToken is correctly set to the JWT token, so this is just
a prefix fix to follow AWS standards.

Fixes #8312
pull/8329/head
Chris Lu 1 week ago
committed by GitHub
parent
commit
25ea48227f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 82
      test/s3/iam/s3_sts_credential_prefix_test.go
  2. 68
      weed/iam/sts/credential_prefix_test.go
  3. 9
      weed/iam/sts/token_utils.go

82
test/s3/iam/s3_sts_credential_prefix_test.go

@ -0,0 +1,82 @@
package iam
import (
"encoding/xml"
"io"
"net/http"
"net/url"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestSTSTemporaryCredentialPrefix verifies that STS temporary credentials use ASIA prefix
// This test ensures AWS compatibility - temporary credentials should use ASIA, not AKIA
func TestSTSTemporaryCredentialPrefix(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
if !isSTSEndpointRunning(t) {
t.Skip("SeaweedFS STS endpoint is not running at", TestSTSEndpoint)
}
// Use test credentials from environment or fall back to defaults
accessKey := os.Getenv("STS_TEST_ACCESS_KEY")
if accessKey == "" {
accessKey = "admin"
}
secretKey := os.Getenv("STS_TEST_SECRET_KEY")
if secretKey == "" {
secretKey = "admin"
}
t.Run("assume_role_returns_asia_prefix", func(t *testing.T) {
resp, err := callSTSAPIWithSigV4(t, url.Values{
"Action": {"AssumeRole"},
"Version": {"2011-06-15"},
"RoleArn": {"arn:aws:iam::role/admin"},
"RoleSessionName": {"asia-prefix-test"},
}, accessKey, secretKey)
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if resp.StatusCode != http.StatusOK {
t.Logf("Response status: %d, body: %s", resp.StatusCode, string(body))
t.Skip("AssumeRole not fully implemented yet")
}
var stsResp AssumeRoleTestResponse
err = xml.Unmarshal(body, &stsResp)
require.NoError(t, err, "Failed to parse response: %s", string(body))
creds := stsResp.Result.Credentials
require.NotEmpty(t, creds.AccessKeyId, "AccessKeyId should not be empty")
// Verify ASIA prefix for temporary credentials
assert.True(t, strings.HasPrefix(creds.AccessKeyId, "ASIA"),
"Temporary credentials must use ASIA prefix (not AKIA for permanent keys), got: %s", creds.AccessKeyId)
// Verify it's NOT using AKIA (permanent credentials)
assert.False(t, strings.HasPrefix(creds.AccessKeyId, "AKIA"),
"Temporary credentials must NOT use AKIA prefix (that's for permanent IAM keys), got: %s", creds.AccessKeyId)
// Verify format: ASIA + 16 hex characters = 20 chars total
assert.Equal(t, 20, len(creds.AccessKeyId),
"Access key ID should be 20 characters (ASIA + 16 hex chars), got: %s", creds.AccessKeyId)
t.Logf("✓ Temporary credentials correctly use ASIA prefix: %s", creds.AccessKeyId)
})
t.Run("assume_role_with_web_identity_returns_asia_prefix", func(t *testing.T) {
// This test would require OIDC setup, so we'll skip it for now
// but the same ASIA prefix validation should apply
t.Skip("AssumeRoleWithWebIdentity requires OIDC provider setup")
})
}

68
weed/iam/sts/credential_prefix_test.go

@ -0,0 +1,68 @@
package sts
import (
"encoding/base64"
"encoding/hex"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// TestTemporaryCredentialPrefix verifies that temporary credentials use ASIA prefix
// (not AKIA which is for permanent IAM user credentials)
func TestTemporaryCredentialPrefix(t *testing.T) {
sessionId := "test-session-for-prefix"
expiration := time.Now().Add(time.Hour)
credGen := NewCredentialGenerator()
cred, err := credGen.GenerateTemporaryCredentials(sessionId, expiration)
assert.NoError(t, err)
assert.NotNil(t, cred)
// Verify ASIA prefix for temporary credentials
assert.True(t, strings.HasPrefix(cred.AccessKeyId, "ASIA"),
"Temporary credentials must use ASIA prefix, got: %s", cred.AccessKeyId)
// Verify it's NOT using AKIA (permanent credentials)
assert.False(t, strings.HasPrefix(cred.AccessKeyId, "AKIA"),
"Temporary credentials must NOT use AKIA prefix (that's for permanent IAM keys)")
}
// TestTemporaryCredentialFormat verifies the full format of temporary credentials
func TestTemporaryCredentialFormat(t *testing.T) {
sessionId := "format-test-session"
expiration := time.Now().Add(time.Hour)
credGen := NewCredentialGenerator()
cred, err := credGen.GenerateTemporaryCredentials(sessionId, expiration)
assert.NoError(t, err)
assert.NotNil(t, cred)
// AWS temporary access key format: ASIA + 16 hex characters = 20 chars total
assert.Equal(t, 20, len(cred.AccessKeyId),
"Access key ID should be 20 characters (ASIA + 16 hex chars)")
// Verify it starts with ASIA
assert.True(t, strings.HasPrefix(cred.AccessKeyId, "ASIA"),
"Access key must start with ASIA prefix")
// Verify the rest is hex (after ASIA prefix)
hexPart := cred.AccessKeyId[4:]
assert.Equal(t, 16, len(hexPart), "Hex part should be 16 characters")
_, err = hex.DecodeString(hexPart)
assert.NoError(t, err, "The part after ASIA prefix should be valid hex")
// Verify secret key is not empty and is a valid base64-encoded SHA256 hash
assert.NotEmpty(t, cred.SecretAccessKey)
assert.Equal(t, 44, len(cred.SecretAccessKey),
"SecretAccessKey should be 44 characters for a base64-encoded 32-byte hash")
_, err = base64.StdEncoding.DecodeString(cred.SecretAccessKey)
assert.NoError(t, err, "SecretAccessKey should be a valid base64 string")
// Verify session token is not empty
assert.NotEmpty(t, cred.SessionToken)
}

9
weed/iam/sts/token_utils.go

@ -170,7 +170,7 @@ func NewCredentialGenerator() *CredentialGenerator {
// GenerateTemporaryCredentials creates temporary AWS credentials // GenerateTemporaryCredentials creates temporary AWS credentials
func (c *CredentialGenerator) GenerateTemporaryCredentials(sessionId string, expiration time.Time) (*Credentials, error) { func (c *CredentialGenerator) GenerateTemporaryCredentials(sessionId string, expiration time.Time) (*Credentials, error) {
accessKeyId, err := c.generateAccessKeyId(sessionId)
accessKeyId, err := c.generateTemporaryAccessKeyId(sessionId)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate access key ID: %w", err) return nil, fmt.Errorf("failed to generate access key ID: %w", err)
} }
@ -193,11 +193,12 @@ func (c *CredentialGenerator) GenerateTemporaryCredentials(sessionId string, exp
}, nil }, nil
} }
// generateAccessKeyId generates an AWS-style access key ID
func (c *CredentialGenerator) generateAccessKeyId(sessionId string) (string, error) {
// generateTemporaryAccessKeyId generates an AWS-style access key ID for temporary STS credentials
func (c *CredentialGenerator) generateTemporaryAccessKeyId(sessionId string) (string, error) {
// Create a deterministic but unique access key ID based on session // Create a deterministic but unique access key ID based on session
hash := sha256.Sum256([]byte("access-key:" + sessionId)) hash := sha256.Sum256([]byte("access-key:" + sessionId))
return "AKIA" + hex.EncodeToString(hash[:8]), nil // AWS format: AKIA + 16 chars
// Use ASIA prefix for temporary credentials (STS), not AKIA (permanent IAM keys)
return "ASIA" + hex.EncodeToString(hash[:8]), nil // AWS format: ASIA + 16 chars
} }
// generateSecretAccessKey generates a deterministic secret access key based on sessionId // generateSecretAccessKey generates a deterministic secret access key based on sessionId

Loading…
Cancel
Save