Skip to content

Commit

Permalink
Merge pull request #57 from scality/feature/COSI-40-DriverRevokeBucke…
Browse files Browse the repository at this point in the history
…tAccess-api

COSI-40, COSI-52: Revoke Bucket Access API Implementation and Tests
  • Loading branch information
anurag4DSB authored Dec 13, 2024
2 parents 7bc2de2 + 07a2202 commit daa6db6
Show file tree
Hide file tree
Showing 8 changed files with 615 additions and 24 deletions.
3 changes: 1 addition & 2 deletions .github/scripts/cleanup_cosi_resources.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ for BUCKET_NAME in $BUCKET_NAMES; do
log_and_run kubectl patch bucket "$BUCKET_NAME" -p '{"metadata":{"finalizers":[]}}' --type=merge || { echo "Finalizers not found for bucket: $BUCKET_NAME" | tee -a "$LOG_FILE"; }
done

log_and_run echo "Deleting Bucket Access and Bucket Access Class..."
log_and_run kubectl delete -f cosi-examples/bucketaccess.yaml || { echo "No BucketAccess resources found." | tee -a "$LOG_FILE"; }
log_and_run echo "Deleting Bucket Access Class..."
log_and_run kubectl delete -f cosi-examples/bucketaccessclass.yaml --all || { echo "No BucketAccessClass resources found." | tee -a "$LOG_FILE"; }

log_and_run echo "Deleting Bucket Class and Bucket Claim..."
Expand Down
29 changes: 28 additions & 1 deletion .github/scripts/e2e_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ log_and_run echo "IAM user found: $IAM_USER_NAME"

log_and_run echo "Verifying inline policy attached to IAM user..."
INLINE_POLICY="$(aws --endpoint-url "$IAM_ENDPOINT" iam list-user-policies --user-name "$IAM_USER_NAME" --query "PolicyNames[0]" --output text)"
EXPECTED_INLINE_POLICY="$BUCKET_FOUND-cosi-ba"
EXPECTED_INLINE_POLICY="$BUCKET_FOUND"

if [[ "$INLINE_POLICY" != "$EXPECTED_INLINE_POLICY" ]]; then
log_and_run echo "Inline policy '$INLINE_POLICY' does not match expected bucket name '$EXPECTED_INLINE_POLICY'."
Expand Down Expand Up @@ -212,4 +212,31 @@ if [[ "$ACTUAL_PROTOCOLS" != "$EXPECTED_PROTOCOLS" ]]; then
exit 1
fi

# Step 11: Delete Bucket Access Resource
log_and_run echo "Deleting Bucket Access Resource..."
log_and_run kubectl delete -f cosi-examples/bucketaccess.yaml

# Step 12: Verify IAM User Deletion
log_and_run echo "Verifying IAM user '$IAM_USER_NAME' deletion..."
log_and_run aws --endpoint-url "$IAM_ENDPOINT" iam get-user --user-name "$IAM_USER_NAME"

# Retry logic for checking user deletion

for ((i=1; i<=$ATTEMPTS; i++)); do
USER_EXISTS="$(aws --endpoint-url "$IAM_ENDPOINT" iam get-user --user-name "$IAM_USER_NAME" 2>&1 || true)"

if [[ "$USER_EXISTS" == *"NoSuchEntity"* ]]; then
log_and_run echo "IAM user '$IAM_USER_NAME' successfully deleted."
break
else
log_and_run echo "Attempt $i: IAM user '$IAM_USER_NAME' still exists. Retrying in $DELETE_DELAY seconds..."
sleep $DELAY
fi
done

if [[ "$USER_EXISTS" != *"NoSuchEntity"* ]]; then
log_and_run echo "IAM user '$IAM_USER_NAME' was not deleted."
exit 1
fi

log_and_run echo "All verifications for object-storage-access-secret passed successfully."
103 changes: 98 additions & 5 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,18 +11,22 @@ 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"
)

// postfix for inline policy which is created when COSI receives a BucketAccess (BA) request
const IAMUserInlinePolicyPostfix = "-cosi-ba"

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 @@ -84,7 +89,6 @@ func (client *IAMClient) CreateUser(ctx context.Context, userName string) error

// AttachS3WildcardInlinePolicy attaches an inline policy to an IAM user for a specific bucket.
func (client *IAMClient) AttachS3WildcardInlinePolicy(ctx context.Context, userName, bucketName string) error {
policyName := fmt.Sprintf("%s%s", bucketName, IAMUserInlinePolicyPostfix)
policyDocument := fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
Expand All @@ -101,7 +105,7 @@ func (client *IAMClient) AttachS3WildcardInlinePolicy(ctx context.Context, userN

input := &iam.PutUserPolicyInput{
UserName: &userName,
PolicyName: &policyName,
PolicyName: &bucketName,
PolicyDocument: &policyDocument,
}

Expand All @@ -110,7 +114,7 @@ func (client *IAMClient) AttachS3WildcardInlinePolicy(ctx context.Context, userN
return fmt.Errorf("failed to attach inline policy to IAM user %s: %w", userName, err)
}

klog.InfoS("Inline policy attachment succeeded", "user", userName, "policyName", policyName)
klog.InfoS("Inline policy attachment succeeded", "user", userName, "policyName", bucketName)
return nil
}

Expand Down Expand Up @@ -148,3 +152,92 @@ 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 {
err := client.EnsureUserExists(ctx, userName)
if err != nil {
return err
}

err = client.DeleteInlinePolicy(ctx, userName, bucketName)
if err != nil {
return err
}

err = client.DeleteAllAccessKeys(ctx, userName)
if err != nil {
return err
}

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 {
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)
}
var noSuchEntityErr *types.NoSuchEntityException
for _, key := range listKeysOutput.AccessKeyMetadata {
_, err := client.IAMService.DeleteAccessKey(ctx, &iam.DeleteAccessKeyInput{
UserName: &userName,
AccessKeyId: key.AccessKeyId,
})
if err != nil {
if errors.As(err, &noSuchEntityErr) {
klog.V(5).InfoS("Access key does not exist, skipping deletion", "user", userName, "accessKeyId", *key.AccessKeyId)
continue
}
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
}
return fmt.Errorf("failed to delete IAM user %s: %w", userName, err)
}
klog.InfoS("Successfully deleted IAM user", "user", userName)
return nil
}
Loading

0 comments on commit daa6db6

Please sign in to comment.