Browse Source

refactor: simplify configuration by using constants for default base paths

This commit addresses the user feedback that configuration files should not
need to specify default paths when constants are available.

### Changes Made:

#### Configuration Simplification:
- Removed redundant basePath configurations from iam_config_distributed.json
- All stores now use constants for defaults:
  * Sessions: /etc/iam/sessions (DefaultSessionBasePath)
  * Policies: /etc/iam/policies (DefaultPolicyBasePath)
  * Roles: /etc/iam/roles (DefaultRoleBasePath)
- Eliminated empty storeConfig objects entirely for cleaner JSON

#### Updated Store Implementations:
- FilerPolicyStore: Updated hardcoded path to use /etc/iam/policies
- FilerRoleStore: Updated hardcoded path to use /etc/iam/roles
- All stores consistently align with /etc/ filer convention

#### Runtime Filer Address Integration:
- Updated IAM manager methods to accept filerAddress parameter:
  * AssumeRoleWithWebIdentity(ctx, filerAddress, request)
  * AssumeRoleWithCredentials(ctx, filerAddress, request)
  * IsActionAllowed(ctx, filerAddress, request)
  * ExpireSessionForTesting(ctx, filerAddress, sessionToken)
- Enhanced S3IAMIntegration to store filerAddress from S3ApiServer
- Updated all test files to pass test filerAddress ('localhost:8888')

### Benefits:
-  Cleaner, minimal configuration files
-  Consistent use of well-defined constants for defaults
-  No configuration needed for standard use cases
-  Runtime filer address flexibility maintained
-  Aligns with SeaweedFS /etc/ convention throughout

### Breaking Change:
- S3IAMIntegration constructor now requires filerAddress parameter
- All IAM manager methods now require filerAddress as second parameter
- Tests and middleware updated accordingly
pull/7160/head
chrislu 1 month ago
parent
commit
586ebbca2d
  1. 13
      test/s3/iam/iam_config_distributed.json
  2. 20
      weed/iam/integration/iam_integration_test.go
  3. 16
      weed/iam/integration/iam_manager.go
  4. 2
      weed/iam/integration/role_store.go
  5. 2
      weed/iam/policy/policy_store.go
  6. 2
      weed/s3api/s3_end_to_end_test.go
  7. 16
      weed/s3api/s3_iam_middleware.go
  8. 2
      weed/s3api/s3api_server.go

13
test/s3/iam/iam_config_distributed.json

