mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-18 04:20:53 +02:00
* s3api: add TrustedProxies allowlist helper for aws:SourceIp extraction Introduces a policy_engine.TrustedProxies type that parses a comma-separated list of bare IPs and CIDRs (mirroring Guard.UpdateWhiteList) and extracts the client IP for aws:SourceIp condition evaluation. When the direct TCP peer is in the allowlist, X-Forwarded-For is walked right-to-left skipping trusted hops (then X-Real-Ip); otherwise the direct peer address is returned. This is the building block for restoring configurable forwarded-header trust removed inb88156f(#11231), as proposed in #11302. * s3api: honor trusted-proxy allowlist in bucket/IAM policy engine Make ExtractConditionValuesFromRequest a method on *PolicyEngine so it can use the engine TrustedProxies when resolving aws:SourceIp. With no allowlist configured the behavior is unchanged fromb88156f: the direct TCP peer is used and forwarded headers are ignored. When an allowlist is configured via SetTrustedProxies, requests from a trusted peer honor X-Forwarded-For (right-to-left) then X-Real-Ip. Update the two call sites (auth_credentials.go, s3api_bucket_policy_engine.go) and the engine tests to the method form, and add a regression test for the trusted-proxy path. * s3api: honor trusted-proxy allowlist in IAM role/session policies Make extractRequestContext and extractSourceIP methods on *S3IAMIntegration so they can use the integration TrustedProxies when resolving aws:SourceIp. With no allowlist configured the behavior is unchanged fromb88156f: the direct TCP peer is used and forwarded headers are ignored. When an allowlist is configured via SetTrustedProxies, requests from a trusted peer honor X-Forwarded-For (right-to-left) then X-Real-Ip. Update the call site in isActionExplicitlyDeniedByIAM to type-assert the integration and use the method, and add a regression test for the trusted-proxy path. * s3api: load [s3.trusted_proxies] from security.toml and wire to engines Read s3.trusted_proxies.white_list (comma-separated IPs/CIDRs) from security.toml and propagate the allowlist to the bucket policy engine, the IAM policy engine (persisted across rebuilds via IdentityAccessManagement.SetTrustedProxies), and the IAM integration. Reloaded on SIGHUP alongside the JWT signing keys. Document the new section in the scaffold security.toml. Closes #11302. * s3api: harden TrustedProxies parsing and X-Forwarded-For traversal Canonicalize bare IP entries (via net.ParseIP + String) so non-canonical IPv6 allowlist entries such as 2001:0db8::1 match peers rendered as 2001:db8::1, and log+skip unparseable bare entries instead of storing them inertly. When walking X-Forwarded-For right-to-left, stop at the first malformed (non-empty, unparseable) entry instead of skipping it, and only fall back to the leftmost valid IP when the chain was well-formed. This prevents a malformed hop from masking a forged IP to its left. Addresses review feedback on #11315. * s3api: make TrustedProxies reload race-free via atomic.Pointer Store the trusted-proxy allowlist behind sync/atomic.Pointer in PolicyEngine and S3IAMIntegration so SIGHUP reloads (which swap the allowlist) cannot race with concurrent request handlers reading it. This mirrors the existing Guard guardState pattern. The IdentityAccessManagement copy is already protected by iam.m. Addresses review feedback on #11315.
571 lines
16 KiB
Go
571 lines
16 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/ldap"
|
|
"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/policy_engine"
|
|
"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"
|
|
)
|
|
|
|
// createTestJWTAuth creates a test JWT token with the specified issuer, subject and signing key
|
|
func createTestJWTAuth(t *testing.T, issuer, subject, signingKey string) string {
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"iss": issuer,
|
|
"sub": subject,
|
|
"aud": "test-client-id",
|
|
"exp": time.Now().Add(time.Hour).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
// Add claims that trust policy validation expects
|
|
"idp": "test-oidc", // Identity provider claim for trust policy matching
|
|
})
|
|
|
|
tokenString, err := token.SignedString([]byte(signingKey))
|
|
require.NoError(t, err)
|
|
return tokenString
|
|
}
|
|
|
|
// TestJWTAuthenticationFlow tests the JWT authentication flow without full S3 server
|
|
func TestJWTAuthenticationFlow(t *testing.T) {
|
|
// Set up IAM system
|
|
iamManager := setupTestIAMManager(t)
|
|
|
|
// Create IAM integration
|
|
s3iam := NewS3IAMIntegration(iamManager, "localhost:8888")
|
|
|
|
// Create IAM server with integration
|
|
iamServer := setupIAMWithIntegration(t, iamManager, s3iam)
|
|
|
|
// Test scenarios
|
|
tests := []struct {
|
|
name string
|
|
roleArn string
|
|
setupRole func(ctx context.Context, mgr *integration.IAMManager)
|
|
testOperations []JWTTestOperation
|
|
}{
|
|
{
|
|
name: "Read-Only JWT Authentication",
|
|
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
|
|
setupRole: setupTestReadOnlyRole,
|
|
testOperations: []JWTTestOperation{
|
|
{Action: s3_constants.ACTION_READ, Bucket: "test-bucket", Object: "test-file.txt", ExpectedAllow: true},
|
|
{Action: s3_constants.ACTION_WRITE, Bucket: "test-bucket", Object: "new-file.txt", ExpectedAllow: false},
|
|
{Action: s3_constants.ACTION_LIST, Bucket: "test-bucket", Object: "", ExpectedAllow: true},
|
|
},
|
|
},
|
|
{
|
|
name: "Admin JWT Authentication",
|
|
roleArn: "arn:aws:iam::role/S3AdminRole",
|
|
setupRole: setupTestAdminRole,
|
|
testOperations: []JWTTestOperation{
|
|
{Action: s3_constants.ACTION_READ, Bucket: "admin-bucket", Object: "admin-file.txt", ExpectedAllow: true},
|
|
{Action: s3_constants.ACTION_WRITE, Bucket: "admin-bucket", Object: "new-admin-file.txt", ExpectedAllow: true},
|
|
{Action: s3_constants.ACTION_DELETE_BUCKET, Bucket: "admin-bucket", Object: "", ExpectedAllow: true},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Set up role
|
|
tt.setupRole(ctx, iamManager)
|
|
|
|
// Create a valid JWT token for testing
|
|
validJWTToken := createTestJWTAuth(t, "https://test-issuer.com", "test-user-123", "test-signing-key")
|
|
|
|
// Assume role to get JWT
|
|
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: tt.roleArn,
|
|
WebIdentityToken: validJWTToken,
|
|
RoleSessionName: "jwt-auth-test",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
jwtToken := response.Credentials.SessionToken
|
|
|
|
// Test each operation
|
|
for _, op := range tt.testOperations {
|
|
t.Run(string(op.Action), func(t *testing.T) {
|
|
// Test JWT authentication
|
|
identity, errCode := testJWTAuthentication(t, iamServer, jwtToken)
|
|
require.Equal(t, s3err.ErrNone, errCode, "JWT authentication should succeed")
|
|
require.NotNil(t, identity)
|
|
|
|
// Test authorization with appropriate role based on test case
|
|
var testRoleName string
|
|
if tt.name == "Read-Only JWT Authentication" {
|
|
testRoleName = "TestReadRole"
|
|
} else {
|
|
testRoleName = "TestAdminRole"
|
|
}
|
|
allowed := testJWTAuthorizationWithRole(t, iamServer, identity, op.Action, op.Bucket, op.Object, jwtToken, testRoleName)
|
|
assert.Equal(t, op.ExpectedAllow, allowed, "Operation %s should have expected result", op.Action)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestJWTTokenValidation tests JWT token validation edge cases
|
|
func TestJWTTokenValidation(t *testing.T) {
|
|
iamManager := setupTestIAMManager(t)
|
|
s3iam := NewS3IAMIntegration(iamManager, "localhost:8888")
|
|
iamServer := setupIAMWithIntegration(t, iamManager, s3iam)
|
|
|
|
tests := []struct {
|
|
name string
|
|
token string
|
|
expectedErr s3err.ErrorCode
|
|
}{
|
|
{
|
|
name: "Empty token",
|
|
token: "",
|
|
expectedErr: s3err.ErrAccessDenied,
|
|
},
|
|
{
|
|
name: "Invalid token format",
|
|
token: "invalid-token",
|
|
expectedErr: s3err.ErrAccessDenied,
|
|
},
|
|
{
|
|
name: "Expired token",
|
|
token: "expired-session-token",
|
|
expectedErr: s3err.ErrAccessDenied,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
identity, errCode := testJWTAuthentication(t, iamServer, tt.token)
|
|
|
|
assert.Equal(t, tt.expectedErr, errCode)
|
|
assert.Nil(t, identity)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRequestContextExtraction tests context extraction for policy conditions
|
|
func TestRequestContextExtraction(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupRequest func() *http.Request
|
|
expectedIP string
|
|
expectedUA string
|
|
}{
|
|
{
|
|
name: "Standard request with IP",
|
|
setupRequest: func() *http.Request {
|
|
req := httptest.NewRequest("GET", "/test-bucket/test-file.txt", http.NoBody)
|
|
req.Header.Set("X-Forwarded-For", "192.168.1.100")
|
|
req.Header.Set("User-Agent", "aws-sdk-go/1.0")
|
|
req.RemoteAddr = "127.0.0.1:12345"
|
|
return req
|
|
},
|
|
expectedIP: "127.0.0.1",
|
|
expectedUA: "aws-sdk-go/1.0",
|
|
},
|
|
{
|
|
name: "Request with X-Real-IP",
|
|
setupRequest: func() *http.Request {
|
|
req := httptest.NewRequest("GET", "/test-bucket/test-file.txt", http.NoBody)
|
|
req.Header.Set("X-Real-IP", "10.0.0.1")
|
|
req.Header.Set("User-Agent", "boto3/1.0")
|
|
req.RemoteAddr = "127.0.0.1:12345"
|
|
return req
|
|
},
|
|
expectedIP: "127.0.0.1",
|
|
expectedUA: "boto3/1.0",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := tt.setupRequest()
|
|
|
|
s3iam := &S3IAMIntegration{}
|
|
context := s3iam.extractRequestContext(req)
|
|
|
|
if tt.expectedIP != "" {
|
|
assert.Equal(t, tt.expectedIP, context["aws:SourceIp"])
|
|
}
|
|
|
|
if tt.expectedUA != "" {
|
|
assert.Equal(t, tt.expectedUA, context["userAgent"])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRequestContextExtraction_TrustedProxy verifies that when a trusted
|
|
// proxy allowlist is configured, X-Forwarded-For is honored.
|
|
func TestRequestContextExtraction_TrustedProxy(t *testing.T) {
|
|
s3iam := &S3IAMIntegration{}
|
|
s3iam.SetTrustedProxies(policy_engine.NewTrustedProxies([]string{"10.0.0.0/24"}))
|
|
|
|
req := httptest.NewRequest("GET", "/test-bucket/test-file.txt", http.NoBody)
|
|
req.Header.Set("X-Forwarded-For", "203.0.113.99")
|
|
req.RemoteAddr = "10.0.0.1:12345"
|
|
|
|
context := s3iam.extractRequestContext(req)
|
|
assert.Equal(t, "203.0.113.99", context["aws:SourceIp"])
|
|
}
|
|
|
|
// TestIPBasedPolicyEnforcement tests IP-based conditional policies
|
|
func TestIPBasedPolicyEnforcement(t *testing.T) {
|
|
iamManager := setupTestIAMManager(t)
|
|
s3iam := NewS3IAMIntegration(iamManager, "localhost:8888")
|
|
ctx := context.Background()
|
|
|
|
// Set up IP-restricted role
|
|
setupTestIPRestrictedRole(ctx, iamManager)
|
|
|
|
// Create a valid JWT token for testing
|
|
validJWTToken := createTestJWTAuth(t, "https://test-issuer.com", "test-user-123", "test-signing-key")
|
|
|
|
// Assume role
|
|
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/S3IPRestrictedRole",
|
|
WebIdentityToken: validJWTToken,
|
|
RoleSessionName: "ip-test-session",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
sourceIP string
|
|
shouldAllow bool
|
|
}{
|
|
{
|
|
name: "Allow from office IP",
|
|
sourceIP: "192.168.1.100",
|
|
shouldAllow: true,
|
|
},
|
|
{
|
|
name: "Block from external IP",
|
|
sourceIP: "8.8.8.8",
|
|
shouldAllow: false,
|
|
},
|
|
{
|
|
name: "Allow from internal range",
|
|
sourceIP: "10.0.0.1",
|
|
shouldAllow: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create request with specific IP
|
|
req := httptest.NewRequest("GET", "/restricted-bucket/file.txt", http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+response.Credentials.SessionToken)
|
|
req.RemoteAddr = tt.sourceIP + ":12345"
|
|
|
|
// Create IAM identity for testing
|
|
identity := &IAMIdentity{
|
|
Name: "test-user",
|
|
Principal: response.AssumedRoleUser.Arn,
|
|
SessionToken: response.Credentials.SessionToken,
|
|
}
|
|
|
|
// Test authorization with IP condition
|
|
errCode := s3iam.AuthorizeAction(ctx, identity, s3_constants.ACTION_READ, "restricted-bucket", "file.txt", req)
|
|
|
|
if tt.shouldAllow {
|
|
assert.Equal(t, s3err.ErrNone, errCode, "Should allow access from IP %s", tt.sourceIP)
|
|
} else {
|
|
assert.Equal(t, s3err.ErrAccessDenied, errCode, "Should deny access from IP %s", tt.sourceIP)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// JWTTestOperation represents a test operation for JWT testing
|
|
type JWTTestOperation struct {
|
|
Action Action
|
|
Bucket string
|
|
Object string
|
|
ExpectedAllow bool
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func setupTestIAMManager(t *testing.T) *integration.IAMManager {
|
|
// Create IAM manager
|
|
manager := integration.NewIAMManager()
|
|
|
|
// Initialize with test configuration
|
|
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",
|
|
},
|
|
}
|
|
|
|
err := manager.Initialize(config, func() string {
|
|
return "localhost:8888" // Mock filer address for testing
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set up test identity providers
|
|
setupTestIdentityProviders(t, manager)
|
|
|
|
return manager
|
|
}
|
|
|
|
func setupTestIdentityProviders(t *testing.T, manager *integration.IAMManager) {
|
|
// Set up OIDC provider
|
|
oidcProvider := oidc.NewMockOIDCProvider("test-oidc")
|
|
oidcConfig := &oidc.OIDCConfig{
|
|
Issuer: "https://test-issuer.com",
|
|
ClientID: "test-client-id",
|
|
}
|
|
err := oidcProvider.Initialize(oidcConfig)
|
|
require.NoError(t, err)
|
|
oidcProvider.SetupDefaultTestData()
|
|
|
|
// Set up LDAP provider
|
|
ldapProvider := ldap.NewMockLDAPProvider("test-ldap")
|
|
err = ldapProvider.Initialize(nil) // Mock doesn't need real config
|
|
require.NoError(t, err)
|
|
ldapProvider.SetupDefaultTestData()
|
|
|
|
// Register providers
|
|
err = manager.RegisterIdentityProvider(oidcProvider)
|
|
require.NoError(t, err)
|
|
err = manager.RegisterIdentityProvider(ldapProvider)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func setupIAMWithIntegration(t *testing.T, iamManager *integration.IAMManager, s3iam *S3IAMIntegration) *IdentityAccessManagement {
|
|
// Create a minimal IdentityAccessManagement for testing
|
|
iam := &IdentityAccessManagement{
|
|
isAuthEnabled: true,
|
|
}
|
|
|
|
// Set IAM integration
|
|
iam.SetIAMIntegration(s3iam)
|
|
|
|
return iam
|
|
}
|
|
|
|
func setupTestReadOnlyRole(ctx context.Context, manager *integration.IAMManager) {
|
|
// Create read-only policy
|
|
readPolicy := &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Sid: "AllowS3Read",
|
|
Effect: "Allow",
|
|
Action: []string{"s3:GetObject", "s3:ListBucket"},
|
|
Resource: []string{
|
|
"arn:aws:s3:::*",
|
|
"arn:aws:s3:::*/*",
|
|
},
|
|
},
|
|
{
|
|
Sid: "AllowSTSSessionValidation",
|
|
Effect: "Allow",
|
|
Action: []string{"sts:ValidateSession"},
|
|
Resource: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
manager.CreatePolicy(ctx, "", "S3ReadOnlyPolicy", readPolicy)
|
|
|
|
// Create role
|
|
manager.CreateRole(ctx, "", "S3ReadOnlyRole", &integration.RoleDefinition{
|
|
RoleName: "S3ReadOnlyRole",
|
|
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{"S3ReadOnlyPolicy"},
|
|
})
|
|
|
|
// Also create a TestReadRole for read-only authorization testing
|
|
manager.CreateRole(ctx, "", "TestReadRole", &integration.RoleDefinition{
|
|
RoleName: "TestReadRole",
|
|
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{"S3ReadOnlyPolicy"},
|
|
})
|
|
}
|
|
|
|
func setupTestAdminRole(ctx context.Context, manager *integration.IAMManager) {
|
|
// Create admin policy
|
|
adminPolicy := &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Sid: "AllowAllS3",
|
|
Effect: "Allow",
|
|
Action: []string{"s3:*"},
|
|
Resource: []string{
|
|
"arn:aws:s3:::*",
|
|
"arn:aws:s3:::*/*",
|
|
},
|
|
},
|
|
{
|
|
Sid: "AllowSTSSessionValidation",
|
|
Effect: "Allow",
|
|
Action: []string{"sts:ValidateSession"},
|
|
Resource: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
manager.CreatePolicy(ctx, "", "S3AdminPolicy", adminPolicy)
|
|
|
|
// Create role
|
|
manager.CreateRole(ctx, "", "S3AdminRole", &integration.RoleDefinition{
|
|
RoleName: "S3AdminRole",
|
|
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{"S3AdminPolicy"},
|
|
})
|
|
|
|
// Also create a TestAdminRole with admin policy for authorization testing
|
|
manager.CreateRole(ctx, "", "TestAdminRole", &integration.RoleDefinition{
|
|
RoleName: "TestAdminRole",
|
|
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{"S3AdminPolicy"}, // Admin gets full access
|
|
})
|
|
}
|
|
|
|
func setupTestIPRestrictedRole(ctx context.Context, manager *integration.IAMManager) {
|
|
// Create IP-restricted policy
|
|
restrictedPolicy := &policy.PolicyDocument{
|
|
Version: "2012-10-17",
|
|
Statement: []policy.Statement{
|
|
{
|
|
Sid: "AllowFromOffice",
|
|
Effect: "Allow",
|
|
Action: []string{"s3:GetObject", "s3:ListBucket"},
|
|
Resource: []string{
|
|
"arn:aws:s3:::*",
|
|
"arn:aws:s3:::*/*",
|
|
},
|
|
Condition: map[string]map[string]interface{}{
|
|
"IpAddress": {
|
|
"aws:SourceIp": []string{"192.168.1.0/24", "10.0.0.0/8"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
manager.CreatePolicy(ctx, "", "S3IPRestrictedPolicy", restrictedPolicy)
|
|
|
|
// Create role
|
|
manager.CreateRole(ctx, "", "S3IPRestrictedRole", &integration.RoleDefinition{
|
|
RoleName: "S3IPRestrictedRole",
|
|
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{"S3IPRestrictedPolicy"},
|
|
})
|
|
}
|
|
|
|
func testJWTAuthentication(t *testing.T, iam *IdentityAccessManagement, token string) (*Identity, s3err.ErrorCode) {
|
|
// Create test request with JWT
|
|
req := httptest.NewRequest("GET", "/test-bucket/test-object", http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
// Test authentication
|
|
if iam.iamIntegration == nil {
|
|
return nil, s3err.ErrNotImplemented
|
|
}
|
|
|
|
return iam.authenticateJWTWithIAM(req)
|
|
}
|
|
|
|
func testJWTAuthorizationWithRole(t *testing.T, iam *IdentityAccessManagement, identity *Identity, action Action, bucket, object, token, roleName string) bool {
|
|
// Create test request
|
|
req := httptest.NewRequest("GET", "/"+bucket+"/"+object, http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set(s3_constants.SeaweedFSSessionTokenHeader, token)
|
|
|
|
// Use a proper principal ARN format that matches what STS would generate
|
|
principalArn := "arn:aws:sts::assumed-role/" + roleName + "/test-session"
|
|
req.Header.Set(s3_constants.SeaweedFSPrincipalHeader, principalArn)
|
|
|
|
// Test authorization
|
|
if iam.iamIntegration == nil {
|
|
return false
|
|
}
|
|
|
|
errCode := iam.authorizeWithIAM(req, identity, action, bucket, object)
|
|
return errCode == s3err.ErrNone
|
|
}
|