Browse Source

address comments

pull/7160/head
chrislu 5 months ago
parent
commit
e9f83af6e2
  1. 2
      test/s3/iam/s3_iam_distributed_test.go
  2. 5
      test/s3/iam/s3_iam_framework.go
  3. 12
      weed/iam/integration/role_store.go
  4. 18
      weed/iam/sts/provider_factory.go
  5. 58
      weed/iam/sts/provider_factory_test.go
  6. 14
      weed/iam/sts/sts_service.go
  7. 12
      weed/iam/sts/token_utils.go
  8. 5
      weed/iam/utils/arn_utils.go
  9. 2
      weed/s3api/s3_iam_simple_test.go

2
test/s3/iam/s3_iam_distributed_test.go

@ -132,7 +132,7 @@ func TestS3IAMDistributedTests(t *testing.T) {
var lastErr error var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ { for attempt := 0; attempt <= maxRetries; attempt++ {
if attempt > 0 { if attempt > 0 {
time.Sleep(retryDelay * time.Duration(attempt)) // Exponential backoff
time.Sleep(retryDelay * time.Duration(attempt)) // Linear backoff
} }
lastErr = operation() lastErr = operation()

5
test/s3/iam/s3_iam_framework.go

@ -390,9 +390,8 @@ func (f *S3IAMTestFramework) generateSTSSessionToken(username, roleName string,
return "", err return "", err
} }
// Note: In a real implementation, we would also create the session in the STS session store
// For now, we'll rely on the fact that if JWT validation passes, the session should be considered valid
// This is a limitation of our current testing approach
// The generated JWT is self-contained and includes all necessary session information.
// The stateless design of the STS service means no external session storage is required.
return tokenString, nil return tokenString, nil
} }

12
weed/iam/integration/role_store.go

@ -317,11 +317,21 @@ func (f *FilerRoleStore) DeleteRole(ctx context.Context, filerAddress string, ro
} }
glog.V(3).Infof("Deleting role %s", roleName) glog.V(3).Infof("Deleting role %s", roleName)
_, err := client.DeleteEntry(ctx, request)
resp, err := client.DeleteEntry(ctx, request)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil // Idempotent: deletion of non-existent role is successful
}
return fmt.Errorf("failed to delete role %s: %v", roleName, err) return fmt.Errorf("failed to delete role %s: %v", roleName, err)
} }
if resp.Error != "" {
if strings.Contains(resp.Error, "not found") {
return nil // Idempotent: deletion of non-existent role is successful
}
return fmt.Errorf("failed to delete role %s: %s", roleName, resp.Error)
}
return nil return nil
}) })
} }

18
weed/iam/sts/provider_factory.go

