s3api: fix static IAM policy enforcement after reload (#8532)

* s3api: honor attached IAM policies over legacy actions

* s3api: hydrate IAM policy docs during config reload

* s3api: use policy-aware auth when listing buckets

* credential: propagate context through filer_etc policy reads

* credential: make legacy policy deletes durable

* s3api: exercise managed policy runtime loader

* s3api: allow static IAM users without session tokens

* iam: deny unmatched attached policies under default allow

* iam: load embedded policy files from filer store

* s3api: require session tokens for IAM presigning

* s3api: sync runtime policies into zero-config IAM

* credential: respect context in policy file loads

* credential: serialize legacy policy deletes

* iam: align filer policy store naming

* s3api: use authenticated principals for presigning

* iam: deep copy policy conditions

* s3api: require request creation in policy tests

* filer: keep ReadInsideFiler as the context-aware API

* iam: harden filer policy store writes

* credential: strengthen legacy policy serialization test

* credential: forward runtime policy loaders through wrapper

* s3api: harden runtime policy merging

* iam: require typed already-exists errors
This commit is contained in:
Chris Lu
2026-03-06 12:35:08 -08:00
committed by GitHub
parent 338be16254
commit f9311a3422
30 changed files with 1903 additions and 168 deletions

View File

@@ -13,16 +13,15 @@ import (
"github.com/seaweedfs/seaweedfs/weed/iam/sts"
"github.com/seaweedfs/seaweedfs/weed/iam/utils"
"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"
)
// TestS3IAMMiddleware tests the basic S3 IAM middleware functionality
func TestS3IAMMiddleware(t *testing.T) {
// Create IAM manager
iamManager := integration.NewIAMManager()
func newTestS3IAMManagerWithDefaultEffect(t *testing.T, defaultEffect string) *integration.IAMManager {
t.Helper()
// Initialize with test configuration
iamManager := integration.NewIAMManager()
config := &integration.IAMConfig{
STS: &sts.STSConfig{
TokenDuration: sts.FlexibleDuration{Duration: time.Hour},
@@ -31,7 +30,7 @@ func TestS3IAMMiddleware(t *testing.T) {
SigningKey: []byte("test-signing-key-32-characters-long"),
},
Policy: &policy.PolicyEngineConfig{
DefaultEffect: "Deny",
DefaultEffect: defaultEffect,
StoreType: "memory",
},
Roles: &integration.RoleStoreConfig{
@@ -40,10 +39,22 @@ func TestS3IAMMiddleware(t *testing.T) {
}
err := iamManager.Initialize(config, func() string {
return "localhost:8888" // Mock filer address for testing
return "localhost:8888"
})
require.NoError(t, err)
return iamManager
}
func newTestS3IAMManager(t *testing.T) *integration.IAMManager {
t.Helper()
return newTestS3IAMManagerWithDefaultEffect(t, "Deny")
}
// TestS3IAMMiddleware tests the basic S3 IAM middleware functionality
func TestS3IAMMiddleware(t *testing.T) {
iamManager := newTestS3IAMManager(t)
// Create S3 IAM integration
s3IAMIntegration := NewS3IAMIntegration(iamManager, "localhost:8888")
@@ -52,6 +63,74 @@ func TestS3IAMMiddleware(t *testing.T) {
assert.True(t, s3IAMIntegration.enabled)
}
func TestS3IAMMiddlewareStaticV4ManagedPolicies(t *testing.T) {
ctx := context.Background()
iamManager := newTestS3IAMManager(t)
allowPolicy := &policy.PolicyDocument{
Version: "2012-10-17",
Statement: []policy.Statement{
{
Effect: "Allow",
Action: policy.StringList{"s3:PutObject", "s3:ListBucket"},
Resource: policy.StringList{"arn:aws:s3:::cli-allowed-bucket", "arn:aws:s3:::cli-allowed-bucket/*"},
},
},
}
require.NoError(t, iamManager.CreatePolicy(ctx, "localhost:8888", "cli-bucket-access-policy", allowPolicy))
s3IAMIntegration := NewS3IAMIntegration(iamManager, "localhost:8888")
identity := &IAMIdentity{
Name: "cli-test-user",
Principal: "arn:aws:iam::000000000000:user/cli-test-user",
PolicyNames: []string{"cli-bucket-access-policy"},
}
putReq := httptest.NewRequest(http.MethodPut, "http://example.com/cli-allowed-bucket/test-file.txt", http.NoBody)
putErrCode := s3IAMIntegration.AuthorizeAction(ctx, identity, s3_constants.ACTION_WRITE, "cli-allowed-bucket", "test-file.txt", putReq)
assert.Equal(t, s3err.ErrNone, putErrCode)
listReq := httptest.NewRequest(http.MethodGet, "http://example.com/cli-allowed-bucket/", http.NoBody)
listErrCode := s3IAMIntegration.AuthorizeAction(ctx, identity, s3_constants.ACTION_LIST, "cli-allowed-bucket", "", listReq)
assert.Equal(t, s3err.ErrNone, listErrCode)
}
func TestS3IAMMiddlewareAttachedPoliciesRestrictDefaultAllow(t *testing.T) {
ctx := context.Background()
iamManager := newTestS3IAMManagerWithDefaultEffect(t, "Allow")
allowPolicy := &policy.PolicyDocument{
Version: "2012-10-17",
Statement: []policy.Statement{
{
Effect: "Allow",
Action: policy.StringList{"s3:PutObject", "s3:ListBucket"},
Resource: policy.StringList{"arn:aws:s3:::cli-allowed-bucket", "arn:aws:s3:::cli-allowed-bucket/*"},
},
},
}
require.NoError(t, iamManager.CreatePolicy(ctx, "localhost:8888", "cli-bucket-access-policy", allowPolicy))
s3IAMIntegration := NewS3IAMIntegration(iamManager, "localhost:8888")
identity := &IAMIdentity{
Name: "cli-test-user",
Principal: "arn:aws:iam::000000000000:user/cli-test-user",
PolicyNames: []string{"cli-bucket-access-policy"},
}
allowedReq := httptest.NewRequest(http.MethodPut, "http://example.com/cli-allowed-bucket/test-file.txt", http.NoBody)
allowedErrCode := s3IAMIntegration.AuthorizeAction(ctx, identity, s3_constants.ACTION_WRITE, "cli-allowed-bucket", "test-file.txt", allowedReq)
assert.Equal(t, s3err.ErrNone, allowedErrCode)
forbiddenReq := httptest.NewRequest(http.MethodPut, "http://example.com/cli-forbidden-bucket/forbidden-file.txt", http.NoBody)
forbiddenErrCode := s3IAMIntegration.AuthorizeAction(ctx, identity, s3_constants.ACTION_WRITE, "cli-forbidden-bucket", "forbidden-file.txt", forbiddenReq)
assert.Equal(t, s3err.ErrAccessDenied, forbiddenErrCode)
forbiddenListReq := httptest.NewRequest(http.MethodGet, "http://example.com/cli-forbidden-bucket/", http.NoBody)
forbiddenListErrCode := s3IAMIntegration.AuthorizeAction(ctx, identity, s3_constants.ACTION_LIST, "cli-forbidden-bucket", "", forbiddenListReq)
assert.Equal(t, s3err.ErrAccessDenied, forbiddenListErrCode)
}
// TestS3IAMMiddlewareJWTAuth tests JWT authentication
func TestS3IAMMiddlewareJWTAuth(t *testing.T) {
// Skip for now since it requires full setup