Skip to content

Commit

Permalink
COSI-40: revoke-bucket-access-api-implementation
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
anurag4DSB committed Dec 10, 2024
1 parent f7e6555 commit ce0a3bf
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 1 deletion.
101 changes: 101 additions & 0 deletions pkg/clients/iam/iam_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package iamclient

import (
"context"
"errors"
"fmt"
"net/http"
"os"
Expand All @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
35 changes: 34 additions & 1 deletion pkg/driver/provisioner_server_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit ce0a3bf

Please sign in to comment.