From ce0a3bf6a664d36979d39d9b6b3fe0164490d621 Mon Sep 17 00:00:00 2001 From: Anurag Mittal Date: Tue, 10 Dec 2024 18:34:25 +0100 Subject: [PATCH] COSI-40: revoke-bucket-access-api-implementation - Gets parameters from the bucket object to get config for IAM client - Adds methods to IAM client to delete user, inline policy and keys - If IAM user doesn't exist, logs a warning and continues - If inline policy doesn't exist, logs a warning and continues --- pkg/clients/iam/iam_client.go | 101 ++++++++++++++++++++++++++ pkg/driver/provisioner_server_impl.go | 35 ++++++++- 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/pkg/clients/iam/iam_client.go b/pkg/clients/iam/iam_client.go index 75806965..9a1955ca 100644 --- a/pkg/clients/iam/iam_client.go +++ b/pkg/clients/iam/iam_client.go @@ -2,6 +2,7 @@ package iamclient import ( "context" + "errors" "fmt" "net/http" "os" @@ -10,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/smithy-go/logging" "github.com/scality/cosi-driver/pkg/util" "k8s.io/klog/v2" @@ -20,6 +22,11 @@ type IAMAPI interface { CreateUser(ctx context.Context, input *iam.CreateUserInput, opts ...func(*iam.Options)) (*iam.CreateUserOutput, error) PutUserPolicy(ctx context.Context, input *iam.PutUserPolicyInput, opts ...func(*iam.Options)) (*iam.PutUserPolicyOutput, error) CreateAccessKey(ctx context.Context, input *iam.CreateAccessKeyInput, opts ...func(*iam.Options)) (*iam.CreateAccessKeyOutput, error) + GetUser(ctx context.Context, input *iam.GetUserInput, opts ...func(*iam.Options)) (*iam.GetUserOutput, error) + DeleteUserPolicy(ctx context.Context, input *iam.DeleteUserPolicyInput, opts ...func(*iam.Options)) (*iam.DeleteUserPolicyOutput, error) + ListAccessKeys(ctx context.Context, input *iam.ListAccessKeysInput, opts ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error) + DeleteAccessKey(ctx context.Context, input *iam.DeleteAccessKeyInput, opts ...func(*iam.Options)) (*iam.DeleteAccessKeyOutput, error) + DeleteUser(ctx context.Context, input *iam.DeleteUserInput, opts ...func(*iam.Options)) (*iam.DeleteUserOutput, error) } type IAMClient struct { @@ -145,3 +152,97 @@ func (client *IAMClient) CreateBucketAccess(ctx context.Context, userName, bucke return accessKeyOutput, nil } + +// RevokeBucketAccess is a helper that revokes bucket access by orchestrating individual steps to delete the user, inline policy, and access keys. +func (client *IAMClient) RevokeBucketAccess(ctx context.Context, userName, bucketName string) error { + // Check if the user exists. + err := client.EnsureUserExists(ctx, userName) + if err != nil { + return err + } + + // Delete the inline policy associated with the bucket. + err = client.DeleteInlinePolicy(ctx, userName, bucketName) + if err != nil { + return err + } + + // Delete all access keys for the user. + err = client.DeleteAllAccessKeys(ctx, userName) + if err != nil { + return err + } + + // Delete the user. + err = client.DeleteUser(ctx, userName) + if err != nil { + return err + } + + klog.InfoS("Successfully revoked bucket access", "user", userName, "bucket", bucketName) + return nil +} + +func (client *IAMClient) EnsureUserExists(ctx context.Context, userName string) error { + _, err := client.IAMService.GetUser(ctx, &iam.GetUserInput{UserName: &userName}) + if err != nil { + var noSuchEntityErr *types.NoSuchEntityException + if errors.As(err, &noSuchEntityErr) { + klog.InfoS("IAM user does not exist, nothing to revoke", "user", userName) + return nil + } + return fmt.Errorf("failed to get IAM user %s: %w", userName, err) + } + return nil +} + +func (client *IAMClient) DeleteInlinePolicy(ctx context.Context, userName, bucketName string) error { + _, err := client.IAMService.DeleteUserPolicy(ctx, &iam.DeleteUserPolicyInput{ + UserName: &userName, + PolicyName: &bucketName, + }) + if err != nil { + var noSuchEntityErr *types.NoSuchEntityException + if errors.As(err, &noSuchEntityErr) { + klog.V(3).InfoS("Inline policy does not exist, skipping deletion", "user", userName, "policyName", bucketName) + return nil + } + return fmt.Errorf("failed to delete inline policy %s for user %s: %w", bucketName, userName, err) + } + klog.InfoS("Successfully deleted inline policy", "user", userName, "policyName", bucketName) + return nil +} + +func (client *IAMClient) DeleteAllAccessKeys(ctx context.Context, userName string) error { + listKeysOutput, err := client.IAMService.ListAccessKeys(ctx, &iam.ListAccessKeysInput{UserName: &userName}) + if err != nil { + return fmt.Errorf("failed to list access keys for IAM user %s: %w", userName, err) + } + + for _, key := range listKeysOutput.AccessKeyMetadata { + _, err := client.IAMService.DeleteAccessKey(ctx, &iam.DeleteAccessKeyInput{ + UserName: &userName, + AccessKeyId: key.AccessKeyId, + }) + if err != nil { + return fmt.Errorf("failed to delete access key %s for IAM user %s: %w", *key.AccessKeyId, userName, err) + } + klog.V(5).InfoS("Successfully deleted access key", "user", userName, "accessKeyId", *key.AccessKeyId) + } + klog.InfoS("Successfully deleted all access keys", "user", userName) + return nil +} + +func (client *IAMClient) DeleteUser(ctx context.Context, userName string) error { + _, err := client.IAMService.DeleteUser(ctx, &iam.DeleteUserInput{UserName: &userName}) + if err != nil { + var noSuchEntityErr *types.NoSuchEntityException + if errors.As(err, &noSuchEntityErr) { + klog.InfoS("IAM user does not exist, skipping deletion", "user", userName) + return nil // User doesn't exist, nothing to delete + } + return fmt.Errorf("failed to delete IAM user %s: %w", userName, err) + } + klog.InfoS("Successfully deleted IAM user", "user", userName) + return nil +} diff --git a/pkg/driver/provisioner_server_impl.go b/pkg/driver/provisioner_server_impl.go index abb776ff..8e3084be 100644 --- a/pkg/driver/provisioner_server_impl.go +++ b/pkg/driver/provisioner_server_impl.go @@ -322,5 +322,38 @@ func (s *ProvisionerServer) DriverGrantBucketAccess(ctx context.Context, func (s *ProvisionerServer) DriverRevokeBucketAccess(ctx context.Context, req *cosiapi.DriverRevokeBucketAccessRequest) (*cosiapi.DriverRevokeBucketAccessResponse, error) { - return nil, status.Error(codes.Unimplemented, "DriverCreateBucket: not implemented") + bucketName := req.GetBucketId() + userName := req.GetAccountId() + + klog.V(3).InfoS("Received DriverRevokeBucketAccess request", "bucketName", bucketName, "userName", userName) + + // Fetch the bucket to retrieve parameters + bucket, err := s.BucketClientset.ObjectstorageV1alpha1().Buckets().Get(ctx, bucketName, metav1.GetOptions{}) + if err != nil { + klog.ErrorS(err, "Failed to get bucket object from kubernetes", "bucketName", bucketName) + return nil, status.Error(codes.Internal, "failed to get bucket object from kubernetes") + } + + parameters := bucket.Spec.Parameters + + client, _, err := InitializeClient(ctx, s.Clientset, parameters, "IAM") + if err != nil { + klog.ErrorS(err, "Failed to initialize IAM client", "bucketName", bucketName, "userName", userName) + return nil, status.Error(codes.Internal, "failed to initialize object storage provider IAM client") + } + + iamClient, ok := client.(*iamclient.IAMClient) + if !ok { + klog.ErrorS(nil, "Unsupported client type for revoking bucket access", "bucketName", bucketName, "userName", userName) + return nil, status.Error(codes.Internal, "unsupported client type for IAM operations") + } + + err = iamClient.RevokeBucketAccess(ctx, userName, bucketName) + if err != nil { + klog.ErrorS(err, "Failed to revoke bucket access", "bucketName", bucketName, "userName", userName) + return nil, status.Error(codes.Internal, "failed to revoke bucket access") + } + + klog.V(3).InfoS("Successfully revoked bucket access", "bucketName", bucketName, "userName", userName) + return &cosiapi.DriverRevokeBucketAccessResponse{}, nil }