Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
233 lines
7.8 KiB
Go
233 lines
7.8 KiB
Go
package s3api
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/seaweedfs/seaweedfs/weed/iam/integration"
|
|
"github.com/seaweedfs/seaweedfs/weed/iam/oidc"
|
|
"github.com/seaweedfs/seaweedfs/weed/iam/policy"
|
|
"github.com/seaweedfs/seaweedfs/weed/iam/sts"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestGroupsRolesPolicyEnforcement verifies that OIDC groups and roles claims reach
|
|
// the request-time policy evaluation of an STS session, so a single role's permission
|
|
// policy can scope bucket access with ForAnyValue:StringEquals on jwt:groups / jwt:roles.
|
|
// The full path is exercised: AssumeRoleWithWebIdentity embeds the claims in the session
|
|
// JWT, AuthenticateJWT restores them (as []interface{} after the JSON round-trip), and
|
|
// AuthorizeAction evaluates the conditions.
|
|
func TestGroupsRolesPolicyEnforcement(t *testing.T) {
|
|
ctx := context.Background()
|
|
iamManager := setupGroupsRolesIAMManager(t)
|
|
s3iam := NewS3IAMIntegration(iamManager, "localhost:8888")
|
|
|
|
// One role for everyone; each bucket statement is gated on a groups or roles claim
|
|
abacPolicy := &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Sid: "TeamABucketByGroup",
|
|
Effect: "Allow",
|
|
Action: []string{"s3:GetObject", "s3:PutObject"},
|
|
Resource: []string{
|
|
"arn:aws:s3:::team-a-bucket/*",
|
|
},
|
|
Condition: map[string]map[string]interface{}{
|
|
"ForAnyValue:StringEquals": {
|
|
"jwt:groups": "team-a",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Sid: "TeamBBucketByRole",
|
|
Effect: "Allow",
|
|
Action: []string{"s3:GetObject", "s3:PutObject"},
|
|
Resource: []string{
|
|
"arn:aws:s3:::team-b-bucket/*",
|
|
},
|
|
Condition: map[string]map[string]interface{}{
|
|
"ForAnyValue:StringEquals": {
|
|
"jwt:roles": "team-b-rw",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
require.NoError(t, iamManager.CreatePolicy(ctx, "", "TeamABACPolicy", abacPolicy))
|
|
require.NoError(t, iamManager.CreateRole(ctx, "", "TeamABACRole", &integration.RoleDefinition{
|
|
RoleName: "TeamABACRole",
|
|
TrustPolicy: &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "test-oidc",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
AttachedPolicies: []string{"TeamABACPolicy"},
|
|
}))
|
|
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
allowTeamA bool
|
|
allowTeamB bool
|
|
}{
|
|
{
|
|
name: "group member gets team-a bucket only",
|
|
token: groupsRolesJWT(t, "team-a-user", []string{"team-a"}, nil),
|
|
allowTeamA: true,
|
|
allowTeamB: false,
|
|
},
|
|
{
|
|
name: "role holder gets team-b bucket only",
|
|
token: groupsRolesJWT(t, "team-b-user", nil, []string{"team-b-rw"}),
|
|
allowTeamA: false,
|
|
allowTeamB: true,
|
|
},
|
|
{
|
|
name: "member of both gets both buckets in one session",
|
|
token: groupsRolesJWT(t, "both-teams-user", []string{"team-a", "team-b"}, []string{"team-a-rw", "team-b-rw"}),
|
|
allowTeamA: true,
|
|
allowTeamB: true,
|
|
},
|
|
{
|
|
name: "user without matching claims is denied everywhere",
|
|
token: groupsRolesJWT(t, "no-team-user", []string{"other-team"}, []string{"other-role"}),
|
|
allowTeamA: false,
|
|
allowTeamB: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TeamABACRole",
|
|
WebIdentityToken: tt.token,
|
|
RoleSessionName: "abac-test-session",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
identity, errCode := authenticateSessionToken(t, s3iam, response.Credentials.SessionToken)
|
|
require.Equal(t, s3err.ErrNone, errCode)
|
|
|
|
assert.Equal(t, tt.allowTeamA, authorizeGet(ctx, s3iam, identity, "team-a-bucket"), "team-a-bucket access")
|
|
assert.Equal(t, tt.allowTeamB, authorizeGet(ctx, s3iam, identity, "team-b-bucket"), "team-b-bucket access")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGroupsRolesSessionRequestContext pins the session round-trip itself: the groups
|
|
// and roles claims must come back from the session JWT as multi-valued context keys.
|
|
func TestGroupsRolesSessionRequestContext(t *testing.T) {
|
|
ctx := context.Background()
|
|
iamManager := setupGroupsRolesIAMManager(t)
|
|
|
|
require.NoError(t, iamManager.CreateRole(ctx, "", "ContextRole", &integration.RoleDefinition{
|
|
RoleName: "ContextRole",
|
|
TrustPolicy: &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Effect: "Allow",
|
|
Principal: map[string]interface{}{
|
|
"Federated": "test-oidc",
|
|
},
|
|
Action: []string{"sts:AssumeRoleWithWebIdentity"},
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
|
|
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/ContextRole",
|
|
WebIdentityToken: groupsRolesJWT(t, "both-teams-user", []string{"team-a", "team-b"}, []string{"team-a-rw", "team-b-rw"}),
|
|
RoleSessionName: "context-test-session",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
sessionInfo, err := iamManager.GetSTSService().ValidateSessionToken(ctx, response.Credentials.SessionToken)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sessionInfo.RequestContext)
|
|
|
|
// JWT serialization turns []string into []interface{}; assert that shape so a
|
|
// future change to the condition evaluator's type handling fails loudly here.
|
|
assert.Equal(t, []interface{}{"team-a", "team-b"}, sessionInfo.RequestContext["groups"])
|
|
assert.Equal(t, []interface{}{"team-a-rw", "team-b-rw"}, sessionInfo.RequestContext["roles"])
|
|
}
|
|
|
|
func setupGroupsRolesIAMManager(t *testing.T) *integration.IAMManager {
|
|
manager := integration.NewIAMManager()
|
|
config := &integration.IAMConfig{
|
|
STS: &sts.STSConfig{
|
|
TokenDuration: sts.FlexibleDuration{Duration: time.Hour},
|
|
MaxSessionLength: sts.FlexibleDuration{Duration: time.Hour * 12},
|
|
Issuer: "test-sts",
|
|
SigningKey: []byte("test-signing-key-32-characters-long"),
|
|
},
|
|
Policy: &policy.PolicyEngineConfig{
|
|
DefaultEffect: "Deny",
|
|
StoreType: "memory",
|
|
},
|
|
Roles: &integration.RoleStoreConfig{
|
|
StoreType: "memory",
|
|
},
|
|
}
|
|
require.NoError(t, manager.Initialize(config, func() string {
|
|
return "localhost:8888"
|
|
}))
|
|
|
|
provider := oidc.NewMockOIDCProvider("test-oidc")
|
|
require.NoError(t, provider.Initialize(&oidc.OIDCConfig{
|
|
Issuer: "https://test-issuer.com",
|
|
ClientID: "test-client-id",
|
|
}))
|
|
|
|
require.NoError(t, manager.RegisterIdentityProvider(provider))
|
|
return manager
|
|
}
|
|
|
|
// groupsRolesJWT builds a web identity token carrying groups and roles claims.
|
|
func groupsRolesJWT(t *testing.T, subject string, groups, roles []string) string {
|
|
claims := jwt.MapClaims{
|
|
"iss": "https://test-issuer.com",
|
|
"sub": subject,
|
|
"aud": "test-client-id",
|
|
"exp": time.Now().Add(time.Hour).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
}
|
|
if groups != nil {
|
|
claims["groups"] = groups
|
|
}
|
|
if roles != nil {
|
|
claims["roles"] = roles
|
|
}
|
|
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-signing-key"))
|
|
require.NoError(t, err)
|
|
return token
|
|
}
|
|
|
|
func authenticateSessionToken(t *testing.T, s3iam *S3IAMIntegration, sessionToken string) (*IAMIdentity, s3err.ErrorCode) {
|
|
req := httptest.NewRequest("GET", "/", http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+sessionToken)
|
|
return s3iam.AuthenticateJWT(context.Background(), req)
|
|
}
|
|
|
|
func authorizeGet(ctx context.Context, s3iam *S3IAMIntegration, identity *IAMIdentity, bucket string) bool {
|
|
req := httptest.NewRequest("GET", "/"+bucket+"/file.txt", http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+identity.SessionToken)
|
|
return s3iam.AuthorizeAction(ctx, identity, s3_constants.ACTION_READ, bucket, "file.txt", req) == s3err.ErrNone
|
|
}
|