* 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
185 lines
5.3 KiB
Go
185 lines
5.3 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/credential"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
|
)
|
|
|
|
func (store *IamGrpcStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) {
|
|
var config *iam_pb.S3ApiConfiguration
|
|
err := store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
resp, err := client.GetConfiguration(ctx, &iam_pb.GetConfigurationRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config = resp.Configuration
|
|
return nil
|
|
})
|
|
return config, err
|
|
}
|
|
|
|
func (store *IamGrpcStore) SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.PutConfiguration(ctx, &iam_pb.PutConfigurationRequest{
|
|
Configuration: config,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *IamGrpcStore) CreateUser(ctx context.Context, identity *iam_pb.Identity) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.CreateUser(ctx, &iam_pb.CreateUserRequest{
|
|
Identity: identity,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *IamGrpcStore) GetUser(ctx context.Context, username string) (*iam_pb.Identity, error) {
|
|
var identity *iam_pb.Identity
|
|
err := store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
resp, err := client.GetUser(ctx, &iam_pb.GetUserRequest{
|
|
Username: username,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
identity = resp.Identity
|
|
return nil
|
|
})
|
|
return identity, err
|
|
}
|
|
|
|
func (store *IamGrpcStore) UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.UpdateUser(ctx, &iam_pb.UpdateUserRequest{
|
|
Username: username,
|
|
Identity: identity,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *IamGrpcStore) DeleteUser(ctx context.Context, username string) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.DeleteUser(ctx, &iam_pb.DeleteUserRequest{
|
|
Username: username,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *IamGrpcStore) ListUsers(ctx context.Context) ([]string, error) {
|
|
var usernames []string
|
|
err := store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
resp, err := client.ListUsers(ctx, &iam_pb.ListUsersRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
usernames = resp.Usernames
|
|
return nil
|
|
})
|
|
return usernames, err
|
|
}
|
|
|
|
func (store *IamGrpcStore) GetUserByAccessKey(ctx context.Context, accessKey string) (*iam_pb.Identity, error) {
|
|
var identity *iam_pb.Identity
|
|
err := store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
resp, err := client.GetUserByAccessKey(ctx, &iam_pb.GetUserByAccessKeyRequest{
|
|
AccessKey: accessKey,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
identity = resp.Identity
|
|
return nil
|
|
})
|
|
return identity, err
|
|
}
|
|
|
|
func (store *IamGrpcStore) CreateAccessKey(ctx context.Context, username string, credential *iam_pb.Credential) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.CreateAccessKey(ctx, &iam_pb.CreateAccessKeyRequest{
|
|
Username: username,
|
|
Credential: credential,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (store *IamGrpcStore) DeleteAccessKey(ctx context.Context, username string, accessKey string) error {
|
|
return store.withIamClient(func(client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.DeleteAccessKey(ctx, &iam_pb.DeleteAccessKeyRequest{
|
|
Username: username,
|
|
AccessKey: accessKey,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
// AttachUserPolicy attaches a managed policy to a user by policy name
|
|
func (store *IamGrpcStore) AttachUserPolicy(ctx context.Context, username string, policyName string) error {
|
|
// Get current user
|
|
identity, err := store.GetUser(ctx, username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Verify policy exists
|
|
policy, err := store.GetPolicy(ctx, policyName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if policy == nil {
|
|
return credential.ErrPolicyNotFound
|
|
}
|
|
|
|
// Check if already attached
|
|
for _, p := range identity.PolicyNames {
|
|
if p == policyName {
|
|
// Already attached - return success (idempotent)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
identity.PolicyNames = append(identity.PolicyNames, policyName)
|
|
return store.UpdateUser(ctx, username, identity)
|
|
}
|
|
|
|
// DetachUserPolicy detaches a managed policy from a user
|
|
func (store *IamGrpcStore) DetachUserPolicy(ctx context.Context, username string, policyName string) error {
|
|
identity, err := store.GetUser(ctx, username)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
found := false
|
|
var newPolicies []string
|
|
for _, p := range identity.PolicyNames {
|
|
if p == policyName {
|
|
found = true
|
|
} else {
|
|
newPolicies = append(newPolicies, p)
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return credential.ErrPolicyNotAttached
|
|
}
|
|
|
|
identity.PolicyNames = newPolicies
|
|
return store.UpdateUser(ctx, username, identity)
|
|
}
|
|
|
|
// ListAttachedUserPolicies returns the list of policy names attached to a user
|
|
func (store *IamGrpcStore) ListAttachedUserPolicies(ctx context.Context, username string) ([]string, error) {
|
|
identity, err := store.GetUser(ctx, username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return identity.PolicyNames, nil
|
|
}
|