mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-19 05:36:58 +00:00
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
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user