@ -133,23 +133,29 @@ func (f *ProviderFactory) convertToOIDCConfig(configMap map[string]interface{})
// Convert scopes array // Convert scopes array
if scopesInterface, ok := configMap[ConfigFieldScopes]; ok { if scopesInterface, ok := configMap[ConfigFieldScopes]; ok {
if scopes, err := f.convertToStringSlice(scopesInterface); err == nil {
config.Scopes = scopes
scopes, err := f.convertToStringSlice(scopesInterface)
if err != nil {
return nil, fmt.Errorf("failed to convert scopes: %w", err)
} }
config.Scopes = scopes
} }
// Convert claims mapping // Convert claims mapping
if claimsMapInterface, ok := configMap["claimsMapping"]; ok { if claimsMapInterface, ok := configMap["claimsMapping"]; ok {
if claimsMap, err := f.convertToStringMap(claimsMapInterface); err == nil {
config.ClaimsMapping = claimsMap
claimsMap, err := f.convertToStringMap(claimsMapInterface)
if err != nil {
return nil, fmt.Errorf("failed to convert claimsMapping: %w", err)
} }
config.ClaimsMapping = claimsMap
} }
// Convert role mapping // Convert role mapping
if roleMappingInterface, ok := configMap["roleMapping"]; ok { if roleMappingInterface, ok := configMap["roleMapping"]; ok {
if roleMapping, err := f.convertToRoleMapping(roleMappingInterface); err == nil {
config.RoleMapping = roleMapping
roleMapping, err := f.convertToRoleMapping(roleMappingInterface)
if err != nil {
return nil, fmt.Errorf("failed to convert roleMapping: %w", err)
} }
config.RoleMapping = roleMapping
} }
glog.V(3).Infof("Converted OIDC config: issuer=%s, clientId=%s, jwksUri=%s", glog.V(3).Infof("Converted OIDC config: issuer=%s, clientId=%s, jwksUri=%s",

58
weed/iam/sts/provider_factory_test.go

@ -198,6 +198,64 @@ func TestProviderFactory_ConvertToStringSlice(t *testing.T) {
}) })
} }
func TestProviderFactory_ConfigConversionErrors(t *testing.T) {
factory := NewProviderFactory()
t.Run("invalid scopes type", func(t *testing.T) {
config := &ProviderConfig{
Name: "invalid-scopes",
Type: "oidc",
Enabled: true,
Config: map[string]interface{}{
"issuer": "https://test-issuer.com",
"clientId": "test-client",
"scopes": "invalid-not-array", // Should be array
},
}
provider, err := factory.CreateProvider(config)
assert.Error(t, err)
assert.Nil(t, provider)
assert.Contains(t, err.Error(), "failed to convert scopes")
})
t.Run("invalid claimsMapping type", func(t *testing.T) {
config := &ProviderConfig{
Name: "invalid-claims",
Type: "oidc",
Enabled: true,
Config: map[string]interface{}{
"issuer": "https://test-issuer.com",
"clientId": "test-client",
"claimsMapping": "invalid-not-map", // Should be map
},
}
provider, err := factory.CreateProvider(config)
assert.Error(t, err)
assert.Nil(t, provider)
assert.Contains(t, err.Error(), "failed to convert claimsMapping")
})
t.Run("invalid roleMapping type", func(t *testing.T) {
config := &ProviderConfig{
Name: "invalid-roles",
Type: "oidc",
Enabled: true,
Config: map[string]interface{}{
"issuer": "https://test-issuer.com",
"clientId": "test-client",
"roleMapping": "invalid-not-map", // Should be map
},
}
provider, err := factory.CreateProvider(config)
assert.Error(t, err)
assert.Nil(t, provider)
assert.Contains(t, err.Error(), "failed to convert roleMapping")
})
}
func TestProviderFactory_ConvertToStringMap(t *testing.T) { func TestProviderFactory_ConvertToStringMap(t *testing.T) {
factory := NewProviderFactory() factory := NewProviderFactory()

14
weed/iam/sts/sts_service.go

@ -641,8 +641,13 @@ func (s *STSService) validateRoleAssumptionForWebIdentity(ctx context.Context, r
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix) return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
} }
// For testing, reject non-existent roles
// Extract role name and validate ARN format
roleName := extractRoleNameFromArn(roleArn) roleName := extractRoleNameFromArn(roleArn)
if roleName == "" {
return fmt.Errorf("invalid role ARN format: %s", roleArn)
}
// For testing, reject non-existent roles
if roleName == "NonExistentRole" { if roleName == "NonExistentRole" {
return fmt.Errorf("role does not exist: %s", roleName) return fmt.Errorf("role does not exist: %s", roleName)
} }
@ -678,8 +683,13 @@ func (s *STSService) validateRoleAssumptionForCredentials(ctx context.Context, r
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix) return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
} }
// For testing, reject non-existent roles
// Extract role name and validate ARN format
roleName := extractRoleNameFromArn(roleArn) roleName := extractRoleNameFromArn(roleArn)
if roleName == "" {
return fmt.Errorf("invalid role ARN format: %s", roleArn)
}
// For testing, reject non-existent roles
if roleName == "NonExistentRole" { if roleName == "NonExistentRole" {
return fmt.Errorf("role does not exist: %s", roleName) return fmt.Errorf("role does not exist: %s", roleName)
} }

12
weed/iam/sts/token_utils.go

@ -6,6 +6,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
@ -207,15 +208,20 @@ func GenerateSessionId() (string, error) {
func GenerateAssumedRoleArn(roleArn, sessionName string) string { func GenerateAssumedRoleArn(roleArn, sessionName string) string {
// Convert role ARN to assumed role user ARN // Convert role ARN to assumed role user ARN
// arn:seaweed:iam::role/RoleName -> arn:seaweed:sts::assumed-role/RoleName/SessionName // arn:seaweed:iam::role/RoleName -> arn:seaweed:sts::assumed-role/RoleName/SessionName
return fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", extractRoleNameFromArn(roleArn), sessionName)
roleName := extractRoleNameFromArn(roleArn)
if roleName == "" {
// This should not happen if validation is done properly upstream
return fmt.Sprintf("arn:seaweed:sts::assumed-role/INVALID-ARN/%s", sessionName)
}
return fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleName, sessionName)
} }
// extractRoleNameFromArn extracts the role name from a role ARN // extractRoleNameFromArn extracts the role name from a role ARN
func extractRoleNameFromArn(roleArn string) string { func extractRoleNameFromArn(roleArn string) string {
// Simple extraction for arn:seaweed:iam::role/RoleName // Simple extraction for arn:seaweed:iam::role/RoleName
prefix := "arn:seaweed:iam::role/" prefix := "arn:seaweed:iam::role/"
if len(roleArn) > len(prefix) && roleArn[:len(prefix)] == prefix {
if strings.HasPrefix(roleArn, prefix) && len(roleArn) > len(prefix) {
return roleArn[len(prefix):] return roleArn[len(prefix):]
} }
return "UnknownRole"
return ""
} }

5
weed/iam/utils/arn_utils.go

@ -23,6 +23,7 @@ func ExtractRoleNameFromPrincipal(principal string) string {
return principal[len(iamPrefix):] return principal[len(iamPrefix):]
} }
// For backwards compatibility, return original principal if no recognized format
return principal
// Return empty string to signal invalid ARN format
// This allows callers to handle the error explicitly instead of masking it
return ""
} }

2
weed/s3api/s3_iam_simple_test.go

@ -451,7 +451,7 @@ func TestExtractRoleNameFromPrincipal(t *testing.T) {
{ {
name: "invalid format", name: "invalid format",
principal: "invalid-principal", principal: "invalid-principal",
expected: "invalid-principal", // Returns original on failure
expected: "", // Returns empty string to signal invalid format
}, },
{ {
name: "missing session name", name: "missing session name",

Loading…
Cancel
Save