s3tables: test a federated session on a bucket its legacy subject owns

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 committed 2026-11-13 02:56:59 -08:00
1 parent df2de6d1a4
commit 720866b50a
1 file changed
+82
@@ -0,0 +1,82 @@
package s3tables
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/iam/sts"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables/s3tablestest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// A table bucket a web-identity role session created before owners were
// issuer-scoped is owned by the bare OIDC subject. The same subject's later
// sessions, signed with SigV4 session credentials, carry the scoped aws:userid
// and no asserted issuers, and must still own the bucket.
func TestSTSSessionKeepsLegacySubjectOwnedBucket(t *testing.T) {
const (
bucket = "lake-norm"
sub = "project_path:dev/infra/datalake/normalisation:ref_type:branch:ref:main"
issuer = "https://gitlab.example.com"
)
arn, err := BuildBucketARN(DefaultRegion, DefaultAccountID, bucket)
require.NoError(t, err)
body, err := json.Marshal(&CreateNamespaceRequest{TableBucketARN: arn, Namespace: []string{"some_ns"}})
require.NoError(t, err)
createNamespace := func(t *testing.T, sessionIssuer string, configured ...string) int {
fs := s3tablestest.Start(t)
metadata, err := json.Marshal(&tableBucketMetadata{Name: bucket, CreatedAt: time.Now(), OwnerAccountID: sub})
require.NoError(t, err)
fs.Put(TablesPath, bucket, map[string][]byte{ExtendedKeyTableBucket: []byte("true"), ExtendedKeyMetadata: metadata})
m := NewManager()
m.SetDefaultAllow(false)
if configured != nil {
m.SetTrustedIssuerSource(func(context.Context) []string { return configured })
}
identity := &testIdentity{
Name: sub,
Account: &testIdentityAccount{Id: sub},
Claims: map[string]interface{}{
"sub": sub,
"aws:userid": sts.ComputeParentUser(sub, sessionIssuer),
},
}
req, err := newManagerRequest(s3_constants.SetIdentityInContext(context.Background(), identity), "CreateNamespace", body, sub)
require.NoError(t, err)
recorder := httptest.NewRecorder()
m.handler.HandleRequest(recorder, req, NewManagerClient(fs.Client))
return recorder.Code
}
assert.Equal(t, http.StatusOK, createNamespace(t, issuer, issuer), "the subject's own session lost its bucket")
assert.Equal(t, http.StatusForbidden, createNamespace(t, issuer), "no configured issuer set must not re-anchor")
assert.Equal(t, http.StatusForbidden, createNamespace(t, issuer, issuer, "https://other.example.com"),
"a legacy subject is ambiguous once two issuers are configured")
assert.Equal(t, http.StatusForbidden, createNamespace(t, "https://other.example.com", issuer),
"another issuer's user with the same subject must not take the bucket")
}
// Issuers asserted by the bearer path win, even when empty; an identity that
// is not a federated session never reads the configured set.
func TestTrustedIssuersSourceOnlyForFederatedSessions(t *testing.T) {
h := NewS3TablesHandler()
h.SetTrustedIssuerSource(func(context.Context) []string { return []string{"configured"} })
issuersFor := func(claims map[string]interface{}) []string {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req = req.WithContext(s3_constants.SetIdentityInContext(req.Context(), &testIdentity{Claims: claims}))
return h.trustedIssuers(req)
}
scoped := sts.ComputeParentUser("alice", "configured")
assert.Equal(t, []string{"configured"}, issuersFor(map[string]interface{}{"aws:userid": scoped}))
assert.Equal(t, []string{"asserted"}, issuersFor(map[string]interface{}{"aws:userid": scoped, "seaweed:oidc-issuers": "asserted"}))
assert.Empty(t, issuersFor(map[string]interface{}{"aws:userid": scoped, "seaweed:oidc-issuers": ""}))
assert.Empty(t, issuersFor(map[string]interface{}{"aws:userid": "alice"}))
assert.Empty(t, issuersFor(map[string]interface{}{"sub": "alice"}))
}