s3api: add AttachUserPolicy/DetachUserPolicy/ListAttachedUserPolicies (#8379)
* iam: add XML responses for managed user policy APIs * s3api: implement attach/detach/list attached user policies * s3api: add embedded IAM tests for managed user policies * iam: update CredentialStore interface and Manager for managed policies Updated the `CredentialStore` interface to include `AttachUserPolicy`, `DetachUserPolicy`, and `ListAttachedUserPolicies` methods. The `CredentialManager` was updated to delegate these calls to the store. Added common error variables for policy management. * iam: implement managed policy methods in MemoryStore Implemented `AttachUserPolicy`, `DetachUserPolicy`, and `ListAttachedUserPolicies` in the MemoryStore. Also ensured deep copying of identities includes PolicyNames. * iam: implement managed policy methods in PostgresStore Modified Postgres schema to include `policy_names` JSONB column in `users`. Implemented `AttachUserPolicy`, `DetachUserPolicy`, and `ListAttachedUserPolicies`. Updated user CRUD operations to handle policy names persistence. * iam: implement managed policy methods in remaining stores Implemented user policy management in: - `FilerEtcStore` (partial implementation) - `IamGrpcStore` (delegated via GetUser/UpdateUser) - `PropagatingCredentialStore` (to broadcast updates) Ensures cluster-wide consistency for policy attachments. * s3api: refactor EmbeddedIamApi to use managed policy APIs - Refactored `AttachUserPolicy`, `DetachUserPolicy`, and `ListAttachedUserPolicies` to use `e.credentialManager` directly. - Fixed a critical error suppression bug in `ExecuteAction` that always returned success even on failure. - Implemented robust error matching using string comparison fallbacks. - Improved consistency by reloading configuration after policy changes. * s3api: update and refine IAM integration tests - Updated tests to use a real `MemoryStore`-backed `CredentialManager`. - Refined test configuration synchronization using `sync.Once` and manual deep-copying to prevent state corruption. - Improved `extractEmbeddedIamErrorCodeAndMessage` to handle more XML formats robustly. - Adjusted test expectations to match current AWS IAM behavior. * fix compilation * visibility * ensure 10 policies * reload * add integration tests * Guard raft command registration * Allow IAM actions in policy tests * Validate gRPC policy attachments * Revert Validate gRPC policy attachments * Tighten gRPC policy attach/detach * Improve IAM managed policy handling * Improve managed policy filters
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
||||
)
|
||||
|
||||
func (store *MemoryStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) {
|
||||
@@ -21,9 +22,16 @@ func (store *MemoryStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiC
|
||||
|
||||
// Convert all users to identities
|
||||
for _, user := range store.users {
|
||||
// Deep copy the identity to avoid mutation issues
|
||||
identityCopy := store.deepCopyIdentity(user)
|
||||
config.Identities = append(config.Identities, identityCopy)
|
||||
config.Identities = append(config.Identities, store.deepCopyIdentity(user))
|
||||
}
|
||||
|
||||
// Add all policies
|
||||
for name, doc := range store.policies {
|
||||
content, _ := json.Marshal(doc)
|
||||
config.Policies = append(config.Policies, &iam_pb.Policy{
|
||||
Name: name,
|
||||
Content: string(content),
|
||||
})
|
||||
}
|
||||
|
||||
return config, nil
|
||||
@@ -40,12 +48,11 @@ func (store *MemoryStore) SaveConfiguration(ctx context.Context, config *iam_pb.
|
||||
// Clear existing data
|
||||
store.users = make(map[string]*iam_pb.Identity)
|
||||
store.accessKeys = make(map[string]string)
|
||||
store.policies = make(map[string]policy_engine.PolicyDocument)
|
||||
|
||||
// Add all identities
|
||||
for _, identity := range config.Identities {
|
||||
// Deep copy to avoid mutation issues
|
||||
identityCopy := store.deepCopyIdentity(identity)
|
||||
store.users[identity.Name] = identityCopy
|
||||
store.users[identity.Name] = store.deepCopyIdentity(identity)
|
||||
|
||||
// Index access keys
|
||||
for _, credential := range identity.Credentials {
|
||||
@@ -53,6 +60,14 @@ func (store *MemoryStore) SaveConfiguration(ctx context.Context, config *iam_pb.
|
||||
}
|
||||
}
|
||||
|
||||
// Add all policies
|
||||
for _, policy := range config.Policies {
|
||||
var doc policy_engine.PolicyDocument
|
||||
if err := json.Unmarshal([]byte(policy.Content), &doc); err == nil {
|
||||
store.policies[policy.Name] = doc
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -284,6 +299,7 @@ func (store *MemoryStore) deepCopyIdentity(identity *iam_pb.Identity) *iam_pb.Id
|
||||
Account: identity.Account,
|
||||
Credentials: identity.Credentials,
|
||||
Actions: identity.Actions,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +311,91 @@ func (store *MemoryStore) deepCopyIdentity(identity *iam_pb.Identity) *iam_pb.Id
|
||||
Account: identity.Account,
|
||||
Credentials: identity.Credentials,
|
||||
Actions: identity.Actions,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
}
|
||||
|
||||
return ©
|
||||
}
|
||||
|
||||
// AttachUserPolicy attaches a managed policy to a user by policy name
|
||||
func (store *MemoryStore) AttachUserPolicy(ctx context.Context, username string, policyName string) error {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
|
||||
if !store.initialized {
|
||||
return fmt.Errorf("store not initialized")
|
||||
}
|
||||
|
||||
user, exists := store.users[username]
|
||||
if !exists {
|
||||
return credential.ErrUserNotFound
|
||||
}
|
||||
|
||||
// Verify policy exists
|
||||
if _, exists := store.policies[policyName]; !exists {
|
||||
return credential.ErrPolicyNotFound
|
||||
}
|
||||
|
||||
// Check if already attached
|
||||
for _, p := range user.PolicyNames {
|
||||
if p == policyName {
|
||||
return credential.ErrPolicyAlreadyAttached
|
||||
}
|
||||
}
|
||||
|
||||
user.PolicyNames = append(user.PolicyNames, policyName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetachUserPolicy detaches a managed policy from a user
|
||||
func (store *MemoryStore) DetachUserPolicy(ctx context.Context, username string, policyName string) error {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
|
||||
if !store.initialized {
|
||||
return fmt.Errorf("store not initialized")
|
||||
}
|
||||
|
||||
user, exists := store.users[username]
|
||||
if !exists {
|
||||
return credential.ErrUserNotFound
|
||||
}
|
||||
|
||||
found := false
|
||||
var newPolicies []string
|
||||
for _, p := range user.PolicyNames {
|
||||
if p == policyName {
|
||||
found = true
|
||||
} else {
|
||||
newPolicies = append(newPolicies, p)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return credential.ErrPolicyNotAttached
|
||||
}
|
||||
|
||||
user.PolicyNames = newPolicies
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAttachedUserPolicies returns the list of policy names attached to a user
|
||||
func (store *MemoryStore) ListAttachedUserPolicies(ctx context.Context, username string) ([]string, error) {
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
|
||||
if !store.initialized {
|
||||
return nil, fmt.Errorf("store not initialized")
|
||||
}
|
||||
|
||||
user, exists := store.users[username]
|
||||
if !exists {
|
||||
return nil, credential.ErrUserNotFound
|
||||
}
|
||||
|
||||
// Return copy to prevent mutation
|
||||
result := make([]string, len(user.PolicyNames))
|
||||
copy(result, user.PolicyNames)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user