github.com/openshift/installer@v1.4.17/pkg/destroy/aws/iamhelpers.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/aws/arn"
     9  	"github.com/aws/aws-sdk-go/aws/awserr"
    10  	"github.com/aws/aws-sdk-go/aws/session"
    11  	"github.com/aws/aws-sdk-go/service/iam"
    12  	"github.com/pkg/errors"
    13  	"github.com/sirupsen/logrus"
    14  	"k8s.io/apimachinery/pkg/util/sets"
    15  )
    16  
    17  // IamRoleSearch holds data to search for IAM roles.
    18  type IamRoleSearch struct {
    19  	Client    *iam.IAM
    20  	Filters   []Filter
    21  	Logger    logrus.FieldLogger
    22  	Unmatched map[string]struct{}
    23  }
    24  
    25  func (search *IamRoleSearch) find(ctx context.Context) (arns []string, names []string, returnErr error) {
    26  	if search.Unmatched == nil {
    27  		search.Unmatched = map[string]struct{}{}
    28  	}
    29  
    30  	var lastError error
    31  	err := search.Client.ListRolesPagesWithContext(
    32  		ctx,
    33  		&iam.ListRolesInput{},
    34  		func(results *iam.ListRolesOutput, lastPage bool) bool {
    35  			search.Logger.Debugf("iterating over a page of %d IAM roles", len(results.Roles))
    36  			for _, role := range results.Roles {
    37  				if _, ok := search.Unmatched[*role.Arn]; ok {
    38  					continue
    39  				}
    40  
    41  				// Unfortunately role.Tags is empty from ListRoles, so we need to query each one
    42  				response, err := search.Client.GetRoleWithContext(ctx, &iam.GetRoleInput{RoleName: role.RoleName})
    43  				if err != nil {
    44  					var awsErr awserr.Error
    45  					if errors.As(err, &awsErr) {
    46  						switch {
    47  						case awsErr.Code() == iam.ErrCodeNoSuchEntityException:
    48  							// The role does not exist.
    49  							// Ignore this IAM Role and donot report this error via
    50  							// lastError
    51  							search.Unmatched[*role.Arn] = exists
    52  						case strings.Contains(err.Error(), "AccessDenied"):
    53  							// Installer does not have access to this IAM role
    54  							// Ignore this IAM Role and donot report this error via
    55  							// lastError
    56  							search.Unmatched[*role.Arn] = exists
    57  						default:
    58  							if lastError != nil {
    59  								search.Logger.Debug(lastError)
    60  							}
    61  							lastError = errors.Wrapf(err, "get tags for %s", *role.Arn)
    62  						}
    63  					}
    64  				} else {
    65  					role = response.Role
    66  					tags := make(map[string]string, len(role.Tags))
    67  					for _, tag := range role.Tags {
    68  						tags[*tag.Key] = *tag.Value
    69  					}
    70  					if tagMatch(search.Filters, tags) {
    71  						arns = append(arns, *role.Arn)
    72  						names = append(names, *role.RoleName)
    73  					} else {
    74  						search.Unmatched[*role.Arn] = exists
    75  					}
    76  				}
    77  			}
    78  
    79  			return !lastPage
    80  		},
    81  	)
    82  
    83  	if lastError != nil {
    84  		return arns, names, lastError
    85  	}
    86  	return arns, names, err
    87  }
    88  
    89  // IamUserSearch holds data to search for IAM users.
    90  type IamUserSearch struct {
    91  	client    *iam.IAM
    92  	filters   []Filter
    93  	logger    logrus.FieldLogger
    94  	unmatched map[string]struct{}
    95  }
    96  
    97  func (search *IamUserSearch) arns(ctx context.Context) ([]string, error) {
    98  	if search.unmatched == nil {
    99  		search.unmatched = map[string]struct{}{}
   100  	}
   101  
   102  	arns := []string{}
   103  	var lastError error
   104  	err := search.client.ListUsersPagesWithContext(
   105  		ctx,
   106  		&iam.ListUsersInput{},
   107  		func(results *iam.ListUsersOutput, lastPage bool) bool {
   108  			search.logger.Debugf("iterating over a page of %d IAM users", len(results.Users))
   109  			for _, user := range results.Users {
   110  				if _, ok := search.unmatched[*user.Arn]; ok {
   111  					continue
   112  				}
   113  
   114  				// Unfortunately user.Tags is empty from ListUsers, so we need to query each one
   115  				response, err := search.client.GetUserWithContext(ctx, &iam.GetUserInput{UserName: aws.String(*user.UserName)})
   116  				if err != nil {
   117  
   118  					var awsErr awserr.Error
   119  					if errors.As(err, &awsErr) {
   120  						switch {
   121  						case awsErr.Code() == iam.ErrCodeNoSuchEntityException:
   122  							// The role does not exist.
   123  							// Ignore this IAM Role and do not report this error via lastError.
   124  							search.unmatched[*user.Arn] = exists
   125  						case strings.Contains(err.Error(), "AccessDenied"):
   126  							// Installer does not have access to this IAM role.
   127  							// Ignore this IAM Role and do not report this error via lastError.
   128  							search.unmatched[*user.Arn] = exists
   129  						default:
   130  							if lastError != nil {
   131  								search.logger.Debug(lastError)
   132  							}
   133  							lastError = errors.Wrapf(err, "get tags for %s", *user.Arn)
   134  						}
   135  					}
   136  				} else {
   137  					user = response.User
   138  					tags := make(map[string]string, len(user.Tags))
   139  					for _, tag := range user.Tags {
   140  						tags[*tag.Key] = *tag.Value
   141  					}
   142  					if tagMatch(search.filters, tags) {
   143  						arns = append(arns, *user.Arn)
   144  					} else {
   145  						search.unmatched[*user.Arn] = exists
   146  					}
   147  				}
   148  			}
   149  
   150  			return !lastPage
   151  		},
   152  	)
   153  
   154  	if lastError != nil {
   155  		return arns, lastError
   156  	}
   157  	return arns, err
   158  }
   159  
   160  // findIAMRoles returns the IAM roles for the cluster.
   161  //
   162  //	deleted - the resources that have already been deleted. Any resources specified in this set will be ignored.
   163  func findIAMRoles(ctx context.Context, search *IamRoleSearch, deleted sets.Set[string], logger logrus.FieldLogger) (sets.Set[string], error) {
   164  	logger.Debug("search for IAM roles")
   165  	resources, _, err := search.find(ctx)
   166  	if err != nil {
   167  		logger.Info(err)
   168  		return nil, err
   169  	}
   170  	return sets.New[string](resources...).Difference(deleted), nil
   171  }
   172  
   173  // findIAMUsers returns the IAM users for the cluster.
   174  //
   175  //	deleted - the resources that have already been deleted. Any resources specified in this set will be ignored.
   176  func findIAMUsers(ctx context.Context, search *IamUserSearch, deleted sets.Set[string], logger logrus.FieldLogger) (sets.Set[string], error) {
   177  	logger.Debug("search for IAM users")
   178  	resources, err := search.arns(ctx)
   179  	if err != nil {
   180  		logger.Info(err)
   181  		return nil, err
   182  	}
   183  	return sets.New[string](resources...).Difference(deleted), nil
   184  }
   185  
   186  func deleteIAM(ctx context.Context, session *session.Session, arn arn.ARN, logger logrus.FieldLogger) error {
   187  	client := iam.New(session)
   188  
   189  	resourceType, id, err := splitSlash("resource", arn.Resource)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	logger = logger.WithField("id", id)
   194  
   195  	switch resourceType {
   196  	case "instance-profile":
   197  		return deleteIAMInstanceProfile(ctx, client, arn, logger)
   198  	case "role":
   199  		return deleteIAMRole(ctx, client, arn, logger)
   200  	case "user":
   201  		return deleteIAMUser(ctx, client, id, logger)
   202  	default:
   203  		return errors.Errorf("unrecognized EC2 resource type %s", resourceType)
   204  	}
   205  }
   206  
   207  func deleteIAMInstanceProfileByName(ctx context.Context, client *iam.IAM, name *string, logger logrus.FieldLogger) error {
   208  	_, err := client.DeleteInstanceProfileWithContext(ctx, &iam.DeleteInstanceProfileInput{
   209  		InstanceProfileName: name,
   210  	})
   211  	if err != nil {
   212  		if err.(awserr.Error).Code() == iam.ErrCodeNoSuchEntityException {
   213  			return nil
   214  		}
   215  		return err
   216  	}
   217  	logger.WithField("InstanceProfileName", *name).Info("Deleted")
   218  	return err
   219  }
   220  
   221  func deleteIAMInstanceProfile(ctx context.Context, client *iam.IAM, profileARN arn.ARN, logger logrus.FieldLogger) error {
   222  	resourceType, name, err := splitSlash("resource", profileARN.Resource)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	if resourceType != "instance-profile" {
   228  		return errors.Errorf("%s ARN passed to deleteIAMInstanceProfile: %s", resourceType, profileARN.String())
   229  	}
   230  
   231  	response, err := client.GetInstanceProfileWithContext(ctx, &iam.GetInstanceProfileInput{
   232  		InstanceProfileName: &name,
   233  	})
   234  	if err != nil {
   235  		if err.(awserr.Error).Code() == iam.ErrCodeNoSuchEntityException {
   236  			return nil
   237  		}
   238  		return err
   239  	}
   240  	profile := response.InstanceProfile
   241  
   242  	for _, role := range profile.Roles {
   243  		_, err = client.RemoveRoleFromInstanceProfileWithContext(ctx, &iam.RemoveRoleFromInstanceProfileInput{
   244  			InstanceProfileName: profile.InstanceProfileName,
   245  			RoleName:            role.RoleName,
   246  		})
   247  		if err != nil {
   248  			return errors.Wrapf(err, "dissociating %s", *role.RoleName)
   249  		}
   250  		logger.WithField("name", name).WithField("role", *role.RoleName).Info("Disassociated")
   251  	}
   252  
   253  	logger = logger.WithField("arn", profileARN.String())
   254  	if err := deleteIAMInstanceProfileByName(ctx, client, profile.InstanceProfileName, logger); err != nil {
   255  		return err
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func deleteIAMRole(ctx context.Context, client *iam.IAM, roleARN arn.ARN, logger logrus.FieldLogger) error {
   262  	resourceType, name, err := splitSlash("resource", roleARN.Resource)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	logger = logger.WithField("name", name)
   267  
   268  	if resourceType != "role" {
   269  		return errors.Errorf("%s ARN passed to deleteIAMRole: %s", resourceType, roleARN.String())
   270  	}
   271  
   272  	var lastError error
   273  	err = client.ListRolePoliciesPagesWithContext(
   274  		ctx,
   275  		&iam.ListRolePoliciesInput{RoleName: &name},
   276  		func(results *iam.ListRolePoliciesOutput, lastPage bool) bool {
   277  			for _, policy := range results.PolicyNames {
   278  				_, err := client.DeleteRolePolicyWithContext(ctx, &iam.DeleteRolePolicyInput{
   279  					RoleName:   &name,
   280  					PolicyName: policy,
   281  				})
   282  				if err != nil {
   283  					if lastError != nil {
   284  						logger.Debug(lastError)
   285  					}
   286  					lastError = errors.Wrapf(err, "deleting IAM role policy %s", *policy)
   287  				}
   288  				logger.WithField("policy", *policy).Info("Deleted")
   289  			}
   290  
   291  			return !lastPage
   292  		},
   293  	)
   294  
   295  	if lastError != nil {
   296  		return lastError
   297  	}
   298  	if err != nil {
   299  		return errors.Wrap(err, "listing IAM role policies")
   300  	}
   301  
   302  	err = client.ListAttachedRolePoliciesPagesWithContext(
   303  		ctx,
   304  		&iam.ListAttachedRolePoliciesInput{RoleName: &name},
   305  		func(results *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool {
   306  			for _, policy := range results.AttachedPolicies {
   307  				_, err := client.DetachRolePolicyWithContext(ctx, &iam.DetachRolePolicyInput{
   308  					RoleName:  &name,
   309  					PolicyArn: policy.PolicyArn,
   310  				})
   311  				if err != nil {
   312  					if lastError != nil {
   313  						logger.Debug(lastError)
   314  					}
   315  					lastError = errors.Wrapf(err, "detaching IAM role policy %s", *policy.PolicyName)
   316  				}
   317  				logger.WithField("policy", *policy.PolicyName).Info("Detached")
   318  			}
   319  
   320  			return !lastPage
   321  		},
   322  	)
   323  
   324  	if lastError != nil {
   325  		return lastError
   326  	}
   327  	if err != nil {
   328  		return errors.Wrap(err, "listing attached IAM role policies")
   329  	}
   330  
   331  	err = client.ListInstanceProfilesForRolePagesWithContext(
   332  		ctx,
   333  		&iam.ListInstanceProfilesForRoleInput{RoleName: &name},
   334  		func(results *iam.ListInstanceProfilesForRoleOutput, lastPage bool) bool {
   335  			for _, profile := range results.InstanceProfiles {
   336  				parsed, err := arn.Parse(*profile.Arn)
   337  				if err != nil {
   338  					if lastError != nil {
   339  						logger.Debug(lastError)
   340  					}
   341  					lastError = errors.Wrap(err, "parse ARN for IAM instance profile")
   342  					continue
   343  				}
   344  
   345  				err = deleteIAMInstanceProfile(ctx, client, parsed, logger)
   346  				if err != nil {
   347  					if lastError != nil {
   348  						logger.Debug(lastError)
   349  					}
   350  					lastError = errors.Wrapf(err, "deleting %s", parsed.String())
   351  				}
   352  			}
   353  
   354  			return !lastPage
   355  		},
   356  	)
   357  
   358  	if lastError != nil {
   359  		return lastError
   360  	}
   361  	if err != nil {
   362  		return errors.Wrap(err, "listing IAM instance profiles")
   363  	}
   364  
   365  	_, err = client.DeleteRoleWithContext(ctx, &iam.DeleteRoleInput{RoleName: &name})
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	logger.Info("Deleted")
   371  	return nil
   372  }
   373  
   374  func deleteIAMUser(ctx context.Context, client *iam.IAM, id string, logger logrus.FieldLogger) error {
   375  	var lastError error
   376  	err := client.ListUserPoliciesPagesWithContext(
   377  		ctx,
   378  		&iam.ListUserPoliciesInput{UserName: &id},
   379  		func(results *iam.ListUserPoliciesOutput, lastPage bool) bool {
   380  			for _, policy := range results.PolicyNames {
   381  				_, err := client.DeleteUserPolicyWithContext(ctx, &iam.DeleteUserPolicyInput{
   382  					UserName:   &id,
   383  					PolicyName: policy,
   384  				})
   385  				if err != nil {
   386  					if lastError != nil {
   387  						logger.Debug(lastError)
   388  					}
   389  					lastError = errors.Wrapf(err, "deleting IAM user policy %s", *policy)
   390  				}
   391  				logger.WithField("policy", *policy).Info("Deleted")
   392  			}
   393  
   394  			return !lastPage
   395  		},
   396  	)
   397  
   398  	if lastError != nil {
   399  		return lastError
   400  	}
   401  	if err != nil {
   402  		return errors.Wrap(err, "listing IAM user policies")
   403  	}
   404  
   405  	err = client.ListAccessKeysPagesWithContext(
   406  		ctx,
   407  		&iam.ListAccessKeysInput{UserName: &id},
   408  		func(results *iam.ListAccessKeysOutput, lastPage bool) bool {
   409  			for _, key := range results.AccessKeyMetadata {
   410  				_, err := client.DeleteAccessKeyWithContext(ctx, &iam.DeleteAccessKeyInput{
   411  					UserName:    &id,
   412  					AccessKeyId: key.AccessKeyId,
   413  				})
   414  				if err != nil {
   415  					if lastError != nil {
   416  						logger.Debug(lastError)
   417  					}
   418  					lastError = errors.Wrapf(err, "deleting IAM access key %s", *key.AccessKeyId)
   419  				}
   420  			}
   421  
   422  			return !lastPage
   423  		},
   424  	)
   425  
   426  	if lastError != nil {
   427  		return lastError
   428  	}
   429  	if err != nil {
   430  		return errors.Wrap(err, "listing IAM access keys")
   431  	}
   432  
   433  	_, err = client.DeleteUserWithContext(ctx, &iam.DeleteUserInput{
   434  		UserName: &id,
   435  	})
   436  	if err != nil {
   437  		return err
   438  	}
   439  
   440  	logger.Info("Deleted")
   441  	return nil
   442  }