s3api: tolerate session tokens on statically configured credentials (#11694)

* s3api: consolidate session token extraction into extractSessionToken

Three call sites duplicated the same header/header/query lookup; share
one helper named after the existing s3tables equivalent.

* s3api: tolerate session tokens on statically configured credentials

Credential vendors like Unity Catalog emit a session token with every vended credential, including static ones (UC's StaticAwsCredentialGenerator only engages when s3.sessionToken is set). Requests signed by a configured access key were routed to STS validation and rejected, so static credential vending never worked against SeaweedFS.

Resolve the access key first: when it maps to a configured credential the signature alone authenticates the request, and the attached token is marked ignored so authorization does not route it into the STS session-policy path. STS-issued access keys are never in the static map, so temporary credentials still validate their token exactly as before.

* s3api: cover static credentials carrying a session token

* test: exercise UC static credential vending against SeaweedFS

s3.sessionToken.0 selects UC's StaticAwsCredentialGenerator, which vends the configured keys verbatim. The vended session token is foreign to SeaweedFS and previously failed SigV4; now it round-trips through temporary-table-credentials into real S3 I/O.

* s3api: reject temporary credentials in GetFederationToken after auth

Token presence alone cannot distinguish a temporary credential from a
statically configured one carrying a vended token. Move the check behind
verifyV4Signature and key it on the operative session token so tolerated
tokens keep the caller eligible.

* s3api: test GetFederationToken with authenticated temporary credentials

Sign the rejection cases with real session credentials so they reach the
post-auth check, and cover a vended static credential being accepted.

* test: check vended-credential delete error in UC integration test
This commit is contained in:
Chris Lu authored and GitHub committed 2026-10-10 10:11:24 +08:00
1 parent 924a683397
commit c023493cf0
9 files changed
+297 -109

No files matched your search

@@ -26,6 +26,10 @@ const (
ucAPIBase = "/api/2.1/unity-catalog"
ucWarehouse = "lakehouse"
ucWarehouseKey = "warehouse"
// A non-empty s3.sessionToken.N puts UC's credential vendor on its
// StaticAwsCredentialGenerator path: it vends the configured access key,
// secret key and this token verbatim instead of calling AWS STS.
ucVendedSessionToken = "unity-catalog-vended-session-token"
// Role used by the master-role STS-vended variant of the test. The trust
// policy is wide open so any caller can assume it; in production UC
@@ -65,6 +69,9 @@ type ucServerOpts struct {
// MasterRoleArn populates aws.masterRoleArn. Empty means UC falls back to
// static aws.accessKey / aws.secretKey for storage operations.
MasterRoleArn string
// SessionToken populates s3.sessionToken.0, selecting UC's static
// credential generator for the per-bucket config.
SessionToken string
// ExtraEnv adds environment variables to the UC container, useful for
// AWS_ENDPOINT_URL_STS-style overrides.
ExtraEnv map[string]string
@@ -220,6 +227,7 @@ func (env *testEnv) startUnityCatalog(t *testing.T, ctx context.Context, opts uc
fmt.Sprintf("s3.awsRoleArn.0=%s", opts.MasterRoleArn),
fmt.Sprintf("s3.accessKey.0=%s", env.accessKey),
fmt.Sprintf("s3.secretKey.0=%s", env.secretKey),
fmt.Sprintf("s3.sessionToken.0=%s", opts.SessionToken),
fmt.Sprintf("s3.endpoint.0=%s", s3EndpointForContainer),
"",
}, "\n")
@@ -8,6 +8,7 @@ package unity_catalog
import (
"context"
"fmt"
"strings"
"testing"
"time"
@@ -46,7 +47,7 @@ func TestUnityCatalogDeltaIntegration(t *testing.T) {
}
t.Log(">>> starting Unity Catalog server (static keys)...")
env.startUnityCatalog(t, ctx, ucServerOpts{})
env.startUnityCatalog(t, ctx, ucServerOpts{SessionToken: ucVendedSessionToken})
t.Log(">>> Unity Catalog ready")
uc := newUCClient(fmt.Sprintf("http://127.0.0.1:%d", env.ucHostPort))
@@ -145,28 +146,41 @@ func TestUnityCatalogDeltaIntegration(t *testing.T) {
}
})
t.Run("TemporaryTableCredentialsRejected", func(t *testing.T) {
// With aws.masterRoleArn empty AND no s3.sessionToken.0 set, UC OSS
// always tries to AssumeRole via its internal StsClient (see
// AwsCredentialVendor.createPerBucketCredentialGenerator). Against a
// non-AWS endpoint, that call doesn't reach a real STS, so UC returns
// "S3 bucket configuration not found." or an STS-side error. This is
// the gap users hit at <https://github.com/data-engineering-helpers/mds-in-a-box/blob/main/unitycatalog-playground/etc/conf/server.properties#L45>:
// "with simple S3 access and secret keys, Unity Catalog does not seem
// to work."
//
// The assertion is therefore inverted: we expect a non-nil error from
// /temporary-table-credentials with this configuration. A future
// variant can pin s3.sessionToken.0 (UC's StaticAwsCredentialGenerator
// path) once SeaweedFS' SigV4 path tolerates the vended session token.
t.Run("TemporaryTableCredentials", func(t *testing.T) {
// s3.sessionToken.0 is set, so UC's AwsCredentialVendor takes the
// StaticAwsCredentialGenerator path and vends the configured keys
// verbatim, token included — no STS call at all. SeaweedFS authenticates
// the request on the static access key and tolerates the foreign token.
if createdTable.TableID == "" {
t.Fatalf("created table has empty table_id; cannot request temporary credentials")
}
_, err := uc.generateTemporaryTableCredentials(ctx, createdTable.TableID, "READ_WRITE")
if err == nil {
t.Fatalf("expected /temporary-table-credentials to fail with the static-key playground configuration; it succeeded unexpectedly")
creds, err := uc.generateTemporaryTableCredentials(ctx, createdTable.TableID, "READ_WRITE")
if err != nil {
t.Fatalf("temporary-table-credentials: %v", err)
}
awsCreds := creds.AwsTempCredentials
if awsCreds == nil || awsCreds.AccessKeyID == "" || awsCreds.SessionToken == "" {
t.Fatalf("expected aws_temp_credentials with a session_token, got %+v", creds)
}
if awsCreds.AccessKeyID != env.accessKey || awsCreds.SessionToken != ucVendedSessionToken {
t.Fatalf("expected UC to vend the configured static credential, got %+v", awsCreds)
}
s3v := env.newHostS3ClientWithCreds(t, ctx, awsCreds.AccessKeyID, awsCreds.SecretAccessKey, awsCreds.SessionToken)
probeKey := fmt.Sprintf("%s/%s/%s/vended.txt", ucWarehouseKey, schemaName, tableName)
if _, err := s3v.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(ucWarehouse),
Key: aws.String(probeKey),
Body: strings.NewReader("ok"),
}); err != nil {
t.Fatalf("PutObject with UC-vended credentials: %v", err)
}
if _, err := s3v.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(ucWarehouse),
Key: aws.String(probeKey),
}); err != nil {
t.Fatalf("DeleteObject with UC-vended credentials: %v", err)
}
t.Logf("expected failure (UC static-key path requires AWS STS): %v", err)
})
t.Run("DeleteTableSchemaCatalog", func(t *testing.T) {
+21 -24
View File
@@ -2674,13 +2674,7 @@ func (iam *IdentityAccessManagement) isActionExplicitlyDeniedByIAM(r *http.Reque
// A chained caller authenticates with an STS session token whose inline
// session policy can also carry an explicit deny.
sessionToken := r.Header.Get(s3_constants.SeaweedFSSessionTokenHeader)
if sessionToken == "" {
sessionToken = r.Header.Get("X-Amz-Security-Token")
if sessionToken == "" {
sessionToken = r.URL.Query().Get("X-Amz-Security-Token")
}
}
sessionToken := extractSessionToken(r)
if len(policyNames) == 0 && sessionToken == "" {
return false
@@ -2748,12 +2742,26 @@ func (iam *IdentityAccessManagement) attachedPolicyNames(identity *Identity) []s
return names
}
// extractSessionToken returns the request's session token, whichever transport
// carried it: the internal header set after JWT authentication, the
// X-Amz-Security-Token header, or the presigned-URL query parameter.
func extractSessionToken(r *http.Request) string {
if s3_constants.IsSessionTokenIgnored(r.Context()) {
return ""
}
if token := r.Header.Get(s3_constants.SeaweedFSSessionTokenHeader); token != "" {
return token
}
if token := r.Header.Get("X-Amz-Security-Token"); token != "" {
return token
}
return r.URL.Query().Get("X-Amz-Security-Token")
}
// hasSessionToken reports whether the request carries an STS session token,
// whose session policies are known only to the IAM integration.
func hasSessionToken(r *http.Request) bool {
return r.Header.Get(s3_constants.SeaweedFSSessionTokenHeader) != "" ||
r.Header.Get("X-Amz-Security-Token") != "" ||
r.URL.Query().Get("X-Amz-Security-Token") != ""
return extractSessionToken(r) != ""
}
// authorizationRoute picks the mechanism, so every caller routes identically.
@@ -3021,22 +3029,11 @@ func (iam *IdentityAccessManagement) authorizeWithIAM(r *http.Request, identity
iam.primeBucketForIAM(bucket)
}
// Get session info from request headers
// First check for JWT-based authentication headers (SeaweedFSSessionTokenHeader)
sessionToken := r.Header.Get(s3_constants.SeaweedFSSessionTokenHeader)
// JWT authentication records its token in SeaweedFSSessionTokenHeader;
// SigV4 requests carry it as X-Amz-Security-Token.
sessionToken := extractSessionToken(r)
principal := r.Header.Get(s3_constants.SeaweedFSPrincipalHeader)
// Fallback to AWS Signature V4 STS token if JWT token not present
// This handles the case where STS AssumeRoleWithWebIdentity generates temporary credentials
// that include an X-Amz-Security-Token header (in addition to the access key and secret)
if sessionToken == "" {
sessionToken = r.Header.Get("X-Amz-Security-Token")
if sessionToken == "" {
// Also check query parameters for presigned URLs with STS tokens
sessionToken = r.URL.Query().Get("X-Amz-Security-Token")
}
}
policyNames := iam.attachedPolicyNames(identity)
iamIdentity := &IAMIdentity{
+28 -26
View File
@@ -265,38 +265,40 @@ func (iam *IdentityAccessManagement) verifyV4Signature(r *http.Request, shouldCh
var cred *Credential
// 2. Check for STS session token
sessionToken := r.Header.Get("X-Amz-Security-Token")
if sessionToken == "" {
sessionToken = r.URL.Query().Get("X-Amz-Security-Token")
}
if sessionToken != "" {
// Validate STS session token
identity, cred, errCode = iam.validateSTSSessionToken(r, sessionToken, authInfo.AccessKey)
if errCode != s3err.ErrNone {
return nil, nil, "", nil, errCode
}
} else {
// 3. Lookup user and credentials
var found bool
identity, cred, found = iam.lookupByAccessKey(authInfo.AccessKey)
if !found {
// Log detailed error information for InvalidAccessKeyId (avoid slice allocation for performance)
iam.m.RLock()
keyCount := len(iam.accessKeyIdent)
iam.m.RUnlock()
glog.Warningf("InvalidAccessKeyId: attempted key '%s' not found. Available keys: %d, Auth enabled: %v",
authInfo.AccessKey, keyCount, iam.isAuthEnabled)
return nil, nil, "", nil, s3err.ErrInvalidAccessKeyID
}
// 2. Resolve the credential. A configured access key wins over a session
// token: credential vendors like Unity Catalog emit a token with static
// credentials too, and it adds nothing the signature does not prove.
// STS-issued access keys are never registered in the static map, so they
// still land on session-token validation.
sessionToken := extractSessionToken(r)
var found bool
identity, cred, found = iam.lookupByAccessKey(authInfo.AccessKey)
switch {
case found:
// Check service account expiration
if cred.isCredentialExpired() {
glog.V(2).Infof("Service account credential %s has expired (expiration: %d, now: %d)",
authInfo.AccessKey, cred.Expiration, time.Now().Unix())
return nil, nil, "", nil, s3err.ErrAccessDenied
}
if sessionToken != "" {
*r = *r.WithContext(s3_constants.IgnoreSessionTokenInContext(r.Context()))
}
case sessionToken != "":
// Validate STS session token
identity, cred, errCode = iam.validateSTSSessionToken(r, sessionToken, authInfo.AccessKey)
if errCode != s3err.ErrNone {
return nil, nil, "", nil, errCode
}
default:
// Log detailed error information for InvalidAccessKeyId (avoid slice allocation for performance)
iam.m.RLock()
keyCount := len(iam.accessKeyIdent)
iam.m.RUnlock()
glog.Warningf("InvalidAccessKeyId: attempted key '%s' not found. Available keys: %d, Auth enabled: %v",
authInfo.AccessKey, keyCount, iam.isAuthEnabled)
return nil, nil, "", nil, s3err.ErrInvalidAccessKeyID
}
// 3. Perform permission check
@@ -0,0 +1,121 @@
package s3api
import (
"net/http"
"sync"
"testing"
"time"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
// Credential vendors like Unity Catalog emit a session token even for static
// credentials. A request signed by a configured access key must authenticate
// as that identity, tolerating the foreign token.
func TestStaticCredentialWithSessionToken(t *testing.T) {
const token = "vended-static-session-token"
newSigned := func(t *testing.T) *http.Request {
req := mustNewRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/object", 0, nil, t)
req.Header.Set("X-Amz-Security-Token", token)
require.NoError(t, signRequestV4(req, "access_key_1", "secret_key_1"))
return mux.SetURLVars(req, map[string]string{"bucket": "bucket", "object": "object"})
}
t.Run("authenticates as the static identity", func(t *testing.T) {
iam := newTestIAMWithCreds()
r := newSigned(t)
identity, errCode := iam.reqSignatureV4Verify(r)
require.Equal(t, s3err.ErrNone, errCode)
assert.Equal(t, "someone", identity.Name)
assert.True(t, s3_constants.IsSessionTokenIgnored(r.Context()))
assert.False(t, hasSessionToken(r), "tolerated token must be inert for authorization")
})
t.Run("authorized through the static action path", func(t *testing.T) {
iam := newTestIAMWithCreds()
r := newSigned(t)
_, errCode := iam.authRequest(r, s3_constants.ACTION_READ)
assert.Equal(t, s3err.ErrNone, errCode)
})
t.Run("repeat verification stays idempotent", func(t *testing.T) {
iam := newTestIAMWithCreds()
r := newSigned(t)
_, errCode := iam.reqSignatureV4Verify(r)
require.Equal(t, s3err.ErrNone, errCode)
// Handlers re-authenticate the same request (e.g. PutObjectAcl).
_, errCode = iam.reqSignatureV4Verify(r)
assert.Equal(t, s3err.ErrNone, errCode)
})
t.Run("unsigned token is still rejected", func(t *testing.T) {
iam := newTestIAMWithCreds()
r := mustNewSignedRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/object", 0, nil, t)
r.Header.Set("X-Amz-Security-Token", token)
_, errCode := iam.reqSignatureV4Verify(r)
assert.Equal(t, s3err.ErrSignatureDoesNotMatch, errCode)
})
t.Run("unknown access key with token still rejected", func(t *testing.T) {
iam := newTestIAMWithCreds()
req := mustNewRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/object", 0, nil, t)
req.Header.Set("X-Amz-Security-Token", token)
require.NoError(t, signRequestV4(req, "no_such_key", "secret_key_1"))
_, errCode := iam.reqSignatureV4Verify(req)
assert.Equal(t, s3err.ErrInvalidAccessKeyID, errCode)
})
t.Run("wrong secret with token still rejected", func(t *testing.T) {
iam := newTestIAMWithCreds()
req := mustNewRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/object", 0, nil, t)
req.Header.Set("X-Amz-Security-Token", token)
require.NoError(t, signRequestV4(req, "access_key_1", "wrong_secret"))
_, errCode := iam.reqSignatureV4Verify(req)
assert.Equal(t, s3err.ErrSignatureDoesNotMatch, errCode)
})
t.Run("presigned URL with token works", func(t *testing.T) {
iam := newTestIAMWithCreds()
req := mustNewRequest(http.MethodGet, "http://127.0.0.1:9000/bucket/object", 0, nil, t)
q := req.URL.Query()
q.Set("X-Amz-Security-Token", token)
req.URL.RawQuery = q.Encode()
require.NoError(t, preSignV4(iam, req, "access_key_1", "secret_key_1", int64((10*time.Minute).Seconds())))
_, _, errCode := iam.doesPresignedSignatureMatch(req)
require.Equal(t, s3err.ErrNone, errCode)
assert.True(t, s3_constants.IsSessionTokenIgnored(req.Context()))
})
}
func newTestIAMWithCreds() *IdentityAccessManagement {
iam := &IdentityAccessManagement{
hashes: make(map[string]*sync.Pool),
hashCounters: make(map[string]*int32),
}
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
Identities: []*iam_pb.Identity{
{
Name: "someone",
Credentials: []*iam_pb.Credential{
{AccessKey: "access_key_1", SecretKey: "secret_key_1"},
},
Actions: []string{"Admin", "Read", "Write"},
},
},
})
return iam
}
+22 -5
View File
@@ -322,11 +322,12 @@ func IsSeaweedFSInternalHeader(headerKey string) bool {
type contextKey string
const (
contextKeyIdentityName contextKey = "s3-identity-name"
contextKeyIdentityObject contextKey = "s3-identity-object"
contextKeyIdentityHolder contextKey = "s3-identity-holder"
contextKeyPrincipalArn contextKey = "s3-principal-arn"
contextKeyIdentityClaim contextKey = "s3-identity-claim"
contextKeyIdentityName contextKey = "s3-identity-name"
contextKeyIdentityObject contextKey = "s3-identity-object"
contextKeyIdentityHolder contextKey = "s3-identity-holder"
contextKeyPrincipalArn contextKey = "s3-principal-arn"
contextKeyIdentityClaim contextKey = "s3-identity-claim"
contextKeyIgnoredSessionToken contextKey = "s3-ignored-session-token"
)
// identityHolder is a mutable container for the authenticated identity name,
@@ -449,6 +450,22 @@ func GetIdentityClaimFromContext(r *http.Request) string {
return ""
}
// IgnoreSessionTokenInContext marks the request's session token as tolerated
// rather than authenticated: a statically configured access key signed the
// request, so the token (which credential vendors like Unity Catalog emit on
// static credentials too) must not steer authorization into the STS session
// path. A context value because headers can be spoofed; this cannot.
func IgnoreSessionTokenInContext(ctx context.Context) context.Context {
return context.WithValue(ctx, contextKeyIgnoredSessionToken, true)
}
// IsSessionTokenIgnored reports whether the request carries a session token
// that was tolerated for a statically configured access key.
func IsSessionTokenIgnored(ctx context.Context) bool {
ignored, _ := ctx.Value(contextKeyIgnoredSessionToken).(bool)
return ignored
}
// SetIdentityInContext stores the full authenticated identity object in the request context
// This is used to pass the full identity (including for JWT users) to handlers
func SetIdentityInContext(ctx context.Context, identity interface{}) context.Context {
+7 -13
View File
@@ -671,19 +671,6 @@ func (h *STSHandlers) handleGetFederationToken(w http.ResponseWriter, r *http.Re
return
}
// Reject calls from temporary credentials (session tokens) early,
// before SigV4 verification — no need to authenticate first.
// GetFederationToken can only be called by long-term IAM users.
securityToken := r.Header.Get("X-Amz-Security-Token")
if securityToken == "" {
securityToken = r.URL.Query().Get("X-Amz-Security-Token")
}
if securityToken != "" {
h.writeSTSErrorResponse(w, r, STSErrAccessDenied,
fmt.Errorf("GetFederationToken cannot be called with temporary credentials"))
return
}
// Check if STS service is initialized
if h.stsService == nil || !h.stsService.IsInitialized() {
h.writeSTSErrorResponse(w, r, STSErrSTSNotReady,
@@ -713,6 +700,13 @@ func (h *STSHandlers) handleGetFederationToken(w http.ResponseWriter, r *http.Re
return
}
// GetFederationToken can only be called by long-term IAM users.
if extractSessionToken(r) != "" {
h.writeSTSErrorResponse(w, r, STSErrAccessDenied,
fmt.Errorf("GetFederationToken cannot be called with temporary credentials"))
return
}
r = r.WithContext(recordIdentityInContext(r, identity))
glog.V(2).Infof("GetFederationToken: caller identity=%s, name=%s", identity.Name, name)
@@ -9,6 +9,7 @@ import (
"net/url"
"sort"
"strings"
"sync"
"testing"
"time"
@@ -129,34 +130,46 @@ func TestGetFederationToken_WithSessionPolicy(t *testing.T) {
assert.Equal(t, []string{"S3FullAccess"}, sessionInfo.Policies)
}
// TestGetFederationToken_RejectTemporaryCredentials tests that requests with
// session tokens are rejected.
// TestGetFederationToken_RejectTemporaryCredentials tests that requests
// authenticated with temporary credentials are rejected.
func TestGetFederationToken_RejectTemporaryCredentials(t *testing.T) {
stsService, _ := setupTestSTSService(t)
stsHandlers := NewSTSHandlers(stsService, &IdentityAccessManagement{
iamIntegration: &MockIAMIntegration{},
})
iam := &IdentityAccessManagement{
iamIntegration: &MockIAMIntegration{
validateSessionFunc: func(context.Context, string) (*sts.SessionInfo, error) {
return &sts.SessionInfo{
AssumedRoleUser: "role/user",
Principal: "arn:aws:sts:::assumed-role/role/user",
Credentials: &sts.Credentials{
AccessKeyId: "STSACCESSKEY",
SecretAccessKey: "sts-secret-key",
},
ExpiresAt: time.Now().Add(time.Hour),
}, nil
},
},
hashes: make(map[string]*sync.Pool),
hashCounters: make(map[string]*int32),
}
stsHandlers := NewSTSHandlers(stsService, iam)
tests := []struct {
name string
setToken func(r *http.Request)
description string
name string
setToken func(r *http.Request)
}{
{
name: "SessionTokenInHeader",
setToken: func(r *http.Request) {
r.Header.Set("X-Amz-Security-Token", "some-session-token")
r.Header.Set("X-Amz-Security-Token", "session-token")
},
description: "Session token in X-Amz-Security-Token header should be rejected",
},
{
name: "SessionTokenInQuery",
setToken: func(r *http.Request) {
q := r.URL.Query()
q.Set("X-Amz-Security-Token", "some-session-token")
q.Set("X-Amz-Security-Token", "session-token")
r.URL.RawQuery = q.Encode()
},
description: "Session token in query string should be rejected",
},
}
@@ -167,28 +180,47 @@ func TestGetFederationToken_RejectTemporaryCredentials(t *testing.T) {
form.Set("Name", "TestUser")
form.Set("Version", "2011-06-15")
req := httptest.NewRequest("POST", "/", strings.NewReader(form.Encode()))
req := mustNewRequest(http.MethodPost, "http://sts.amazonaws.com/",
int64(len(form.Encode())), strings.NewReader(form.Encode()), t)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
tt.setToken(req)
// Parse form so the handler can read it
require.NoError(t, req.ParseForm())
// Re-set values after parse
req.Form.Set("Action", "GetFederationToken")
req.Form.Set("Name", "TestUser")
req.Form.Set("Version", "2011-06-15")
require.NoError(t, signRequestV4(req, "STSACCESSKEY", "sts-secret-key"))
rr := httptest.NewRecorder()
stsHandlers.HandleSTSRequest(rr, req)
// The handler rejects temporary credentials before SigV4 verification
assert.Equal(t, http.StatusForbidden, rr.Code, tt.description)
assert.Equal(t, http.StatusForbidden, rr.Code)
assert.Contains(t, rr.Body.String(), "AccessDenied")
assert.Contains(t, rr.Body.String(), "cannot be called with temporary credentials")
})
}
}
// A session token attached to a statically configured credential (as credential
// vendors emit) must not disqualify the caller as temporary.
func TestGetFederationToken_VendedStaticCredential(t *testing.T) {
stsService, _ := setupTestSTSService(t)
iam := newTestIAMWithCreds()
stsHandlers := NewSTSHandlers(stsService, iam)
form := url.Values{}
form.Set("Action", "GetFederationToken")
form.Set("Name", "VendedApp")
form.Set("Version", "2011-06-15")
req := mustNewRequest(http.MethodPost, "http://sts.amazonaws.com/",
int64(len(form.Encode())), strings.NewReader(form.Encode()), t)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("X-Amz-Security-Token", "vendor-token")
require.NoError(t, signRequestV4(req, "access_key_1", "secret_key_1"))
rr := httptest.NewRecorder()
stsHandlers.HandleSTSRequest(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "GetFederationTokenResponse")
}
// TestGetFederationToken_MissingName tests that a missing Name parameter returns an error
func TestGetFederationToken_MissingName(t *testing.T) {
stsService, _ := setupTestSTSService(t)
+3
View File
@@ -97,6 +97,9 @@ func hasSessionToken(r *http.Request) bool {
}
func extractSessionToken(r *http.Request) string {
if s3_constants.IsSessionTokenIgnored(r.Context()) {
return ""
}
if token := r.Header.Get(s3_constants.SeaweedFSSessionTokenHeader); token != "" {
return token
}