mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-03 18:47:41 +01:00
* s3vectors: resolve the caller the way table buckets do The vector checks used the identity name as principal, while the catalog records a bucket's owner through getAccountID: the account id, or the issuer-scoped user id of a federated identity. An identity with an account or a federated identity did not own the vector bucket it created, a Deny naming its account did not bind it, and an Admin identity in the admin account did not get the admin bypass it has on table buckets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3vectors: honor identity actions scoped to a vector bucket An identity action such as s3vectors:QueryVectors:team-a granted nothing, because the vector policy context never named the bucket. Table buckets match <action>:<bucket> through the same context field, so pass the bucket the check is about. Creating a bucket stays unscoped, as for table buckets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.6 KiB
Go
62 lines
2.6 KiB
Go
package s3tables
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestGetAccountIDRejectsHeaderAdmin(t *testing.T) {
|
|
h := NewS3TablesHandler()
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAdminId)
|
|
|
|
assert.NotEqual(t, s3_constants.AccountAdminId, h.getAccountID(req),
|
|
"a client-supplied account header must not resolve to the admin principal")
|
|
}
|
|
|
|
func TestGetAccountIDHeaderBranchResolvesCaller(t *testing.T) {
|
|
h := NewS3TablesHandler()
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.Header.Set(s3_constants.AmzAccountId, "alice")
|
|
|
|
assert.Equal(t, "alice", h.getAccountID(req))
|
|
}
|
|
|
|
func TestPrincipalForMirrorsGetAccountID(t *testing.T) {
|
|
m := NewManager()
|
|
m.SetAccountID("000000000000")
|
|
|
|
named := func(name string) *http.Request {
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
return req.WithContext(s3_constants.SetIdentityNameInContext(req.Context(), name))
|
|
}
|
|
withIdentity := func(id *testIdentity) *http.Request {
|
|
req := named(id.Name)
|
|
return req.WithContext(s3_constants.SetIdentityInContext(req.Context(), id))
|
|
}
|
|
|
|
assert.Equal(t, "000000000000", m.PrincipalFor(nil))
|
|
assert.Equal(t, "000000000000", m.PrincipalFor(named("")))
|
|
assert.Equal(t, "alice", m.PrincipalFor(named("alice")))
|
|
assert.Equal(t, s3_constants.AccountAdminId, m.PrincipalFor(named(s3_constants.AccountAdminId)),
|
|
"the name branch of getAccountID keeps the admin name")
|
|
assert.Equal(t, "000000000000", m.PrincipalFor(named(" ")))
|
|
assert.Equal(t, "bob", m.PrincipalFor(named("arn:aws:iam::123456789012:user/bob")),
|
|
"ARN identities collapse to the trailing segment like getAccountID")
|
|
assert.Equal(t, "team-acct", m.PrincipalFor(withIdentity(&testIdentity{Name: "carol", Account: &testIdentityAccount{Id: "team-acct"}})),
|
|
"an identity with an account is its account, not its name")
|
|
assert.Equal(t, "uid-alice", m.PrincipalFor(withIdentity(&testIdentity{Name: "alice-sub", Account: &testIdentityAccount{Id: "alice-sub"},
|
|
Claims: map[string]interface{}{"aws:userid": "uid-alice"}})),
|
|
"a federated identity is its issuer-scoped user id")
|
|
}
|
|
|
|
func TestCheckPermissionDenyPolicyBindsNonAdmin(t *testing.T) {
|
|
denyAll := `{"Statement":[{"Effect":"Deny","Principal":"*","Action":"s3tables:DeleteTableBucket"}]}`
|
|
assert.False(t, CheckPermissionWithContext("s3tables:DeleteTableBucket", "mallory", "owner123", denyAll, "arn:aws:s3tables:us-east-1:000000000000:bucket/victim",
|
|
&PolicyContext{DefaultAllow: true}))
|
|
}
|