mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-12 01:07:35 +02:00
* 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
122 lines
4.1 KiB
Go
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
|
|
}
|