mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-24 06:50:40 +02:00
* s3api: persist single-identity IAM changes through the credential manager CreateAccessKey, DeleteAccessKey, UpdateAccessKey, PutUserPolicy, DeleteUserPolicy, SetUserStatus, UpdateUser and DeleteUser used to load the whole configuration, change one identity and then SaveConfiguration, rewriting and re-propagating every identity. They now write only the identity (and, for rename/delete, the groups and service accounts that referenced it) through the credential manager, which propagates that identity alone. Static identities and skipPersist keep the previous full-save/no-write contract. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * iamapi: persist single-identity IAM changes through the credential manager Same targeted-write change as the embedded IAM API for the standalone weed iam dispatcher: UpdateUser, DeleteUser, CreateAccessKey, DeleteAccessKey, UpdateAccessKey, PutUserPolicy and DeleteUserPolicy write one identity through the credential manager instead of saving the whole configuration. CreateAccessKey keeps the full save when the handler created a user the store does not know. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * credential/memory: re-key identity on UpdateUser rename UpdateUser stored the identity under the old username key even when identity.Name changed. Re-key it the way filer_etc does (delete old, save new) so the targeted rename path sees the moved record. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: cover targeted single-identity IAM writes Recording-store tests assert each rerouted action uses the single-identity credential store methods and never SaveConfiguration, in both the embedded S3 IAM dispatcher and the standalone iamapi dispatcher, plus the skipPersist and static-identity contracts and a memory-store rename case. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * credential: keep not-found and rename-collision semantics in stores IamGrpcStore.DeleteAccessKey translates gRPC NotFound to ErrAccessKeyNotFound so a repeat delete stays the no-op the IAM handlers advertise. MemoryStore.UpdateUser rejects a rename onto an existing user instead of overwriting it, matching RenameUser. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
201 lines
6.2 KiB
Go
201 lines
6.2 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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
|
|
})
|
|
// The filer-side handler returns gRPC NotFound when the user is absent;
|
|
// translate back to the package sentinel so callers can use
|
|
// errors.Is(err, credential.ErrUserNotFound) uniformly across stores.
|
|
if err != nil && status.Code(err) == codes.NotFound {
|
|
return nil, credential.ErrUserNotFound
|
|
}
|
|
return identity, err
|
|
}
|
|
|
|
func (store *IamGrpcStore) UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error {
|
|
return store.withIamClient(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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(ctx, func(ctx context.Context, 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 {
|
|
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
|
_, err := client.DeleteAccessKey(ctx, &iam_pb.DeleteAccessKeyRequest{
|
|
Username: username,
|
|
AccessKey: accessKey,
|
|
})
|
|
return err
|
|
})
|
|
// The filer-side handler returns gRPC NotFound when the user or the key is
|
|
// absent; translate back to the sentinel so callers can use
|
|
// errors.Is(err, credential.ErrAccessKeyNotFound) uniformly across stores.
|
|
if err != nil && status.Code(err) == codes.NotFound {
|
|
return credential.ErrAccessKeyNotFound
|
|
}
|
|
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
|
|
}
|