@ -5,9 +5,6 @@
"issuer": "seaweedfs-sts",
"signingKey": "dGVzdC1zaWduaW5nLWtleS0zMi1jaGFyYWN0ZXJzLWxvbmc=",
"sessionStoreType": "filer",
"sessionStoreConfig": {
"basePath": "/etc/iam/sessions"
},
"providers": [
{
"name": "keycloak-oidc",
@ -38,16 +35,10 @@
},
"policy": {
"defaultEffect": "Deny",
"storeType": "filer",
"storeConfig": {
"basePath": "/etc/iam/policies"
}
"storeType": "filer"
},
"roleStore": {
"storeType": "filer",
"storeConfig": {
"basePath": "/etc/iam/roles"
}
"storeType": "filer"
},
"roles": [

20
weed/iam/integration/iam_integration_test.go

@ -63,7 +63,7 @@ func TestFullOIDCWorkflow(t *testing.T) {
RoleSessionName: tt.sessionName,
}
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, "localhost:8888", assumeRequest)
if !tt.expectedAllow {
assert.Error(t, err)
@ -78,7 +78,7 @@ func TestFullOIDCWorkflow(t *testing.T) {
// Step 2: Test policy enforcement with assumed credentials
if tt.testAction != "" && tt.testResource != "" {
allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
allowed, err := iamManager.IsActionAllowed(ctx, "localhost:8888", &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: tt.testAction,
Resource: tt.testResource,
@ -139,7 +139,7 @@ func TestFullLDAPWorkflow(t *testing.T) {
ProviderName: "test-ldap",
}
response, err := iamManager.AssumeRoleWithCredentials(ctx, assumeRequest)
response, err := iamManager.AssumeRoleWithCredentials(ctx, "localhost:8888", assumeRequest)
if !tt.expectedAllow {
assert.Error(t, err)
@ -152,7 +152,7 @@ func TestFullLDAPWorkflow(t *testing.T) {
// Step 2: Test policy enforcement
if tt.testAction != "" && tt.testResource != "" {
allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
allowed, err := iamManager.IsActionAllowed(ctx, "localhost:8888", &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: tt.testAction,
Resource: tt.testResource,
@ -178,7 +178,7 @@ func TestPolicyEnforcement(t *testing.T) {
RoleSessionName: "policy-test-session",
}
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, "localhost:8888", assumeRequest)
require.NoError(t, err)
sessionToken := response.Credentials.SessionToken
@ -230,7 +230,7 @@ func TestPolicyEnforcement(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
allowed, err := iamManager.IsActionAllowed(ctx, "localhost:8888", &ActionRequest{
Principal: principal,
Action: tt.action,
Resource: tt.resource,
@ -256,13 +256,13 @@ func TestSessionExpiration(t *testing.T) {
DurationSeconds: int64Ptr(900), // 15 minutes
}
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, "localhost:8888", assumeRequest)
require.NoError(t, err)
sessionToken := response.Credentials.SessionToken
// Verify session is initially valid
allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
allowed, err := iamManager.IsActionAllowed(ctx, "localhost:8888", &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::test-bucket/file.txt",
@ -276,11 +276,11 @@ func TestSessionExpiration(t *testing.T) {
assert.True(t, response.Credentials.Expiration.Before(time.Now().Add(16*time.Minute)))
// Test actual session expiration
err = iamManager.ExpireSessionForTesting(ctx, sessionToken)
err = iamManager.ExpireSessionForTesting(ctx, "localhost:8888", sessionToken)
require.NoError(t, err)
// Verify session is now expired and access is denied
allowed, err = iamManager.IsActionAllowed(ctx, &ActionRequest{
allowed, err = iamManager.IsActionAllowed(ctx, "localhost:8888", &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::test-bucket/file.txt",

16
weed/iam/integration/iam_manager.go

@ -174,7 +174,7 @@ func (m *IAMManager) CreateRole(ctx context.Context, roleName string, roleDef *R
}
// AssumeRoleWithWebIdentity assumes a role using web identity (OIDC)
func (m *IAMManager) AssumeRoleWithWebIdentity(ctx context.Context, request *sts.AssumeRoleWithWebIdentityRequest) (*sts.AssumeRoleResponse, error) {
func (m *IAMManager) AssumeRoleWithWebIdentity(ctx context.Context, filerAddress string, request *sts.AssumeRoleWithWebIdentityRequest) (*sts.AssumeRoleResponse, error) {
if !m.initialized {
return nil, fmt.Errorf("IAM manager not initialized")
}
@ -194,11 +194,11 @@ func (m *IAMManager) AssumeRoleWithWebIdentity(ctx context.Context, request *sts
}
// Use STS service to assume the role
return m.stsService.AssumeRoleWithWebIdentity(ctx, request)
return m.stsService.AssumeRoleWithWebIdentity(ctx, filerAddress, request)
}
// AssumeRoleWithCredentials assumes a role using credentials (LDAP)
func (m *IAMManager) AssumeRoleWithCredentials(ctx context.Context, request *sts.AssumeRoleWithCredentialsRequest) (*sts.AssumeRoleResponse, error) {
func (m *IAMManager) AssumeRoleWithCredentials(ctx context.Context, filerAddress string, request *sts.AssumeRoleWithCredentialsRequest) (*sts.AssumeRoleResponse, error) {
if !m.initialized {
return nil, fmt.Errorf("IAM manager not initialized")
}
@ -218,17 +218,17 @@ func (m *IAMManager) AssumeRoleWithCredentials(ctx context.Context, request *sts
}
// Use STS service to assume the role
return m.stsService.AssumeRoleWithCredentials(ctx, request)
return m.stsService.AssumeRoleWithCredentials(ctx, filerAddress, request)
}
// IsActionAllowed checks if a principal is allowed to perform an action on a resource
func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest) (bool, error) {
func (m *IAMManager) IsActionAllowed(ctx context.Context, filerAddress string, request *ActionRequest) (bool, error) {
if !m.initialized {
return false, fmt.Errorf("IAM manager not initialized")
}
// Validate session token first
_, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken)
_, err := m.stsService.ValidateSessionToken(ctx, filerAddress, request.SessionToken)
if err != nil {
return false, fmt.Errorf("invalid session: %w", err)
}
@ -381,10 +381,10 @@ func indexOf(s, substr string) int {
}
// ExpireSessionForTesting manually expires a session for testing purposes
func (m *IAMManager) ExpireSessionForTesting(ctx context.Context, sessionToken string) error {
func (m *IAMManager) ExpireSessionForTesting(ctx context.Context, filerAddress string, sessionToken string) error {
if !m.initialized {
return fmt.Errorf("IAM manager not initialized")
}
return m.stsService.ExpireSessionForTesting(ctx, sessionToken)
return m.stsService.ExpireSessionForTesting(ctx, filerAddress, sessionToken)
}

2
weed/iam/integration/role_store.go

@ -143,7 +143,7 @@ type FilerRoleStore struct {
// NewFilerRoleStore creates a new filer-based role store
func NewFilerRoleStore(config map[string]interface{}) (*FilerRoleStore, error) {
store := &FilerRoleStore{
basePath: "/seaweedfs/iam/roles", // Default path for role storage
basePath: "/etc/iam/roles", // Default path for role storage - aligned with /etc/ convention
}
// Parse configuration

2
weed/iam/policy/policy_store.go

@ -156,7 +156,7 @@ type FilerPolicyStore struct {
// NewFilerPolicyStore creates a new filer-based policy store
func NewFilerPolicyStore(config map[string]interface{}) (*FilerPolicyStore, error) {
store := &FilerPolicyStore{
basePath: "/seaweedfs/iam/policies", // Default path for policy storage
basePath: "/etc/iam/policies", // Default path for policy storage - aligned with /etc/ convention
}
// Parse configuration

2
weed/s3api/s3_end_to_end_test.go

@ -84,7 +84,7 @@ func TestS3EndToEndWithJWT(t *testing.T) {
tt.setupRole(ctx, iamManager)
// Assume role to get JWT token
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, "localhost:8888", &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: tt.roleArn,
WebIdentityToken: "valid-oidc-token",
RoleSessionName: tt.sessionName,

16
weed/s3api/s3_iam_middleware.go

@ -16,15 +16,17 @@ import (
// S3IAMIntegration provides IAM integration for S3 API
type S3IAMIntegration struct {
iamManager *integration.IAMManager
enabled bool
iamManager *integration.IAMManager
filerAddress string
enabled bool
}
// NewS3IAMIntegration creates a new S3 IAM integration
func NewS3IAMIntegration(iamManager *integration.IAMManager) *S3IAMIntegration {
func NewS3IAMIntegration(iamManager *integration.IAMManager, filerAddress string) *S3IAMIntegration {
return &S3IAMIntegration{
iamManager: iamManager,
enabled: iamManager != nil,
iamManager: iamManager,
filerAddress: filerAddress,
enabled: iamManager != nil,
}
}
@ -93,7 +95,7 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ
}
glog.V(0).Infof("AuthenticateJWT: calling IsActionAllowed for principal=%s", principalArn)
allowed, err := s3iam.iamManager.IsActionAllowed(ctx, testRequest)
allowed, err := s3iam.iamManager.IsActionAllowed(ctx, s3iam.filerAddress, testRequest)
glog.V(0).Infof("AuthenticateJWT: IsActionAllowed returned allowed=%t, err=%v", allowed, err)
if err != nil || !allowed {
glog.V(0).Infof("IAM validation failed for %s: %v", principalArn, err)
@ -145,7 +147,7 @@ func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IA
}
// Check if action is allowed using our policy engine
allowed, err := s3iam.iamManager.IsActionAllowed(ctx, actionRequest)
allowed, err := s3iam.iamManager.IsActionAllowed(ctx, s3iam.filerAddress, actionRequest)
if err != nil {
// Log the error but treat authentication/authorization failures as access denied
// rather than internal errors to provide better user experience

2
weed/s3api/s3api_server.go

@ -107,7 +107,7 @@ func NewS3ApiServerWithStore(router *mux.Router, option *S3ApiServerOption, expl
glog.Errorf("Failed to load IAM configuration: %v", err)
} else {
// Create S3 IAM integration with the loaded IAM manager
s3iam := NewS3IAMIntegration(iamManager)
s3iam := NewS3IAMIntegration(iamManager, string(option.Filer))
// Set IAM integration in server
s3ApiServer.iamIntegration = s3iam

Loading…
Cancel
Save