Files
seaweedfs/weed/iam/sts/credential_prefix_test.go
T
Chris Lu f40687b34e s3: tighten STS session token handling (#11383)
* s3api: test that a session token must not reveal its credential

* sts: derive secret access key with HMAC keyed on the signing key

* s3api: stop accepting STS session tokens as bearer credentials

* security: reject STS session tokens on filer and admin gRPC auth

* test: sign s3/iam framework requests with the session credential

* s3api: exercise the real auth pipeline in the end-to-end harness
2026-09-17 23:50:16 -07:00

101 lines
3.4 KiB
Go

package sts
import (
"context"
"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 := testCredGen
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 := testCredGen
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)
}
// A duration that came from configuration is bounded the same as one from a
// request: the catalog passes -s3.iceberg.credentialDurationSeconds straight
// through.
func TestAssumeRoleForPrincipalValidatesDuration(t *testing.T) {
svc := NewSTSService()
err := svc.Initialize(&STSConfig{
TokenDuration: FlexibleDuration{time.Hour},
MaxSessionLength: FlexibleDuration{12 * time.Hour},
Issuer: "test-issuer",
SigningKey: []byte("test-signing-key-at-least-32-bytes-long"),
})
if err != nil {
t.Fatalf("Initialize() error = %v", err)
}
for _, seconds := range []int64{60, 100000} {
duration := seconds
_, err := svc.AssumeRoleForPrincipal(context.Background(), &AssumeRoleForPrincipalRequest{
RoleArn: "arn:aws:iam::role/IcebergTableAccess",
Principal: "admin",
DurationSeconds: &duration,
})
if err == nil {
t.Fatalf("AssumeRoleForPrincipal(%ds) error = nil, want a validation error", seconds)
}
if !strings.Contains(err.Error(), "DurationSeconds") {
t.Errorf("AssumeRoleForPrincipal(%ds) error = %v, want it to name DurationSeconds", seconds, err)
}
}
}