Skip to content

Commit

Permalink
Merge pull request openshift#178 from sjenning/idempotent-users
Browse files Browse the repository at this point in the history
make user creation idempotent
  • Loading branch information
openshift-merge-robot authored Apr 22, 2021
2 parents 79a8da9 + a6ec639 commit de53a22
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 77 deletions.
78 changes: 24 additions & 54 deletions cmd/infra/aws/destroy_iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,22 +162,19 @@ func (o *DestroyIAMOptions) DestroyOIDCResources(ctx context.Context, iamClient
break
}
}
err = o.DestroyOIDCRole(iamClient, "openshift-ingress")
err = o.DestroyOIDCRole(iamClient, "openshift-image-registry")
err = o.DestroyOIDCRole(iamClient, "aws-ebs-csi-driver-operator")

cloudControllerUserName := fmt.Sprintf("%s-%s", o.InfraID, "cloud-controller")
nodePoolUserName := fmt.Sprintf("%s-%s", o.InfraID, "node-pool")
if err := o.DestroyUser(ctx, iamClient, cloudControllerUserName); err != nil {
if err = o.DestroyOIDCRole(iamClient, "openshift-ingress"); err != nil {
return err
}
if err = o.DestroyOIDCRole(iamClient, "openshift-image-registry"); err != nil {
return err
}
if err := o.DestroyUser(ctx, iamClient, nodePoolUserName); err != nil {
if err = o.DestroyOIDCRole(iamClient, "aws-ebs-csi-driver-operator"); err != nil {
return err
}
if err := o.DestroyPolicy(ctx, iamClient, cloudControllerUserName); err != nil {
if err := o.DestroyUser(ctx, iamClient, "cloud-controller"); err != nil {
return err
}
if err := o.DestroyPolicy(ctx, iamClient, nodePoolUserName); err != nil {
if err := o.DestroyUser(ctx, iamClient, "node-pool"); err != nil {
return err
}

Expand Down Expand Up @@ -281,7 +278,9 @@ func (o *DestroyIAMOptions) DestroyWorkerInstanceProfile(client iamiface.IAMAPI)
return nil
}

func (o *DestroyIAMOptions) DestroyUser(ctx context.Context, client iamiface.IAMAPI, userName string) error {
func (o *DestroyIAMOptions) DestroyUser(ctx context.Context, client iamiface.IAMAPI, name string) error {
userName := fmt.Sprintf("%s-%s", o.InfraID, name)

// Tear down any access keys for the user
if output, err := client.ListAccessKeysWithContext(ctx, &iam.ListAccessKeysInput{
UserName: aws.String(userName),
Expand Down Expand Up @@ -310,28 +309,24 @@ func (o *DestroyIAMOptions) DestroyUser(ctx context.Context, client iamiface.IAM
}
}

// Detach any policies from the user
if output, err := client.ListAttachedUserPoliciesWithContext(ctx, &iam.ListAttachedUserPoliciesInput{
UserName: aws.String(userName),
}); err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != iam.ErrCodeNoSuchEntityException {
return fmt.Errorf("failed to list user policies: %w", err)
// Delete the policy
policyName := userName
_, err := client.DeleteUserPolicy(&iam.DeleteUserPolicyInput{
PolicyName: aws.String(policyName),
UserName: aws.String(userName),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() != iam.ErrCodeNoSuchEntityException {
log.Error(aerr, "Error deleting user policy", "user", userName)
return aerr
}
} else {
return fmt.Errorf("failed to list user policies: %w", err)
log.Error(err, "Error deleting user policy", "user", userName)
return err
}
} else {
for _, policy := range output.AttachedPolicies {
if _, err := client.DetachUserPolicyWithContext(ctx, &iam.DetachUserPolicyInput{
PolicyArn: policy.PolicyArn,
UserName: aws.String(userName),
}); err != nil {
return fmt.Errorf("failed to detach policy from user: %w", err)
} else {
log.Info("Detached user policy", "user", userName, "policyArn", aws.StringValue(policy.PolicyArn), "policyName", aws.StringValue(policy.PolicyName))
}
}
log.Info("Deleted user policy", "user", userName)
}

// Now the user can be deleted
Expand All @@ -347,28 +342,3 @@ func (o *DestroyIAMOptions) DestroyUser(ctx context.Context, client iamiface.IAM
}
return nil
}

func (o *DestroyIAMOptions) DestroyPolicy(ctx context.Context, client iamiface.IAMAPI, name string) (result error) {
return client.ListPoliciesPagesWithContext(ctx, &iam.ListPoliciesInput{}, func(output *iam.ListPoliciesOutput, _ bool) bool {
for _, policy := range output.Policies {
if aws.StringValue(policy.PolicyName) != name {
continue
}
if _, err := client.DeletePolicyWithContext(ctx, &iam.DeletePolicyInput{
PolicyArn: policy.Arn,
}); err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == iam.ErrCodeNoSuchEntityException {
return true
}
}
result = fmt.Errorf("failed to delete policy: %w", err)
return true
} else {
log.Info("Deleted policy", "name", name, "arn", aws.StringValue(policy.Arn))
return true
}
}
return true
})
}
84 changes: 61 additions & 23 deletions cmd/infra/aws/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,35 +605,44 @@ func (o *CreateIAMOptions) CreateWorkerInstanceProfile(client iamiface.IAMAPI, p

func (o *CreateIAMOptions) CreateCredentialedUserWithPolicy(ctx context.Context, client iamiface.IAMAPI, userName, policyDocument string) (*iam.AccessKey, error) {
var user *iam.User
if output, err := client.CreateUserWithContext(ctx, &iam.CreateUserInput{
UserName: aws.String(userName),
Tags: iamTags(o.InfraID, userName),
}); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
} else {
user = output.User
user, err := existingUser(client, userName)
if err != nil {
return nil, err
}
log.Info("Created user", "user", userName)

var policy *iam.Policy
if output, err := client.CreatePolicyWithContext(ctx, &iam.CreatePolicyInput{
PolicyName: aws.String(userName),
PolicyDocument: aws.String(policyDocument),
}); err != nil {
return nil, fmt.Errorf("failed to create policy: %w", err)
if user != nil {
log.Info("Found existing user", "user", userName)
} else {
policy = output.Policy
if output, err := client.CreateUserWithContext(ctx, &iam.CreateUserInput{
UserName: aws.String(userName),
Tags: iamTags(o.InfraID, userName),
}); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
} else {
user = output.User
}
log.Info("Created user", "user", userName)
}
log.Info("Created policy", "name", aws.StringValue(policy.PolicyName), "arn", aws.StringValue(policy.Arn))

if _, err := client.AttachUserPolicyWithContext(ctx, &iam.AttachUserPolicyInput{
UserName: user.UserName,
PolicyArn: policy.Arn,
}); err != nil {
return nil, fmt.Errorf("failed to attach user policy: %w", err)
policyName := userName
hasPolicy, err := existingUserPolicy(client, userName, userName)
if err != nil {
return nil, err
}
if hasPolicy {
log.Info("Found existing user policy", "user", userName)
} else {
_, err := client.PutUserPolicyWithContext(ctx, &iam.PutUserPolicyInput{
PolicyName: aws.String(policyName),
PolicyDocument: aws.String(policyDocument),
UserName: aws.String(userName),
})
if err != nil {
return nil, err
}
log.Info("Created user policy", "user", userName)
}
log.Info("Attached user to policy", "user", aws.StringValue(user.UserName), "policy", aws.StringValue(policy.Arn))

// We create a new access key regardless as there is no way to get access to existing keys
if output, err := client.CreateAccessKeyWithContext(ctx, &iam.CreateAccessKeyInput{
UserName: user.UserName,
}); err != nil {
Expand All @@ -657,6 +666,19 @@ func existingRole(client iamiface.IAMAPI, roleName string) (*iam.Role, error) {
return result.Role, nil
}

func existingUser(client iamiface.IAMAPI, userName string) (*iam.User, error) {
result, err := client.GetUser(&iam.GetUserInput{UserName: aws.String(userName)})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == iam.ErrCodeNoSuchEntityException {
return nil, nil
}
}
return nil, fmt.Errorf("cannot get existing role: %w", err)
}
return result.User, nil
}

func existingInstanceProfile(client iamiface.IAMAPI, profileName string) (*iam.InstanceProfile, error) {
result, err := client.GetInstanceProfile(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
Expand Down Expand Up @@ -688,6 +710,22 @@ func existingRolePolicy(client iamiface.IAMAPI, roleName, policyName string) (bo
return aws.StringValue(result.PolicyName) == policyName, nil
}

func existingUserPolicy(client iamiface.IAMAPI, userName, policyName string) (bool, error) {
result, err := client.GetUserPolicy(&iam.GetUserPolicyInput{
UserName: aws.String(userName),
PolicyName: aws.String(policyName),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == iam.ErrCodeNoSuchEntityException {
return false, nil
}
}
return false, fmt.Errorf("cannot get existing user policy: %w", err)
}
return aws.StringValue(result.PolicyName) == policyName, nil
}

func iamTags(infraID, name string) []*iam.Tag {
tags := []*iam.Tag{
{
Expand Down

0 comments on commit de53a22

Please sign in to comment.