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 }