Files
seaweedfs/weed/s3api/auth_static_session_token_test.go
T
Chris Lu c023493cf0 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
2026-10-10 10:11:24 +08:00

122 lines
4.1 KiB
Go

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
}