Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/iam.py: 38%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1383 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3from collections import OrderedDict 

4import csv 

5import datetime 

6import functools 

7import json 

8import io 

9from datetime import timedelta 

10import itertools 

11import time 

12 

13# Used to parse saml provider metadata configuration. 

14from xml.etree import ElementTree # nosec nosemgrep 

15 

16from concurrent.futures import as_completed 

17from dateutil.tz import tzutc 

18from dateutil.parser import parse as parse_date 

19 

20from botocore.exceptions import ClientError 

21 

22from c7n import deprecated 

23from c7n.actions import BaseAction 

24from c7n.exceptions import PolicyValidationError 

25from c7n.filters import ValueFilter, Filter 

26from c7n.filters.multiattr import MultiAttrFilter 

27from c7n.filters.iamaccess import CrossAccountAccessFilter 

28from c7n.manager import resources 

29from c7n.query import ( 

30 ChildResourceManager, 

31 ConfigSource, 

32 DescribeSource, 

33 QueryResourceManager, 

34 TypeInfo, 

35) 

36from c7n.resolver import ValuesFrom 

37from c7n.tags import TagActionFilter, TagDelayedAction, Tag, RemoveTag, universal_augment 

38from c7n.utils import ( 

39 get_partition, local_session, type_schema, chunks, filter_empty, QueryParser, 

40 select_keys 

41) 

42 

43from c7n.resources.aws import Arn 

44from c7n.resources.securityhub import OtherResourcePostFinding 

45 

46 

47class DescribeGroup(DescribeSource): 

48 

49 def get_resources(self, resource_ids, cache=True): 

50 """For IAM Groups on events, resource ids are Group Names.""" 

51 client = local_session(self.manager.session_factory).client('iam') 

52 resources = [] 

53 for rid in resource_ids: 

54 try: 

55 result = self.manager.retry(client.get_group, GroupName=rid) 

56 except client.exceptions.NoSuchEntityException: 

57 continue 

58 group = result.pop('Group') 

59 group['c7n:Users'] = result['Users'] 

60 resources.append(group) 

61 return resources 

62 

63 

64@resources.register('iam-group') 

65class Group(QueryResourceManager): 

66 

67 class resource_type(TypeInfo): 

68 service = 'iam' 

69 arn_type = 'group' 

70 enum_spec = ('list_groups', 'Groups', None) 

71 id = name = 'GroupName' 

72 date = 'CreateDate' 

73 cfn_type = config_type = "AWS::IAM::Group" 

74 # Denotes this resource type exists across regions 

75 global_resource = True 

76 arn = 'Arn' 

77 

78 source_mapping = { 

79 'describe': DescribeGroup, 

80 'config': ConfigSource 

81 } 

82 

83 

84class DescribeRole(DescribeSource): 

85 

86 def get_resources(self, resource_ids, cache=True): 

87 client = local_session(self.manager.session_factory).client('iam') 

88 resources = [] 

89 for rid in resource_ids: 

90 if rid.startswith('arn:'): 

91 rid = Arn.parse(rid).resource 

92 try: 

93 result = self.manager.retry(client.get_role, RoleName=rid) 

94 except client.exceptions.NoSuchEntityException: 

95 continue 

96 resources.append(result.pop('Role')) 

97 return resources 

98 

99 

100@resources.register('iam-role') 

101class Role(QueryResourceManager): 

102 

103 class resource_type(TypeInfo): 

104 service = 'iam' 

105 arn_type = 'role' 

106 enum_spec = ('list_roles', 'Roles', None) 

107 detail_spec = ('get_role', 'RoleName', 'RoleName', 'Role') 

108 id = name = 'RoleName' 

109 config_id = 'RoleId' 

110 date = 'CreateDate' 

111 cfn_type = config_type = "AWS::IAM::Role" 

112 # Denotes this resource type exists across regions 

113 global_resource = True 

114 arn = 'Arn' 

115 

116 source_mapping = { 

117 'describe': DescribeRole, 

118 'config': ConfigSource 

119 } 

120 

121 

122Role.action_registry.register('mark-for-op', TagDelayedAction) 

123Role.filter_registry.register('marked-for-op', TagActionFilter) 

124 

125 

126@Role.action_registry.register('post-finding') 

127class RolePostFinding(OtherResourcePostFinding): 

128 

129 resource_type = 'AwsIamRole' 

130 

131 def format_resource(self, r): 

132 envelope, payload = self.format_envelope(r) 

133 payload.update(self.filter_empty( 

134 select_keys(r, ['AssumeRolePolicyDocument', 'CreateDate', 

135 'MaxSessionDuration', 'Path', 'RoleId', 

136 'RoleName']))) 

137 payload['AssumeRolePolicyDocument'] = json.dumps( 

138 payload['AssumeRolePolicyDocument']) 

139 payload['CreateDate'] = payload['CreateDate'].isoformat() 

140 return envelope 

141 

142 

143@Role.action_registry.register('tag') 

144class RoleTag(Tag): 

145 """Tag an iam role.""" 

146 

147 permissions = ('iam:TagRole',) 

148 

149 def process_resource_set(self, client, roles, tags): 

150 for role in roles: 

151 try: 

152 self.manager.retry( 

153 client.tag_role, RoleName=role['RoleName'], Tags=tags) 

154 except client.exceptions.NoSuchEntityException: 

155 continue 

156 

157 

158@Role.action_registry.register('remove-tag') 

159class RoleRemoveTag(RemoveTag): 

160 """Remove tags from an iam role.""" 

161 

162 permissions = ('iam:UntagRole',) 

163 

164 def process_resource_set(self, client, roles, tags): 

165 for role in roles: 

166 try: 

167 self.manager.retry( 

168 client.untag_role, RoleName=role['RoleName'], TagKeys=tags) 

169 except client.exceptions.NoSuchEntityException: 

170 continue 

171 

172 

173class SetBoundary(BaseAction): 

174 """Set IAM Permission boundary on an IAM Role or User. 

175 

176 A role or user can only have a single permission boundary set. 

177 """ 

178 

179 schema = type_schema( 

180 'set-boundary', 

181 state={'enum': ['present', 'absent']}, 

182 policy={'type': 'string'}) 

183 

184 def validate(self): 

185 state = self.data.get('state', 'present') == 'present' 

186 if state and not self.data.get('policy'): 

187 raise PolicyValidationError("set-boundary requires policy arn") 

188 

189 def process(self, resources): 

190 state = self.data.get('state', 'present') == 'present' 

191 client = self.manager.session_factory().client('iam') 

192 policy = self.data.get('policy') 

193 if policy and not policy.startswith('arn'): 

194 policy = 'arn:{}:iam::{}:policy/{}'.format( 

195 get_partition(self.manager.config.region), 

196 self.manager.account_id, policy) 

197 for r in resources: 

198 method, params = self.get_method(client, state, policy, r) 

199 try: 

200 self.manager.retry(method, **params) 

201 except client.exceptions.NoSuchEntityException: 

202 continue 

203 

204 def get_method(self, client, state, policy, resource): 

205 raise NotImplementedError() 

206 

207 

208@Role.action_registry.register('set-boundary') 

209class RoleSetBoundary(SetBoundary): 

210 

211 def get_permissions(self): 

212 if self.data.get('state', True): 

213 return ('iam:PutRolePermissionsBoundary',) 

214 return ('iam:DeleteRolePermissionsBoundary',) 

215 

216 def get_method(self, client, state, policy, resource): 

217 if state: 

218 return client.put_role_permissions_boundary, { 

219 'RoleName': resource['RoleName'], 

220 'PermissionsBoundary': policy} 

221 else: 

222 return client.delete_role_permissions_boundary, { 

223 'RoleName': resource['RoleName']} 

224 

225 

226class DescribeUser(DescribeSource): 

227 

228 def augment(self, resources): 

229 # iam has a race condition, where listing will potentially return a 

230 # new user prior it to its availability to get user 

231 client = local_session(self.manager.session_factory).client('iam') 

232 results = [] 

233 for r in resources: 

234 ru = self.manager.retry( 

235 client.get_user, UserName=r['UserName'], 

236 ignore_err_codes=client.exceptions.NoSuchEntityException) 

237 if ru: 

238 results.append(ru['User']) 

239 return list(filter(None, results)) 

240 

241 def get_resources(self, resource_ids, cache=True): 

242 client = local_session(self.manager.session_factory).client('iam') 

243 results = [] 

244 

245 for r in resource_ids: 

246 try: 

247 results.append(client.get_user(UserName=r)['User']) 

248 except client.exceptions.NoSuchEntityException: 

249 continue 

250 return results 

251 

252 

253@resources.register('iam-user') 

254class User(QueryResourceManager): 

255 

256 class resource_type(TypeInfo): 

257 service = 'iam' 

258 arn_type = 'user' 

259 detail_spec = ('get_user', 'UserName', 'UserName', 'User') 

260 enum_spec = ('list_users', 'Users', None) 

261 id = name = 'UserName' 

262 date = 'CreateDate' 

263 cfn_type = config_type = "AWS::IAM::User" 

264 # Denotes this resource type exists across regions 

265 global_resource = True 

266 arn = 'Arn' 

267 config_id = 'UserId' 

268 

269 source_mapping = { 

270 'describe': DescribeUser, 

271 'config': ConfigSource 

272 } 

273 

274 

275@User.action_registry.register('tag') 

276class UserTag(Tag): 

277 """Tag an iam user.""" 

278 

279 permissions = ('iam:TagUser',) 

280 

281 def process_resource_set(self, client, users, tags): 

282 for u in users: 

283 try: 

284 self.manager.retry( 

285 client.tag_user, UserName=u['UserName'], Tags=tags) 

286 except client.exceptions.NoSuchEntityException: 

287 continue 

288 

289 

290@User.action_registry.register('remove-tag') 

291class UserRemoveTag(RemoveTag): 

292 """Remove tags from an iam user.""" 

293 

294 permissions = ('iam:UntagUser',) 

295 

296 def process_resource_set(self, client, users, tags): 

297 for u in users: 

298 try: 

299 self.manager.retry( 

300 client.untag_user, UserName=u['UserName'], TagKeys=tags) 

301 except client.exceptions.NoSuchEntityException: 

302 continue 

303 

304 

305User.action_registry.register('mark-for-op', TagDelayedAction) 

306User.filter_registry.register('marked-for-op', TagActionFilter) 

307 

308 

309Role.action_registry.register('mark-for-op', TagDelayedAction) 

310Role.filter_registry.register('marked-for-op', TagActionFilter) 

311 

312 

313@User.action_registry.register('set-groups') 

314class SetGroups(BaseAction): 

315 """Set a specific IAM user as added/removed from a group 

316 

317 :example: 

318 

319 .. code-block:: yaml 

320 

321 - name: iam-user-add-remove 

322 resource: iam-user 

323 filters: 

324 - type: value 

325 key: UserName 

326 value: Bob 

327 actions: 

328 - type: set-groups 

329 state: remove 

330 group: Admin 

331 

332 """ 

333 schema = type_schema( 

334 'set-groups', 

335 state={'enum': ['add', 'remove']}, 

336 group={'type': 'string'}, 

337 required=['state', 'group'] 

338 ) 

339 

340 permissions = ('iam:AddUserToGroup', 'iam:RemoveUserFromGroup',) 

341 

342 def validate(self): 

343 if self.data.get('group') == '': 

344 raise PolicyValidationError('group cannot be empty on %s' 

345 % (self.manager.data)) 

346 

347 def process(self, resources): 

348 group_name = self.data['group'] 

349 state = self.data['state'] 

350 client = local_session(self.manager.session_factory).client('iam') 

351 op_map = { 

352 'add': client.add_user_to_group, 

353 'remove': client.remove_user_from_group 

354 } 

355 for r in resources: 

356 try: 

357 op_map[state](GroupName=group_name, UserName=r['UserName']) 

358 except client.exceptions.NoSuchEntityException: 

359 continue 

360 

361 

362@User.action_registry.register('set-boundary') 

363class UserSetBoundary(SetBoundary): 

364 

365 def get_permissions(self): 

366 if self.data.get('state', True): 

367 return ('iam:PutUserPermissionsBoundary',) 

368 return ('iam:DeleteUserPermissionsBoundary',) 

369 

370 def get_method(self, client, state, policy, resource): 

371 if state: 

372 return client.put_user_permissions_boundary, { 

373 'UserName': resource['UserName'], 

374 'PermissionsBoundary': policy} 

375 else: 

376 return client.delete_user_permissions_boundary, { 

377 'UserName': resource['UserName']} 

378 

379 

380class DescribePolicy(DescribeSource): 

381 

382 def resources(self, query=None): 

383 queries = PolicyQueryParser.parse(self.manager.data.get('query', [])) 

384 query = query or {} 

385 for q in queries: 

386 query.update(q) 

387 if 'Scope' not in query: 

388 query['Scope'] = 'Local' 

389 return super(DescribePolicy, self).resources(query=query) 

390 

391 def get_resources(self, resource_ids, cache=True): 

392 client = local_session(self.manager.session_factory).client('iam') 

393 results = [] 

394 

395 for r in resource_ids: 

396 try: 

397 results.append(client.get_policy(PolicyArn=r)['Policy']) 

398 except ClientError as e: 

399 if e.response['Error']['Code'] == 'NoSuchEntityException': 

400 continue 

401 return results 

402 

403 def augment(self, resources): 

404 return universal_augment(self.manager, super().augment(resources)) 

405 

406 

407@resources.register('iam-policy') 

408class Policy(QueryResourceManager): 

409 

410 class resource_type(TypeInfo): 

411 service = 'iam' 

412 arn_type = 'policy' 

413 enum_spec = ('list_policies', 'Policies', None) 

414 id = 'PolicyId' 

415 name = 'PolicyName' 

416 date = 'CreateDate' 

417 cfn_type = config_type = "AWS::IAM::Policy" 

418 # Denotes this resource type exists across regions 

419 global_resource = True 

420 arn = 'Arn' 

421 universal_taggable = object() 

422 

423 source_mapping = { 

424 'describe': DescribePolicy, 

425 'config': ConfigSource 

426 } 

427 

428 

429class PolicyQueryParser(QueryParser): 

430 

431 QuerySchema = { 

432 'Scope': ('All', 'AWS', 'Local'), 

433 'PolicyUsageFilter': ('PermissionsPolicy', 'PermissionsBoundary'), 

434 'PathPrefix': str, 

435 'OnlyAttached': bool, 

436 'MaxItems': int, 

437 } 

438 multi_value = False 

439 type_name = 'IAM Policy' 

440 

441 

442@resources.register('iam-profile') 

443class InstanceProfile(QueryResourceManager): 

444 

445 class resource_type(TypeInfo): 

446 service = 'iam' 

447 arn_type = 'instance-profile' 

448 enum_spec = ('list_instance_profiles', 'InstanceProfiles', None) 

449 name = id = 'InstanceProfileName' 

450 date = 'CreateDate' 

451 # Denotes this resource type exists across regions 

452 global_resource = True 

453 arn = 'Arn' 

454 cfn_type = 'AWS::IAM::InstanceProfile' 

455 

456 

457@resources.register('iam-certificate') 

458class ServerCertificate(QueryResourceManager): 

459 

460 class resource_type(TypeInfo): 

461 service = 'iam' 

462 arn_type = 'server-certificate' 

463 enum_spec = ('list_server_certificates', 

464 'ServerCertificateMetadataList', 

465 None) 

466 name = id = 'ServerCertificateName' 

467 config_type = "AWS::IAM::ServerCertificate" 

468 name = 'ServerCertificateName' 

469 date = 'Expiration' 

470 # Denotes this resource type exists across regions 

471 global_resource = True 

472 

473 

474@ServerCertificate.action_registry.register('delete') 

475class CertificateDelete(BaseAction): 

476 """Delete an IAM Certificate 

477 

478 For example, if you want to automatically delete an unused IAM certificate. 

479 

480 :example: 

481 

482 .. code-block:: yaml 

483 

484 - name: aws-iam-certificate-delete-expired 

485 resource: iam-certificate 

486 filters: 

487 - type: value 

488 key: Expiration 

489 value_type: expiration 

490 op: greater-than 

491 value: 0 

492 actions: 

493 - type: delete 

494 

495 """ 

496 schema = type_schema('delete') 

497 permissions = ('iam:DeleteServerCertificate',) 

498 

499 def process(self, resources): 

500 client = local_session(self.manager.session_factory).client('iam') 

501 for cert in resources: 

502 self.manager.retry( 

503 client.delete_server_certificate, 

504 ServerCertificateName=cert['ServerCertificateName'], 

505 ignore_err_codes=( 

506 'NoSuchEntityException', 

507 'DeleteConflictException', 

508 ), 

509 ) 

510 

511 

512@User.filter_registry.register('usage') 

513@Role.filter_registry.register('usage') 

514@Group.filter_registry.register('usage') 

515@Policy.filter_registry.register('usage') 

516class ServiceUsage(Filter): 

517 """Filter iam resources by their api/service usage. 

518 

519 Note recent activity (last 4hrs) may not be shown, evaluation 

520 is against the last 365 days of data. 

521 

522 Each service access record is evaluated against all specified 

523 attributes. Attribute filters can be specified in short form k:v 

524 pairs or in long form as a value type filter. 

525 

526 match-operator allows to specify how a resource is treated across 

527 service access record matches. 'any' means a single matching 

528 service record will return the policy resource as matching. 'all' 

529 means all service access records have to match. 

530 

531 

532 Find iam users that have not used any services in the last year 

533 

534 :example: 

535 

536 .. code-block:: yaml 

537 

538 - name: usage-unused-users 

539 resource: iam-user 

540 filters: 

541 - type: usage 

542 match-operator: all 

543 LastAuthenticated: null 

544 

545 Find iam users that have used dynamodb in last 30 days 

546 

547 :example: 

548 

549 .. code-block:: yaml 

550 

551 - name: unused-users 

552 resource: iam-user 

553 filters: 

554 - type: usage 

555 ServiceNamespace: dynamodb 

556 TotalAuthenticatedEntities: 1 

557 LastAuthenticated: 

558 type: value 

559 value_type: age 

560 op: less-than 

561 value: 30 

562 match-operator: any 

563 

564 https://aws.amazon.com/blogs/security/automate-analyzing-permissions-using-iam-access-advisor/ 

565 

566 """ 

567 

568 JOB_COMPLETE = 'COMPLETED' 

569 SERVICE_ATTR = { 

570 'ServiceName', 'ServiceNamespace', 'TotalAuthenticatedEntities', 

571 'LastAuthenticated', 'LastAuthenticatedEntity'} 

572 

573 schema_alias = True 

574 schema_attr = { 

575 sa: {'oneOf': [ 

576 {'type': 'string'}, 

577 {'type': 'boolean'}, 

578 {'type': 'number'}, 

579 {'type': 'null'}, 

580 {'$ref': '#/definitions/filters/value'}]} 

581 for sa in sorted(SERVICE_ATTR)} 

582 schema_attr['match-operator'] = {'enum': ['all', 'any']} 

583 schema_attr['poll-delay'] = {'type': 'number'} 

584 schema = type_schema( 

585 'usage', 

586 required=('match-operator',), 

587 **schema_attr) 

588 permissions = ('iam:GenerateServiceLastAccessedDetails', 

589 'iam:GetServiceLastAccessedDetails') 

590 

591 def process(self, resources, event=None): 

592 client = local_session(self.manager.session_factory).client('iam') 

593 

594 job_resource_map = {} 

595 for arn, r in zip(self.manager.get_arns(resources), resources): 

596 try: 

597 jid = self.manager.retry( 

598 client.generate_service_last_accessed_details, 

599 Arn=arn)['JobId'] 

600 job_resource_map[jid] = r 

601 except client.exceptions.NoSuchEntityException: 

602 continue 

603 

604 conf = dict(self.data) 

605 conf.pop('match-operator') 

606 saf = MultiAttrFilter(conf) 

607 saf.multi_attrs = self.SERVICE_ATTR 

608 

609 results = [] 

610 match_operator = self.data.get('match-operator', 'all') 

611 

612 while job_resource_map: 

613 job_results_map = {} 

614 for jid, r in job_resource_map.items(): 

615 result = self.manager.retry( 

616 client.get_service_last_accessed_details, JobId=jid) 

617 if result['JobStatus'] != self.JOB_COMPLETE: 

618 continue 

619 job_results_map[jid] = result['ServicesLastAccessed'] 

620 

621 for jid, saf_results in job_results_map.items(): 

622 r = job_resource_map.pop(jid) 

623 saf_matches = saf.process(saf_results) 

624 if match_operator == 'all' and len(saf_matches) == len(saf_results): 

625 results.append(r) 

626 elif match_operator != 'all' and saf_matches: 

627 results.append(r) 

628 

629 time.sleep(self.data.get('poll-delay', 2)) 

630 

631 return results 

632 

633 

634@User.filter_registry.register('check-permissions') 

635@Group.filter_registry.register('check-permissions') 

636@Role.filter_registry.register('check-permissions') 

637@Policy.filter_registry.register('check-permissions') 

638class CheckPermissions(Filter): 

639 """Check IAM permissions associated with a resource. 

640 

641 :example: 

642 

643 Find users that can create other users 

644 

645 .. code-block:: yaml 

646 

647 policies: 

648 - name: super-users 

649 resource: aws.iam-user 

650 filters: 

651 - type: check-permissions 

652 match: allowed 

653 actions: 

654 - iam:CreateUser 

655 

656 :example: 

657 

658 Find users with access to all services and actions 

659 

660 .. code-block:: yaml 

661 

662 policies: 

663 - name: admin-users 

664 resource: aws.iam-user 

665 filters: 

666 - type: check-permissions 

667 match: allowed 

668 actions: 

669 - '*:*' 

670 

671 By default permission boundaries are checked. 

672 """ 

673 

674 schema = type_schema( 

675 'check-permissions', **{ 

676 'match': {'oneOf': [ 

677 {'enum': ['allowed', 'denied']}, 

678 {'$ref': '#/definitions/filters/valuekv'}, 

679 {'$ref': '#/definitions/filters/value'}]}, 

680 'boundaries': {'type': 'boolean'}, 

681 'match-operator': {'enum': ['and', 'or']}, 

682 'actions': {'type': 'array', 'items': {'type': 'string'}}, 

683 'required': ('actions', 'match')}) 

684 schema_alias = True 

685 policy_annotation = 'c7n:policy' 

686 eval_annotation = 'c7n:perm-matches' 

687 

688 def validate(self): 

689 # This filter relies on IAM policy simulator APIs. From the docs concerning action names: 

690 # 

691 # "Each operation must include the service identifier, such as iam:CreateUser. This 

692 # operation does not support using wildcards (*) in an action name." 

693 # 

694 # We can catch invalid actions during policy validation, rather than waiting to hit 

695 # runtime exceptions. 

696 for action in self.data['actions']: 

697 if ':' not in action[1:-1]: 

698 raise PolicyValidationError( 

699 "invalid check-permissions action: '%s' must be in the form <service>:<action>" 

700 % (action,)) 

701 return self 

702 

703 def get_permissions(self): 

704 if self.manager.type == 'iam-policy': 

705 return ('iam:SimulateCustomPolicy', 'iam:GetPolicyVersion') 

706 perms = ('iam:SimulatePrincipalPolicy', 'iam:GetPolicy', 'iam:GetPolicyVersion') 

707 if self.manager.type not in ('iam-user', 'iam-role',): 

708 # for simulating w/ permission boundaries 

709 perms += ('iam:GetRole',) 

710 return perms 

711 

712 def process(self, resources, event=None): 

713 client = local_session(self.manager.session_factory).client('iam') 

714 actions = self.data['actions'] 

715 matcher = self.get_eval_matcher() 

716 operator = self.data.get('match-operator', 'and') == 'and' and all or any 

717 arn_resources = list(zip(self.get_iam_arns(resources), resources)) 

718 

719 # To ignore permission boundaries, override with an allow-all policy 

720 self.simulation_boundary_override = ''' 

721 { 

722 "Version": "2012-10-17", 

723 "Statement": [{ 

724 "Effect": "Allow", 

725 "Action": "*", 

726 "Resource": "*" 

727 }] 

728 } 

729 ''' if not self.data.get('boundaries', True) else None 

730 results = [] 

731 eval_cache = {} 

732 for arn, r in arn_resources: 

733 if arn is None: 

734 continue 

735 if arn in eval_cache: 

736 evaluations = eval_cache[arn] 

737 else: 

738 evaluations = self.get_evaluations(client, arn, r, actions) 

739 eval_cache[arn] = evaluations 

740 if not evaluations: 

741 continue 

742 matches = [] 

743 matched = [] 

744 for e in evaluations: 

745 match = matcher(e) 

746 if match: 

747 matched.append(e) 

748 matches.append(match) 

749 if operator(matches): 

750 r[self.eval_annotation] = matched 

751 results.append(r) 

752 return results 

753 

754 def get_iam_arns(self, resources): 

755 return self.manager.get_arns(resources) 

756 

757 def get_evaluations(self, client, arn, r, actions): 

758 if self.manager.type == 'iam-policy': 

759 policy = r.get(self.policy_annotation) 

760 if policy is None: 

761 r['c7n:policy'] = policy = client.get_policy_version( 

762 PolicyArn=r['Arn'], 

763 VersionId=r['DefaultVersionId']).get('PolicyVersion', {}) 

764 evaluations = self.manager.retry( 

765 client.simulate_custom_policy, 

766 PolicyInputList=[json.dumps(policy['Document'])], 

767 ActionNames=actions).get('EvaluationResults', ()) 

768 return evaluations 

769 

770 params = dict( 

771 PolicySourceArn=arn, 

772 ActionNames=actions, 

773 ignore_err_codes=('NoSuchEntity',)) 

774 

775 # simulate_principal_policy() respects permission boundaries by default. To opt out of 

776 # considering boundaries for this filter, we can provide an allow-all policy 

777 # as the boundary. 

778 # 

779 # Note: Attempting to use an empty list for the boundary seems like a reasonable 

780 # impulse too, but that seems to cause the simulator to fall back to its default 

781 # of using existing boundaries. 

782 if self.simulation_boundary_override: 

783 params['PermissionsBoundaryPolicyInputList'] = [self.simulation_boundary_override] 

784 

785 evaluations = (self.manager.retry( 

786 client.simulate_principal_policy, 

787 **params) or {}).get('EvaluationResults', ()) 

788 return evaluations 

789 

790 def get_eval_matcher(self): 

791 if isinstance(self.data['match'], str): 

792 if self.data['match'] == 'denied': 

793 values = ['explicitDeny', 'implicitDeny'] 

794 else: 

795 values = ['allowed'] 

796 vf = ValueFilter({'type': 'value', 'key': 

797 'EvalDecision', 'value': values, 

798 'op': 'in'}) 

799 else: 

800 vf = ValueFilter(self.data['match']) 

801 vf.annotate = False 

802 return vf 

803 

804 

805class IamRoleUsage(Filter): 

806 

807 def get_permissions(self): 

808 perms = list(itertools.chain(*[ 

809 self.manager.get_resource_manager(m).get_permissions() 

810 for m in ['lambda', 'launch-config', 'ec2']])) 

811 perms.extend(['ecs:DescribeClusters', 'ecs:DescribeServices']) 

812 return perms 

813 

814 def service_role_usage(self): 

815 results = set() 

816 results.update(self.scan_lambda_roles()) 

817 results.update(self.scan_ecs_roles()) 

818 results.update(self.collect_profile_roles()) 

819 return results 

820 

821 def instance_profile_usage(self): 

822 results = set() 

823 results.update(self.scan_asg_roles()) 

824 results.update(self.scan_ec2_roles()) 

825 return results 

826 

827 def scan_lambda_roles(self): 

828 manager = self.manager.get_resource_manager('lambda') 

829 return [r['Role'] for r in manager.resources() if 'Role' in r] 

830 

831 def scan_ecs_roles(self): 

832 results = [] 

833 client = local_session(self.manager.session_factory).client('ecs') 

834 for cluster in client.describe_clusters()['clusters']: 

835 services = client.list_services( 

836 cluster=cluster['clusterName'])['serviceArns'] 

837 if services: 

838 for service in client.describe_services( 

839 cluster=cluster['clusterName'], 

840 services=services)['services']: 

841 if 'roleArn' in service: 

842 results.append(service['roleArn']) 

843 return results 

844 

845 def collect_profile_roles(self): 

846 # Collect iam roles attached to instance profiles of EC2/ASG resources 

847 profiles = set() 

848 profiles.update(self.scan_asg_roles()) 

849 profiles.update(self.scan_ec2_roles()) 

850 

851 manager = self.manager.get_resource_manager('iam-profile') 

852 iprofiles = manager.resources() 

853 results = [] 

854 for p in iprofiles: 

855 if p['InstanceProfileName'] not in profiles: 

856 continue 

857 for role in p.get('Roles', []): 

858 results.append(role['RoleName']) 

859 return results 

860 

861 def scan_asg_roles(self): 

862 manager = self.manager.get_resource_manager('launch-config') 

863 return [r['IamInstanceProfile'] for r in manager.resources() if ( 

864 'IamInstanceProfile' in r)] 

865 

866 def scan_ec2_roles(self): 

867 manager = self.manager.get_resource_manager('ec2') 

868 results = [] 

869 for e in manager.resources(): 

870 # do not include instances that have been recently terminated 

871 if e['State']['Name'] == 'terminated': 

872 continue 

873 profile_arn = e.get('IamInstanceProfile', {}).get('Arn', None) 

874 if not profile_arn: 

875 continue 

876 # split arn to get the profile name 

877 results.append(profile_arn.split('/')[-1]) 

878 return results 

879 

880 

881################### 

882# IAM Roles # 

883################### 

884 

885@Role.filter_registry.register('used') 

886class UsedIamRole(IamRoleUsage): 

887 """Filter IAM roles that are either being used or not 

888 

889 Checks for usage on EC2, Lambda, ECS only 

890 

891 :example: 

892 

893 .. code-block:: yaml 

894 

895 policies: 

896 - name: iam-role-in-use 

897 resource: iam-role 

898 filters: 

899 - type: used 

900 state: true 

901 """ 

902 

903 schema = type_schema( 

904 'used', 

905 state={'type': 'boolean'}) 

906 

907 def process(self, resources, event=None): 

908 roles = self.service_role_usage() 

909 if self.data.get('state', True): 

910 return [r for r in resources if ( 

911 r['Arn'] in roles or r['RoleName'] in roles)] 

912 

913 return [r for r in resources if ( 

914 r['Arn'] not in roles and r['RoleName'] not in roles)] 

915 

916 

917@Role.filter_registry.register('unused') 

918class UnusedIamRole(IamRoleUsage): 

919 """Filter IAM roles that are either being used or not 

920 

921 This filter has been deprecated. Please use the 'used' filter 

922 with the 'state' attribute to get unused iam roles 

923 

924 Checks for usage on EC2, Lambda, ECS only 

925 

926 :example: 

927 

928 .. code-block:: yaml 

929 

930 policies: 

931 - name: iam-roles-not-in-use 

932 resource: iam-role 

933 filters: 

934 - type: used 

935 state: false 

936 """ 

937 deprecations = ( 

938 deprecated.filter("use the 'used' filter with 'state' attribute"), 

939 ) 

940 

941 schema = type_schema('unused') 

942 

943 def process(self, resources, event=None): 

944 return UsedIamRole({'state': False}, self.manager).process(resources) 

945 

946 

947@Role.filter_registry.register('cross-account') 

948class RoleCrossAccountAccess(CrossAccountAccessFilter): 

949 

950 policy_attribute = 'AssumeRolePolicyDocument' 

951 permissions = ('iam:ListRoles',) 

952 

953 schema = type_schema( 

954 'cross-account', 

955 # white list accounts 

956 whitelist_from=ValuesFrom.schema, 

957 whitelist={'type': 'array', 'items': {'type': 'string'}}) 

958 

959 

960@Role.filter_registry.register('has-inline-policy') 

961class IamRoleInlinePolicy(Filter): 

962 """Filter IAM roles that have an inline-policy attached 

963 True: Filter roles that have an inline-policy 

964 False: Filter roles that do not have an inline-policy 

965 

966 :example: 

967 

968 .. code-block:: yaml 

969 

970 policies: 

971 - name: iam-roles-with-inline-policies 

972 resource: iam-role 

973 filters: 

974 - type: has-inline-policy 

975 value: True 

976 """ 

977 

978 schema = type_schema('has-inline-policy', value={'type': 'boolean'}) 

979 permissions = ('iam:ListRolePolicies',) 

980 

981 def _inline_policies(self, client, resource): 

982 policies = client.list_role_policies( 

983 RoleName=resource['RoleName'])['PolicyNames'] 

984 resource['c7n:InlinePolicies'] = policies 

985 return resource 

986 

987 def process(self, resources, event=None): 

988 c = local_session(self.manager.session_factory).client('iam') 

989 res = [] 

990 value = self.data.get('value', True) 

991 for r in resources: 

992 r = self._inline_policies(c, r) 

993 if len(r['c7n:InlinePolicies']) > 0 and value: 

994 res.append(r) 

995 if len(r['c7n:InlinePolicies']) == 0 and not value: 

996 res.append(r) 

997 return res 

998 

999 

1000@Role.filter_registry.register('has-specific-managed-policy') 

1001class SpecificIamRoleManagedPolicy(ValueFilter): 

1002 """Find IAM roles that have a specific policy attached 

1003 

1004 :example: 

1005 

1006 Check for roles with 'admin-policy' attached: 

1007 

1008 .. code-block:: yaml 

1009 

1010 policies: 

1011 - name: iam-roles-have-admin 

1012 resource: aws.iam-role 

1013 filters: 

1014 - type: has-specific-managed-policy 

1015 value: admin-policy 

1016 

1017 :example: 

1018 

1019 Check for roles with an attached policy matching 

1020 a given list: 

1021 

1022 .. code-block:: yaml 

1023 

1024 policies: 

1025 - name: iam-roles-with-selected-policies 

1026 resource: aws.iam-role 

1027 filters: 

1028 - type: has-specific-managed-policy 

1029 op: in 

1030 value: 

1031 - AmazonS3FullAccess 

1032 - AWSOrganizationsFullAccess 

1033 

1034 :example: 

1035 

1036 Check for roles with attached policy names matching a pattern: 

1037 

1038 .. code-block:: yaml 

1039 

1040 policies: 

1041 - name: iam-roles-with-full-access-policies 

1042 resource: aws.iam-role 

1043 filters: 

1044 - type: has-specific-managed-policy 

1045 op: glob 

1046 value: "*FullAccess" 

1047 

1048 Check for roles with attached policy ARNs matching a pattern: 

1049 

1050 .. code-block:: yaml 

1051 

1052 policies: 

1053 - name: iam-roles-with-aws-full-access-policies 

1054 resource: aws.iam-role 

1055 filters: 

1056 - type: has-specific-managed-policy 

1057 key: PolicyArn 

1058 op: regex 

1059 value: "arn:aws:iam::aws:policy/.*FullAccess" 

1060 """ 

1061 

1062 schema = type_schema('has-specific-managed-policy', rinherit=ValueFilter.schema) 

1063 permissions = ('iam:ListAttachedRolePolicies',) 

1064 annotation_key = 'c7n:AttachedPolicies' 

1065 matched_annotation_key = 'c7n:MatchedPolicies' 

1066 schema_alias = False 

1067 

1068 def __init__(self, data, manager=None): 

1069 # Preserve backward compatibility 

1070 if 'key' not in data: 

1071 data['key'] = 'PolicyName' 

1072 super(SpecificIamRoleManagedPolicy, self).__init__(data, manager) 

1073 

1074 def get_managed_policies(self, client, role_set): 

1075 for role in role_set: 

1076 role[self.annotation_key] = [ 

1077 role_policy 

1078 for role_policy 

1079 in client.list_attached_role_policies( 

1080 RoleName=role['RoleName'])['AttachedPolicies'] 

1081 ] 

1082 

1083 def process(self, resources, event=None): 

1084 client = local_session(self.manager.session_factory).client('iam') 

1085 with self.executor_factory(max_workers=2) as w: 

1086 augment_set = [r for r in resources if self.annotation_key not in r] 

1087 self.log.debug( 

1088 "Querying %d roles' attached policies" % len(augment_set)) 

1089 list(w.map( 

1090 functools.partial(self.get_managed_policies, client), 

1091 chunks(augment_set, 50))) 

1092 

1093 matched = [] 

1094 for r in resources: 

1095 matched_keys = [k for k in r[self.annotation_key] if self.match(k)] 

1096 self.merge_annotation(r, self.matched_annotation_key, matched_keys) 

1097 if matched_keys: 

1098 matched.append(r) 

1099 return matched 

1100 

1101 

1102@Role.filter_registry.register('no-specific-managed-policy') 

1103class NoSpecificIamRoleManagedPolicy(Filter): 

1104 """Filter IAM roles that do not have a specific policy attached 

1105 

1106 For example, if the user wants to check all roles without 'ip-restriction': 

1107 

1108 :example: 

1109 

1110 .. code-block:: yaml 

1111 

1112 policies: 

1113 - name: iam-roles-no-ip-restriction 

1114 resource: iam-role 

1115 filters: 

1116 - type: no-specific-managed-policy 

1117 value: ip-restriction 

1118 """ 

1119 

1120 schema = type_schema('no-specific-managed-policy', value={'type': 'string'}) 

1121 permissions = ('iam:ListAttachedRolePolicies',) 

1122 

1123 def _managed_policies(self, client, resource): 

1124 return [r['PolicyName'] for r in client.list_attached_role_policies( 

1125 RoleName=resource['RoleName'])['AttachedPolicies']] 

1126 

1127 def process(self, resources, event=None): 

1128 c = local_session(self.manager.session_factory).client('iam') 

1129 if self.data.get('value'): 

1130 return [r for r in resources if self.data.get('value') not in 

1131 self._managed_policies(c, r)] 

1132 return [] 

1133 

1134 

1135@Role.action_registry.register('set-policy') 

1136class SetPolicy(BaseAction): 

1137 """Set a specific IAM policy as attached or detached on a role. 

1138 

1139 You will identify the policy by its arn. 

1140 

1141 Returns a list of roles modified by the action. 

1142 

1143 For example, if you want to automatically attach a policy to all roles which don't have it... 

1144 

1145 :example: 

1146 

1147 .. code-block:: yaml 

1148 

1149 - name: iam-attach-role-policy 

1150 resource: iam-role 

1151 filters: 

1152 - type: no-specific-managed-policy 

1153 value: my-iam-policy 

1154 actions: 

1155 - type: set-policy 

1156 state: detached 

1157 arn: "*" 

1158 - type: set-policy 

1159 state: attached 

1160 arn: arn:aws:iam::123456789012:policy/my-iam-policy 

1161 

1162 """ 

1163 schema = type_schema( 

1164 'set-policy', 

1165 state={'enum': ['attached', 'detached']}, 

1166 arn={'type': 'string'}, 

1167 required=['state', 'arn']) 

1168 

1169 permissions = ('iam:AttachRolePolicy', 'iam:DetachRolePolicy', "iam:ListAttachedRolePolicies",) 

1170 

1171 def validate(self): 

1172 if self.data.get('state') == 'attached' and self.data.get('arn') == "*": 

1173 raise PolicyValidationError( 

1174 '* operator is not supported for state: attached on %s' % (self.manager.data)) 

1175 

1176 def attach_policy(self, client, resource, policy_arn): 

1177 client.attach_role_policy( 

1178 RoleName=resource['RoleName'], 

1179 PolicyArn=policy_arn 

1180 ) 

1181 

1182 def detach_policy(self, client, resource, policy_arn): 

1183 try: 

1184 client.detach_role_policy( 

1185 RoleName=resource['RoleName'], 

1186 PolicyArn=policy_arn 

1187 ) 

1188 except client.exceptions.NoSuchEntityException: 

1189 return 

1190 

1191 def list_attached_policies(self, client, resource): 

1192 attached_policy = client.list_attached_role_policies(RoleName=resource['RoleName']) 

1193 policy_arns = [p.get('PolicyArn') for p in attached_policy['AttachedPolicies']] 

1194 return policy_arns 

1195 

1196 def process(self, resources): 

1197 client = local_session(self.manager.session_factory).client('iam') 

1198 policy_arn = self.data['arn'] 

1199 if policy_arn != "*" and not policy_arn.startswith('arn'): 

1200 policy_arn = 'arn:{}:iam::{}:policy/{}'.format( 

1201 get_partition(self.manager.config.region), 

1202 self.manager.account_id, policy_arn) 

1203 state = self.data['state'] 

1204 for r in resources: 

1205 if state == 'attached': 

1206 self.attach_policy(client, r, policy_arn) 

1207 elif state == 'detached' and policy_arn != "*": 

1208 self.detach_policy(client, r, policy_arn) 

1209 elif state == 'detached' and policy_arn == "*": 

1210 try: 

1211 self.detach_all_policies(client, r) 

1212 except client.exceptions.NoSuchEntityException: 

1213 continue 

1214 

1215 def detach_all_policies(self, client, resource): 

1216 policy_arns = self.list_attached_policies(client, resource) 

1217 for parn in policy_arns: 

1218 self.detach_policy(client, resource, parn) 

1219 

1220 

1221@User.action_registry.register("set-policy") 

1222class SetUserPolicy(SetPolicy): 

1223 """Set a specific IAM policy as attached or detached on a user. 

1224 

1225 You will identify the policy by its arn. 

1226 

1227 Returns a list of roles modified by the action. 

1228 

1229 For example, if you want to automatically attach a single policy while 

1230 detaching all exisitng policies: 

1231 

1232 :example: 

1233 

1234 .. code-block:: yaml 

1235 

1236 - name: iam-attach-user-policy 

1237 resource: iam-user 

1238 filters: 

1239 - type: value 

1240 key: UserName 

1241 op: not-in 

1242 value: 

1243 - AdminUser1 

1244 - AdminUser2 

1245 actions: 

1246 - type: set-policy 

1247 state: detached 

1248 arn: arn:aws:iam::aws:policy/AdministratorAccess 

1249 

1250 """ 

1251 

1252 permissions = ( 

1253 "iam:AttachUserPolicy", "iam:DetachUserPolicy", "iam:ListAttachedUserPolicies",) 

1254 

1255 def attach_policy(self, client, resource, policy_arn): 

1256 client.attach_user_policy( 

1257 UserName=resource["UserName"], PolicyArn=policy_arn) 

1258 

1259 def detach_policy(self, client, resource, policy_arn): 

1260 try: 

1261 client.detach_user_policy( 

1262 UserName=resource["UserName"], PolicyArn=policy_arn) 

1263 except client.exceptions.NoSuchEntityException: 

1264 return 

1265 

1266 def list_attached_policies(self, client, resource): 

1267 attached_policies = client.list_attached_user_policies( 

1268 UserName=resource["UserName"] 

1269 ) 

1270 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']] 

1271 return policy_arns 

1272 

1273 

1274@Group.action_registry.register("set-policy") 

1275class SetGroupPolicy(SetPolicy): 

1276 """Set a specific IAM policy as attached or detached on a group. 

1277 

1278 You will identify the policy by its arn. 

1279 

1280 Returns a list of roles modified by the action. 

1281 

1282 For example, if you want to automatically attach a single policy while 

1283 detaching all exisitng policies: 

1284 

1285 :example: 

1286 

1287 .. code-block:: yaml 

1288 

1289 - name: iam-attach-group-policy 

1290 resource: iam-group 

1291 actions: 

1292 - type: set-policy 

1293 state: detached 

1294 arn: "*" 

1295 - type: set-policy 

1296 state: attached 

1297 arn: arn:aws:iam::{account_id}:policy/my-iam-policy 

1298 

1299 """ 

1300 

1301 permissions = ( 

1302 "iam:AttachGroupPolicy", "iam:DetachGroupPolicy", "iam:ListAttachedGroupPolicies",) 

1303 

1304 def attach_policy(self, client, resource, policy_arn): 

1305 client.attach_group_policy( 

1306 GroupName=resource["GroupName"], PolicyArn=policy_arn) 

1307 

1308 def detach_policy(self, client, resource, policy_arn): 

1309 try: 

1310 client.detach_group_policy( 

1311 GroupName=resource["GroupName"], PolicyArn=policy_arn) 

1312 except client.exceptions.NoSuchEntityException: 

1313 return 

1314 

1315 def list_attached_policies(self, client, resource): 

1316 attached_policies = client.list_attached_group_policies( 

1317 GroupName=resource["GroupName"] 

1318 ) 

1319 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']] 

1320 return policy_arns 

1321 

1322 

1323@Role.action_registry.register('delete') 

1324class RoleDelete(BaseAction): 

1325 """Delete an IAM Role. 

1326 

1327 To delete IAM Role you must first delete the policies 

1328 that are associated with the role. Also, you need to remove 

1329 the role from all instance profiles that the role is in. 

1330 

1331 https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_manage_delete.html 

1332 

1333 For this case option 'force' is used. If you set it as 'true', 

1334 policies that are associated with the role would be detached 

1335 (inline policies would be removed) and all instance profiles 

1336 the role is in would be removed as well as the role. 

1337 

1338 For example, if you want to automatically delete an unused IAM role. 

1339 

1340 :example: 

1341 

1342 .. code-block:: yaml 

1343 

1344 - name: iam-delete-unused-role 

1345 resource: iam-role 

1346 filters: 

1347 - type: usage 

1348 match-operator: all 

1349 LastAuthenticated: null 

1350 actions: 

1351 - type: delete 

1352 force: true 

1353 

1354 """ 

1355 schema = type_schema('delete', force={'type': 'boolean'}) 

1356 permissions = ('iam:DeleteRole', 'iam:DeleteInstanceProfile',) 

1357 

1358 def detach_inline_policies(self, client, r): 

1359 policies = (self.manager.retry( 

1360 client.list_role_policies, RoleName=r['RoleName'], 

1361 ignore_err_codes=('NoSuchEntityException',)) or {}).get('PolicyNames', ()) 

1362 for p in policies: 

1363 self.manager.retry( 

1364 client.delete_role_policy, 

1365 RoleName=r['RoleName'], PolicyName=p, 

1366 ignore_err_codes=('NoSuchEntityException',)) 

1367 

1368 def delete_instance_profiles(self, client, r): 

1369 # An instance profile can contain only one IAM role, 

1370 # although a role can be included in multiple instance profiles 

1371 profile_names = [] 

1372 profiles = self.manager.retry( 

1373 client.list_instance_profiles_for_role, 

1374 RoleName=r['RoleName'], 

1375 ignore_err_codes=('NoSuchEntityException',)) 

1376 if profiles: 

1377 profile_names = [p.get('InstanceProfileName') for p in profiles['InstanceProfiles']] 

1378 for p in profile_names: 

1379 self.manager.retry( 

1380 client.remove_role_from_instance_profile, 

1381 RoleName=r['RoleName'], InstanceProfileName=p, 

1382 ignore_err_codes=('NoSuchEntityException',)) 

1383 self.manager.retry( 

1384 client.delete_instance_profile, 

1385 InstanceProfileName=p, 

1386 ignore_err_codes=('NoSuchEntityException',)) 

1387 

1388 def process(self, resources): 

1389 client = local_session(self.manager.session_factory).client('iam') 

1390 error = None 

1391 if self.data.get('force', False): 

1392 policy_setter = self.manager.action_registry['set-policy']( 

1393 {'state': 'detached', 'arn': '*'}, self.manager) 

1394 policy_setter.process(resources) 

1395 

1396 for r in resources: 

1397 if self.data.get('force', False): 

1398 self.detach_inline_policies(client, r) 

1399 self.delete_instance_profiles(client, r) 

1400 try: 

1401 client.delete_role(RoleName=r['RoleName']) 

1402 except client.exceptions.DeleteConflictException as e: 

1403 self.log.warning( 

1404 ("Role:%s cannot be deleted, set force " 

1405 "to detach policy, instance profile and delete, error: %s") % ( 

1406 r['Arn'], str(e))) 

1407 error = e 

1408 except (client.exceptions.NoSuchEntityException, 

1409 client.exceptions.UnmodifiableEntityException): 

1410 continue 

1411 if error: 

1412 raise error 

1413 

1414 

1415###################### 

1416# IAM Policies # 

1417###################### 

1418 

1419 

1420@Policy.filter_registry.register('used') 

1421class UsedIamPolicies(Filter): 

1422 """Filter IAM policies that are being used 

1423 (either attached to some roles or used as a permissions boundary). 

1424 

1425 :example: 

1426 

1427 .. code-block:: yaml 

1428 

1429 policies: 

1430 - name: iam-policy-used 

1431 resource: iam-policy 

1432 filters: 

1433 - type: used 

1434 """ 

1435 

1436 schema = type_schema('used') 

1437 permissions = ('iam:ListPolicies',) 

1438 

1439 def process(self, resources, event=None): 

1440 return [r for r in resources if 

1441 r['AttachmentCount'] > 0 or r.get('PermissionsBoundaryUsageCount', 0) > 0] 

1442 

1443 

1444@Policy.filter_registry.register('unused') 

1445class UnusedIamPolicies(Filter): 

1446 """Filter IAM policies that are not being used 

1447 (neither attached to any roles nor used as a permissions boundary). 

1448 

1449 :example: 

1450 

1451 .. code-block:: yaml 

1452 

1453 policies: 

1454 - name: iam-policy-unused 

1455 resource: iam-policy 

1456 filters: 

1457 - type: unused 

1458 """ 

1459 

1460 schema = type_schema('unused') 

1461 permissions = ('iam:ListPolicies',) 

1462 

1463 def process(self, resources, event=None): 

1464 return [r for r in resources if 

1465 r['AttachmentCount'] == 0 and r.get('PermissionsBoundaryUsageCount', 0) == 0] 

1466 

1467 

1468@Policy.filter_registry.register('has-allow-all') 

1469class AllowAllIamPolicies(Filter): 

1470 """Check if IAM policy resource(s) have allow-all IAM policy statement block. 

1471 

1472 This allows users to implement CIS AWS check 1.24 which states that no 

1473 policy must exist with the following requirements. 

1474 

1475 Policy must have 'Action' and Resource = '*' with 'Effect' = 'Allow' 

1476 

1477 The policy will trigger on the following IAM policy (statement). 

1478 For example: 

1479 

1480 .. code-block:: json 

1481 

1482 { 

1483 "Version": "2012-10-17", 

1484 "Statement": [{ 

1485 "Action": "*", 

1486 "Resource": "*", 

1487 "Effect": "Allow" 

1488 }] 

1489 } 

1490 

1491 Additionally, the policy checks if the statement has no 'Condition' or 

1492 'NotAction'. 

1493 

1494 For example, if the user wants to check all used policies and filter on 

1495 allow all: 

1496 

1497 .. code-block:: yaml 

1498 

1499 - name: iam-no-used-all-all-policy 

1500 resource: iam-policy 

1501 filters: 

1502 - type: used 

1503 - type: has-allow-all 

1504 

1505 Note that scanning and getting all policies and all statements can take 

1506 a while. Use it sparingly or combine it with filters such as 'used' as 

1507 above. 

1508 

1509 """ 

1510 schema = type_schema('has-allow-all') 

1511 permissions = ('iam:ListPolicies', 'iam:ListPolicyVersions') 

1512 

1513 def has_allow_all_policy(self, client, resource): 

1514 statements = client.get_policy_version( 

1515 PolicyArn=resource['Arn'], 

1516 VersionId=resource['DefaultVersionId'] 

1517 )['PolicyVersion']['Document']['Statement'] 

1518 if isinstance(statements, dict): 

1519 statements = [statements] 

1520 

1521 for s in statements: 

1522 if ('Condition' not in s and 

1523 'Action' in s and 

1524 isinstance(s['Action'], str) and 

1525 s['Action'] == "*" and 

1526 'Resource' in s and 

1527 isinstance(s['Resource'], str) and 

1528 s['Resource'] == "*" and 

1529 s['Effect'] == "Allow"): 

1530 return True 

1531 return False 

1532 

1533 def process(self, resources, event=None): 

1534 c = local_session(self.manager.session_factory).client('iam') 

1535 results = [r for r in resources if self.has_allow_all_policy(c, r)] 

1536 self.log.info( 

1537 "%d of %d iam policies have allow all.", 

1538 len(results), len(resources)) 

1539 return results 

1540 

1541 

1542@Policy.action_registry.register('delete') 

1543class PolicyDelete(BaseAction): 

1544 """Delete an IAM Policy. 

1545 

1546 For example, if you want to automatically delete all unused IAM policies. 

1547 

1548 :example: 

1549 

1550 .. code-block:: yaml 

1551 

1552 - name: iam-delete-unused-policies 

1553 resource: iam-policy 

1554 filters: 

1555 - type: unused 

1556 actions: 

1557 - delete 

1558 

1559 """ 

1560 schema = type_schema('delete') 

1561 permissions = ('iam:DeletePolicy',) 

1562 

1563 def process(self, resources): 

1564 client = local_session(self.manager.session_factory).client('iam') 

1565 

1566 rcount = len(resources) 

1567 resources = [r for r in resources if Arn.parse(r['Arn']).account_id != 'aws'] 

1568 if len(resources) != rcount: 

1569 self.log.warning("Implicitly filtering AWS managed policies: %d -> %d", 

1570 rcount, len(resources)) 

1571 

1572 for r in resources: 

1573 if r.get('DefaultVersionId', '') != 'v1': 

1574 versions = [v['VersionId'] for v in client.list_policy_versions( 

1575 PolicyArn=r['Arn']).get('Versions') if not v.get('IsDefaultVersion')] 

1576 for v in versions: 

1577 client.delete_policy_version(PolicyArn=r['Arn'], VersionId=v) 

1578 client.delete_policy(PolicyArn=r['Arn']) 

1579 

1580 

1581############################### 

1582# IAM Instance Profiles # 

1583############################### 

1584 

1585 

1586@InstanceProfile.filter_registry.register('used') 

1587class UsedInstanceProfiles(IamRoleUsage): 

1588 """Filter IAM profiles that are being used. 

1589 

1590 :example: 

1591 

1592 .. code-block:: yaml 

1593 

1594 policies: 

1595 - name: iam-instance-profiles-in-use 

1596 resource: iam-profile 

1597 filters: 

1598 - type: used 

1599 """ 

1600 

1601 schema = type_schema('used') 

1602 

1603 def process(self, resources, event=None): 

1604 results = [] 

1605 profiles = self.instance_profile_usage() 

1606 for r in resources: 

1607 if r['Arn'] in profiles or r['InstanceProfileName'] in profiles: 

1608 results.append(r) 

1609 self.log.info( 

1610 "%d of %d instance profiles currently in use." % ( 

1611 len(results), len(resources))) 

1612 return results 

1613 

1614 

1615@InstanceProfile.filter_registry.register('unused') 

1616class UnusedInstanceProfiles(IamRoleUsage): 

1617 """Filter IAM profiles that are not being used 

1618 

1619 :example: 

1620 

1621 .. code-block:: yaml 

1622 

1623 policies: 

1624 - name: iam-instance-profiles-not-in-use 

1625 resource: iam-profile 

1626 filters: 

1627 - type: unused 

1628 """ 

1629 

1630 schema = type_schema('unused') 

1631 

1632 def process(self, resources, event=None): 

1633 results = [] 

1634 profiles = self.instance_profile_usage() 

1635 for r in resources: 

1636 if (r['Arn'] not in profiles and r['InstanceProfileName'] not in profiles): 

1637 results.append(r) 

1638 self.log.info( 

1639 "%d of %d instance profiles currently not in use." % ( 

1640 len(results), len(resources))) 

1641 return results 

1642 

1643 

1644@InstanceProfile.action_registry.register('set-role') 

1645class InstanceProfileSetRole(BaseAction): 

1646 """Upserts specified role name for IAM instance profiles. 

1647 Instance profile roles are removed when empty role name is specified. 

1648 

1649 :example: 

1650 

1651 .. code-block:: yaml 

1652 

1653 policies: 

1654 - name: iam-instance-profile-set-role 

1655 resource: iam-profile 

1656 actions: 

1657 - type: set-role 

1658 role: my-test-role 

1659 """ 

1660 

1661 schema = type_schema('set-role', 

1662 role={'type': 'string'}) 

1663 permissions = ('iam:AddRoleToInstanceProfile', 'iam:RemoveRoleFromInstanceProfile',) 

1664 

1665 def add_role(self, client, resource, role): 

1666 self.manager.retry( 

1667 client.add_role_to_instance_profile, 

1668 InstanceProfileName=resource['InstanceProfileName'], 

1669 RoleName=role 

1670 ) 

1671 return 

1672 

1673 def remove_role(self, client, resource): 

1674 self.manager.retry( 

1675 client.remove_role_from_instance_profile, 

1676 InstanceProfileName=resource['InstanceProfileName'], 

1677 RoleName=resource['Roles'][0]['RoleName'] 

1678 ) 

1679 return 

1680 

1681 def process(self, resources): 

1682 client = local_session(self.manager.session_factory).client('iam') 

1683 role = self.data.get('role', '') 

1684 for r in resources: 

1685 if not role: 

1686 if len(r['Roles']) == 0: 

1687 continue 

1688 else: 

1689 self.remove_role(client, r) 

1690 else: 

1691 if len(r['Roles']) == 0: 

1692 self.add_role(client, r, role) 

1693 elif role == r['Roles'][0]['RoleName']: 

1694 continue 

1695 else: 

1696 self.remove_role(client, r) 

1697 self.add_role(client, r, role) 

1698 

1699 

1700@InstanceProfile.action_registry.register("set-policy") 

1701class SetInstanceProfileRolePolicy(SetPolicy): 

1702 """Set a specific IAM policy as attached or detached on an instance profile role. 

1703 

1704 You will identify the policy by its arn. 

1705 

1706 Returns a list of roles modified by the action. 

1707 

1708 For example, if you want to automatically attach a single policy while 

1709 detaching all exisitng policies: 

1710 

1711 :example: 

1712 

1713 .. code-block:: yaml 

1714 

1715 - name: iam-attach-instance-profile-role-policy 

1716 resource: iam-profile 

1717 filters: 

1718 - not: 

1719 - type: has-specific-managed-policy 

1720 value: my-iam-policy 

1721 actions: 

1722 - type: set-policy 

1723 state: attached 

1724 arn: arn:aws:iam::123456789012:policy/my-iam-policy 

1725 

1726 """ 

1727 

1728 permissions = ('iam:AttachRolePolicy', 'iam:DetachRolePolicy', "iam:ListAttachedRolePolicies",) 

1729 

1730 def attach_policy(self, client, resource, policy_arn): 

1731 client.attach_role_policy( 

1732 RoleName=resource["Roles"][0]["RoleName"], 

1733 PolicyArn=policy_arn 

1734 ) 

1735 

1736 def detach_policy(self, client, resource, policy_arn): 

1737 try: 

1738 client.detach_role_policy( 

1739 RoleName=resource["Roles"][0]["RoleName"], 

1740 PolicyArn=policy_arn 

1741 ) 

1742 except client.exceptions.NoSuchEntityException: 

1743 return 

1744 

1745 def list_attached_policies(self, client, resource): 

1746 attached_policies = client.list_attached_role_policies( 

1747 RoleName=resource["Roles"][0]["RoleName"] 

1748 ) 

1749 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']] 

1750 return policy_arns 

1751 

1752 

1753################### 

1754# IAM Users # 

1755################### 

1756 

1757class CredentialReport(Filter): 

1758 """Use IAM Credential report to filter users. 

1759 

1760 The IAM Credential report aggregates multiple pieces of 

1761 information on iam users. This makes it highly efficient for 

1762 querying multiple aspects of a user that would otherwise require 

1763 per user api calls. 

1764 

1765 https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html 

1766 

1767 For example if we wanted to retrieve all users with mfa who have 

1768 never used their password but have active access keys from the 

1769 last month 

1770 

1771 .. code-block:: yaml 

1772 

1773 - name: iam-mfa-active-keys-no-login 

1774 resource: iam-user 

1775 filters: 

1776 - type: credential 

1777 key: mfa_active 

1778 value: true 

1779 - type: credential 

1780 key: password_last_used 

1781 value: absent 

1782 - type: credential 

1783 key: access_keys.last_used_date 

1784 value_type: age 

1785 value: 30 

1786 op: less-than 

1787 

1788 Credential Report Transforms 

1789 

1790 We perform some default transformations from the raw 

1791 credential report. Sub-objects (access_key_1, cert_2) 

1792 are turned into array of dictionaries for matching 

1793 purposes with their common prefixes stripped. 

1794 N/A values are turned into None, TRUE/FALSE are turned 

1795 into boolean values. 

1796 

1797 """ 

1798 schema = type_schema( 

1799 'credential', 

1800 value_type={'$ref': '#/definitions/filters_common/value_types'}, 

1801 key={'type': 'string', 

1802 'title': 'report key to search', 

1803 'enum': [ 

1804 'user', 

1805 'arn', 

1806 'user_creation_time', 

1807 'password_enabled', 

1808 'password_last_used', 

1809 'password_last_changed', 

1810 'password_next_rotation', 

1811 'mfa_active', 

1812 'access_keys', 

1813 'access_keys.active', 

1814 'access_keys.last_used_date', 

1815 'access_keys.last_used_region', 

1816 'access_keys.last_used_service', 

1817 'access_keys.last_rotated', 

1818 'certs', 

1819 'certs.active', 

1820 'certs.last_rotated', 

1821 ]}, 

1822 value={'$ref': '#/definitions/filters_common/value'}, 

1823 op={'$ref': '#/definitions/filters_common/comparison_operators'}, 

1824 report_generate={ 

1825 'title': 'Generate a report if none is present.', 

1826 'default': True, 

1827 'type': 'boolean'}, 

1828 report_delay={ 

1829 'title': 'Number of seconds to wait for report generation.', 

1830 'default': 10, 

1831 'type': 'number'}, 

1832 report_max_age={ 

1833 'title': 'Number of seconds to consider a report valid.', 

1834 'default': 60 * 60 * 24, 

1835 'type': 'number'}) 

1836 

1837 list_sub_objects = ( 

1838 ('access_key_1_', 'access_keys'), 

1839 ('access_key_2_', 'access_keys'), 

1840 ('cert_1_', 'certs'), 

1841 ('cert_2_', 'certs')) 

1842 

1843 # for access keys only 

1844 matched_annotation_key = 'c7n:matched-keys' 

1845 

1846 permissions = ('iam:GenerateCredentialReport', 

1847 'iam:GetCredentialReport') 

1848 

1849 def get_value_or_schema_default(self, k): 

1850 if k in self.data: 

1851 return self.data[k] 

1852 return self.schema['properties'][k]['default'] 

1853 

1854 def get_credential_report(self): 

1855 cache = self.manager._cache 

1856 with cache: 

1857 cache_key = {'account': self.manager.config.account_id, 'iam-credential-report': True} 

1858 report = cache.get(cache_key) 

1859 

1860 if report: 

1861 return report 

1862 data = self.fetch_credential_report() 

1863 report = {} 

1864 if isinstance(data, bytes): 

1865 reader = csv.reader(io.StringIO(data.decode('utf-8'))) 

1866 else: 

1867 reader = csv.reader(io.StringIO(data)) 

1868 headers = next(reader) 

1869 for line in reader: 

1870 info = dict(zip(headers, line)) 

1871 report[info['user']] = self.process_user_record(info) 

1872 cache.save(cache_key, report) 

1873 

1874 return report 

1875 

1876 @classmethod 

1877 def process_user_record(cls, info): 

1878 """Type convert the csv record, modifies in place.""" 

1879 keys = list(info.keys()) 

1880 # Value conversion 

1881 for k in keys: 

1882 v = info[k] 

1883 if v in ('N/A', 'no_information'): 

1884 info[k] = None 

1885 elif v == 'false': 

1886 info[k] = False 

1887 elif v == 'true': 

1888 info[k] = True 

1889 # Object conversion 

1890 for p, t in cls.list_sub_objects: 

1891 obj = dict([(k[len(p):], info.pop(k)) 

1892 for k in keys if k.startswith(p)]) 

1893 if obj.get('active', False) or obj.get('last_rotated', False): 

1894 info.setdefault(t, []).append(obj) 

1895 return info 

1896 

1897 def fetch_credential_report(self): 

1898 client = local_session(self.manager.session_factory).client('iam') 

1899 try: 

1900 report = client.get_credential_report() 

1901 except ClientError as e: 

1902 if e.response['Error']['Code'] == 'ReportNotPresent': 

1903 report = None 

1904 elif e.response['Error']['Code'] == 'ReportInProgress': 

1905 # Someone else asked for the report before it was done. Wait 

1906 # for it again. 

1907 time.sleep(self.get_value_or_schema_default('report_delay')) 

1908 report = client.get_credential_report() 

1909 else: 

1910 raise 

1911 if report: 

1912 threshold = datetime.datetime.now(tz=tzutc()) - timedelta( 

1913 seconds=self.get_value_or_schema_default( 

1914 'report_max_age')) 

1915 if not report['GeneratedTime'].tzinfo: 

1916 threshold = threshold.replace(tzinfo=None) 

1917 if report['GeneratedTime'] < threshold: 

1918 report = None 

1919 if report is None: 

1920 if not self.get_value_or_schema_default('report_generate'): 

1921 raise ValueError("Credential Report Not Present") 

1922 client.generate_credential_report() 

1923 time.sleep(self.get_value_or_schema_default('report_delay')) 

1924 report = client.get_credential_report() 

1925 return report['Content'] 

1926 

1927 def process(self, resources, event=None): 

1928 if '.' in self.data['key']: 

1929 self.matcher_config = dict(self.data) 

1930 self.matcher_config['key'] = self.data['key'].split('.', 1)[1] 

1931 return [] 

1932 

1933 def match(self, resource, info): 

1934 if info is None: 

1935 return False 

1936 k = self.data.get('key') 

1937 if '.' not in k: 

1938 vf = ValueFilter(self.data) 

1939 vf.annotate = False 

1940 return vf(info) 

1941 

1942 # access key matching 

1943 prefix, _ = k.split('.', 1) 

1944 vf = ValueFilter(self.matcher_config) 

1945 vf.annotate = False 

1946 

1947 # annotation merging with previous respecting block operators 

1948 k_matched = [] 

1949 for v in info.get(prefix, ()): 

1950 if vf.match(v): 

1951 k_matched.append(v) 

1952 

1953 for k in k_matched: 

1954 k['c7n:match-type'] = 'credential' 

1955 

1956 self.merge_annotation(resource, self.matched_annotation_key, k_matched) 

1957 return bool(k_matched) 

1958 

1959 

1960@User.filter_registry.register('credential') 

1961class UserCredentialReport(CredentialReport): 

1962 

1963 def process(self, resources, event=None): 

1964 super(UserCredentialReport, self).process(resources, event) 

1965 report = self.get_credential_report() 

1966 if report is None: 

1967 return [] 

1968 results = [] 

1969 for r in resources: 

1970 info = report.get(r['UserName']) 

1971 if self.match(r, info): 

1972 r['c7n:credential-report'] = info 

1973 results.append(r) 

1974 return results 

1975 

1976 

1977@User.filter_registry.register('has-inline-policy') 

1978class IamUserInlinePolicy(Filter): 

1979 """ 

1980 Filter IAM users that have an inline-policy attached 

1981 

1982 True: Filter users that have an inline-policy 

1983 False: Filter users that do not have an inline-policy 

1984 """ 

1985 

1986 schema = type_schema('has-inline-policy', value={'type': 'boolean'}) 

1987 permissions = ('iam:ListUserPolicies',) 

1988 

1989 def _inline_policies(self, client, resource): 

1990 resource['c7n:InlinePolicies'] = client.list_user_policies( 

1991 UserName=resource['UserName'])['PolicyNames'] 

1992 return resource 

1993 

1994 def process(self, resources, event=None): 

1995 c = local_session(self.manager.session_factory).client('iam') 

1996 value = self.data.get('value', True) 

1997 res = [] 

1998 for r in resources: 

1999 r = self._inline_policies(c, r) 

2000 if len(r['c7n:InlinePolicies']) > 0 and value: 

2001 res.append(r) 

2002 if len(r['c7n:InlinePolicies']) == 0 and not value: 

2003 res.append(r) 

2004 return res 

2005 

2006 

2007@User.filter_registry.register('policy') 

2008class UserPolicy(ValueFilter): 

2009 """Filter IAM users based on attached policy values 

2010 

2011 :example: 

2012 

2013 .. code-block:: yaml 

2014 

2015 policies: 

2016 - name: iam-users-with-admin-access 

2017 resource: iam-user 

2018 filters: 

2019 - type: policy 

2020 key: PolicyName 

2021 value: AdministratorAccess 

2022 include-via: true 

2023 """ 

2024 

2025 schema = type_schema('policy', rinherit=ValueFilter.schema, 

2026 **{'include-via': {'type': 'boolean'}}) 

2027 schema_alias = False 

2028 permissions = ( 

2029 'iam:ListAttachedUserPolicies', 

2030 'iam:ListGroupsForUser', 

2031 'iam:ListAttachedGroupPolicies', 

2032 ) 

2033 

2034 def find_in_user_set(self, user_set, search_key, arn_key, arn): 

2035 for u in user_set: 

2036 if search_key in u: 

2037 searched = next((v for v in u[search_key] if v.get(arn_key) == arn), None) 

2038 if searched is not None: 

2039 return searched 

2040 

2041 return None 

2042 

2043 def user_groups_policies(self, client, user_set, u): 

2044 u['c7n:Groups'] = client.list_groups_for_user( 

2045 UserName=u['UserName'])['Groups'] 

2046 

2047 for ug in u['c7n:Groups']: 

2048 ug_searched = self.find_in_user_set(user_set, 'c7n:Groups', 'Arn', ug['Arn']) 

2049 if ug_searched and ug_searched.get('AttachedPolicies'): 

2050 ug['AttachedPolicies'] = ug_searched['AttachedPolicies'] 

2051 else: 

2052 ug['AttachedPolicies'] = client.list_attached_group_policies( 

2053 GroupName=ug['GroupName'])['AttachedPolicies'] 

2054 

2055 for ap in ug['AttachedPolicies']: 

2056 p_searched = self.find_in_user_set([u], 'c7n:Policies', 'Arn', ap['PolicyArn']) 

2057 if not p_searched: 

2058 p_searched = self.find_in_user_set( 

2059 user_set, 'c7n:Policies', 'Arn', ap['PolicyArn'] 

2060 ) 

2061 if p_searched: 

2062 u['c7n:Policies'].append(p_searched) 

2063 else: 

2064 u['c7n:Policies'].append( 

2065 client.get_policy(PolicyArn=ap['PolicyArn'])['Policy']) 

2066 

2067 return u 

2068 

2069 def user_policies(self, user_set): 

2070 client = local_session(self.manager.session_factory).client('iam') 

2071 for u in user_set: 

2072 if 'c7n:Policies' not in u: 

2073 u['c7n:Policies'] = [] 

2074 aps = client.list_attached_user_policies( 

2075 UserName=u['UserName'])['AttachedPolicies'] 

2076 for ap in aps: 

2077 u['c7n:Policies'].append( 

2078 client.get_policy(PolicyArn=ap['PolicyArn'])['Policy']) 

2079 if self.data.get('include-via'): 

2080 u = self.user_groups_policies(client, user_set, u) 

2081 

2082 def process(self, resources, event=None): 

2083 user_set = chunks(resources, size=50) 

2084 with self.executor_factory(max_workers=2) as w: 

2085 self.log.debug( 

2086 "Querying %d users policies" % len(resources)) 

2087 list(w.map(self.user_policies, user_set)) 

2088 

2089 matched = [] 

2090 for r in resources: 

2091 for p in r['c7n:Policies']: 

2092 if self.match(p) and r not in matched: 

2093 matched.append(r) 

2094 return matched 

2095 

2096 

2097@User.filter_registry.register('group') 

2098class GroupMembership(ValueFilter): 

2099 """Filter IAM users based on attached group values 

2100 

2101 :example: 

2102 

2103 .. code-block:: yaml 

2104 

2105 policies: 

2106 - name: iam-users-in-admin-group 

2107 resource: iam-user 

2108 filters: 

2109 - type: group 

2110 key: GroupName 

2111 value: Admins 

2112 """ 

2113 

2114 schema = type_schema('group', rinherit=ValueFilter.schema) 

2115 schema_alias = False 

2116 permissions = ('iam:ListGroupsForUser',) 

2117 

2118 def get_user_groups(self, client, user_set): 

2119 for u in user_set: 

2120 u['c7n:Groups'] = client.list_groups_for_user( 

2121 UserName=u['UserName'])['Groups'] 

2122 

2123 def process(self, resources, event=None): 

2124 client = local_session(self.manager.session_factory).client('iam') 

2125 with self.executor_factory(max_workers=2) as w: 

2126 futures = [] 

2127 for user_set in chunks( 

2128 [r for r in resources if 'c7n:Groups' not in r], size=50): 

2129 futures.append( 

2130 w.submit(self.get_user_groups, client, user_set)) 

2131 for f in as_completed(futures): 

2132 pass 

2133 

2134 matched = [] 

2135 for r in resources: 

2136 for p in r.get('c7n:Groups', []): 

2137 if self.match(p) and r not in matched: 

2138 matched.append(r) 

2139 return matched 

2140 

2141 

2142@User.filter_registry.register('access-key') 

2143class UserAccessKey(ValueFilter): 

2144 """Filter IAM users based on access-key values 

2145 

2146 By default multiple uses of this filter will match 

2147 on any user key satisfying either filter. To find 

2148 specific keys that match multiple access-key filters, 

2149 use `match-operator: and` 

2150 

2151 :example: 

2152 

2153 .. code-block:: yaml 

2154 

2155 policies: 

2156 - name: iam-users-with-active-keys 

2157 resource: iam-user 

2158 filters: 

2159 - type: access-key 

2160 key: Status 

2161 value: Active 

2162 - type: access-key 

2163 match-operator: and 

2164 key: CreateDate 

2165 value_type: age 

2166 value: 90 

2167 """ 

2168 

2169 schema = type_schema( 

2170 'access-key', 

2171 rinherit=ValueFilter.schema, 

2172 **{'match-operator': {'enum': ['and', 'or']}}) 

2173 schema_alias = False 

2174 permissions = ('iam:ListAccessKeys',) 

2175 annotation_key = 'c7n:AccessKeys' 

2176 matched_annotation_key = 'c7n:matched-keys' 

2177 annotate = False 

2178 

2179 def get_user_keys(self, client, user_set): 

2180 for u in user_set: 

2181 u[self.annotation_key] = self.manager.retry( 

2182 client.list_access_keys, 

2183 UserName=u['UserName'])['AccessKeyMetadata'] 

2184 

2185 def process(self, resources, event=None): 

2186 client = local_session(self.manager.session_factory).client('iam') 

2187 with self.executor_factory(max_workers=2) as w: 

2188 augment_set = [r for r in resources if self.annotation_key not in r] 

2189 self.log.debug( 

2190 "Querying %d users' api keys" % len(augment_set)) 

2191 list(w.map( 

2192 functools.partial(self.get_user_keys, client), 

2193 chunks(augment_set, 50))) 

2194 

2195 matched = [] 

2196 match_op = self.data.get('match-operator', 'or') 

2197 for r in resources: 

2198 keys = r[self.annotation_key] 

2199 if self.matched_annotation_key in r and match_op == 'and': 

2200 keys = r[self.matched_annotation_key] 

2201 k_matched = [] 

2202 for k in keys: 

2203 if self.match(k): 

2204 k_matched.append(k) 

2205 for k in k_matched: 

2206 k['c7n:match-type'] = 'access' 

2207 self.merge_annotation(r, self.matched_annotation_key, k_matched) 

2208 if k_matched: 

2209 matched.append(r) 

2210 return matched 

2211 

2212 

2213@User.filter_registry.register('ssh-key') 

2214class UserSSHKeyFilter(ValueFilter): 

2215 """Filter IAM users based on uploaded SSH public keys 

2216 

2217 :example: 

2218 

2219 .. code-block:: yaml 

2220 

2221 policies: 

2222 - name: iam-users-with-old-ssh-keys 

2223 resource: iam-user 

2224 filters: 

2225 - type: ssh-key 

2226 key: Status 

2227 value: Active 

2228 - type: ssh-key 

2229 key: UploadDate 

2230 value_type: age 

2231 value: 90 

2232 """ 

2233 

2234 schema = type_schema( 

2235 'ssh-key', 

2236 rinherit=ValueFilter.schema) 

2237 schema_alias = False 

2238 permissions = ('iam:ListSSHPublicKeys',) 

2239 annotation_key = 'c7n:SSHKeys' 

2240 matched_annotation_key = 'c7n:matched-ssh-keys' 

2241 annotate = False 

2242 

2243 def get_user_ssh_keys(self, client, user_set): 

2244 for u in user_set: 

2245 u[self.annotation_key] = self.manager.retry( 

2246 client.list_ssh_public_keys, 

2247 UserName=u['UserName'])['SSHPublicKeys'] 

2248 

2249 def process(self, resources, event=None): 

2250 client = local_session(self.manager.session_factory).client('iam') 

2251 with self.executor_factory(max_workers=2) as w: 

2252 augment_set = [r for r in resources if self.annotation_key not in r] 

2253 self.log.debug( 

2254 "Querying %d users' SSH keys" % len(augment_set)) 

2255 list(w.map( 

2256 functools.partial(self.get_user_ssh_keys, client), 

2257 chunks(augment_set, 50))) 

2258 

2259 matched = [] 

2260 for r in resources: 

2261 matched_keys = [k for k in r[self.annotation_key] if self.match(k)] 

2262 self.merge_annotation(r, self.matched_annotation_key, matched_keys) 

2263 if matched_keys: 

2264 matched.append(r) 

2265 return matched 

2266 

2267 

2268@User.filter_registry.register('login-profile') 

2269class UserLoginProfile(ValueFilter): 

2270 """Filter IAM users that have an associated login-profile 

2271 

2272 For quicker evaluation and reduced API traffic, it is recommended to 

2273 instead use the 'credential' filter with 'password_enabled': true when 

2274 a delay of up to four hours for credential report syncing is acceptable. 

2275 

2276 (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html) 

2277 

2278 :example: 

2279 

2280 .. code-block: yaml 

2281 

2282 policies: 

2283 - name: iam-users-with-console-access 

2284 resource: iam-user 

2285 filters: 

2286 - type: login-profile 

2287 """ 

2288 

2289 schema = type_schema('login-profile', rinherit=ValueFilter.schema) 

2290 permissions = ('iam:GetLoginProfile',) 

2291 annotation_key = 'c7n:LoginProfile' 

2292 

2293 def user_login_profiles(self, user_set): 

2294 client = local_session(self.manager.session_factory).client('iam') 

2295 for u in user_set: 

2296 u[self.annotation_key] = False 

2297 try: 

2298 login_profile_resp = client.get_login_profile(UserName=u['UserName']) 

2299 if u['UserName'] == login_profile_resp['LoginProfile']['UserName']: 

2300 u[self.annotation_key] = True 

2301 except ClientError as e: 

2302 if e.response['Error']['Code'] not in ('NoSuchEntity',): 

2303 raise 

2304 

2305 def process(self, resources, event=None): 

2306 user_set = chunks(resources, size=50) 

2307 with self.executor_factory(max_workers=2) as w: 

2308 self.log.debug( 

2309 "Querying %d users for login profile" % len(resources)) 

2310 list(w.map(self.user_login_profiles, user_set)) 

2311 

2312 matched = [] 

2313 for r in resources: 

2314 if r[self.annotation_key]: 

2315 matched.append(r) 

2316 return matched 

2317 

2318 

2319# Mfa-device filter for iam-users 

2320@User.filter_registry.register('mfa-device') 

2321class UserMfaDevice(ValueFilter): 

2322 """Filter iam-users based on mfa-device status 

2323 

2324 :example: 

2325 

2326 .. code-block:: yaml 

2327 

2328 policies: 

2329 - name: mfa-enabled-users 

2330 resource: iam-user 

2331 filters: 

2332 - type: mfa-device 

2333 key: UserName 

2334 value: not-null 

2335 """ 

2336 

2337 schema = type_schema('mfa-device', rinherit=ValueFilter.schema) 

2338 schema_alias = False 

2339 permissions = ('iam:ListMFADevices',) 

2340 

2341 def __init__(self, *args, **kw): 

2342 super(UserMfaDevice, self).__init__(*args, **kw) 

2343 self.data['key'] = 'MFADevices' 

2344 

2345 def process(self, resources, event=None): 

2346 

2347 def _user_mfa_devices(resource): 

2348 client = local_session(self.manager.session_factory).client('iam') 

2349 resource['MFADevices'] = client.list_mfa_devices( 

2350 UserName=resource['UserName'])['MFADevices'] 

2351 

2352 with self.executor_factory(max_workers=2) as w: 

2353 query_resources = [ 

2354 r for r in resources if 'MFADevices' not in r] 

2355 self.log.debug( 

2356 "Querying %d users' mfa devices" % len(query_resources)) 

2357 list(w.map(_user_mfa_devices, query_resources)) 

2358 

2359 matched = [] 

2360 for r in resources: 

2361 if self.match(r): 

2362 matched.append(r) 

2363 

2364 return matched 

2365 

2366 

2367@User.action_registry.register('post-finding') 

2368class UserFinding(OtherResourcePostFinding): 

2369 

2370 def format_resource(self, r): 

2371 if any(filter(lambda x: isinstance(x, UserAccessKey), self.manager.iter_filters())): 

2372 details = { 

2373 "UserName": "arn:aws:iam:{}:user/{}".format( 

2374 self.manager.config.account_id, r["c7n:AccessKeys"][0]["UserName"] 

2375 ), 

2376 "Status": r["c7n:AccessKeys"][0]["Status"], 

2377 "CreatedAt": r["c7n:AccessKeys"][0]["CreateDate"].isoformat(), 

2378 } 

2379 accesskey = { 

2380 "Type": "AwsIamAccessKey", 

2381 "Id": r["c7n:AccessKeys"][0]["AccessKeyId"], 

2382 "Region": self.manager.config.region, 

2383 "Details": {"AwsIamAccessKey": filter_empty(details)}, 

2384 } 

2385 return filter_empty(accesskey) 

2386 else: 

2387 return super(UserFinding, self).format_resource(r) 

2388 

2389 

2390@User.action_registry.register('delete') 

2391class UserDelete(BaseAction): 

2392 """Delete a user or properties of a user. 

2393 

2394 For example if you want to have a whitelist of valid (machine-)users 

2395 and want to ensure that no users have been clicked without documentation. 

2396 

2397 You can use both the 'credential' or the 'username' 

2398 filter. 'credential' will have an SLA of 4h, 

2399 (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html), 

2400 but the added benefit of performing less API calls, whereas 

2401 'username' will make more API calls, but have a SLA of your cache. 

2402 

2403 :example: 

2404 

2405 .. code-block:: yaml 

2406 

2407 # using a 'credential' filter' 

2408 - name: iam-only-whitelisted-users-credential 

2409 resource: iam-user 

2410 filters: 

2411 - type: credential 

2412 key: user 

2413 op: not-in 

2414 value: 

2415 - valid-user-1 

2416 - valid-user-2 

2417 actions: 

2418 - delete 

2419 

2420 # using a 'username' filter with 'UserName' 

2421 - name: iam-only-whitelisted-users-username 

2422 resource: iam-user 

2423 filters: 

2424 - type: value 

2425 key: UserName 

2426 op: not-in 

2427 value: 

2428 - valid-user-1 

2429 - valid-user-2 

2430 actions: 

2431 - delete 

2432 

2433 # using a 'username' filter with 'Arn' 

2434 - name: iam-only-whitelisted-users-arn 

2435 resource: iam-user 

2436 filters: 

2437 - type: value 

2438 key: Arn 

2439 op: not-in 

2440 value: 

2441 - arn:aws:iam:123456789012:user/valid-user-1 

2442 - arn:aws:iam:123456789012:user/valid-user-2 

2443 actions: 

2444 - delete 

2445 

2446 Additionally, you can specify the options to delete properties of an iam-user, 

2447 including console-access, access-keys, attached-user-policies, 

2448 inline-user-policies, mfa-devices, groups, 

2449 ssh-keys, signing-certificates, and service-specific-credentials. 

2450 

2451 Note: using options will _not_ delete the user itself, only the items specified 

2452 by ``options`` that are attached to the respective iam-user. To delete a user 

2453 completely, use the ``delete`` action without specifying ``options``. 

2454 

2455 :example: 

2456 

2457 .. code-block:: yaml 

2458 

2459 - name: delete-console-access-unless-valid 

2460 comment: | 

2461 finds iam-users with console access and deletes console access unless 

2462 the username is included in whitelist 

2463 resource: iam-user 

2464 filters: 

2465 - type: value 

2466 key: UserName 

2467 op: not-in 

2468 value: 

2469 - valid-user-1 

2470 - valid-user-2 

2471 - type: credential 

2472 key: password_enabled 

2473 value: true 

2474 actions: 

2475 - type: delete 

2476 options: 

2477 - console-access 

2478 

2479 - name: delete-misc-access-for-iam-user 

2480 comment: | 

2481 deletes multiple options from test_user 

2482 resource: iam-user 

2483 filters: 

2484 - UserName: test_user 

2485 actions: 

2486 - type: delete 

2487 options: 

2488 - mfa-devices 

2489 - access-keys 

2490 - ssh-keys 

2491 """ 

2492 

2493 ORDERED_OPTIONS = OrderedDict([ 

2494 ('console-access', 'delete_console_access'), 

2495 ('access-keys', 'delete_access_keys'), 

2496 ('attached-user-policies', 'delete_attached_user_policies'), 

2497 ('inline-user-policies', 'delete_inline_user_policies'), 

2498 ('mfa-devices', 'delete_hw_mfa_devices'), 

2499 ('groups', 'delete_groups'), 

2500 ('ssh-keys', 'delete_ssh_keys'), 

2501 ('signing-certificates', 'delete_signing_certificates'), 

2502 ('service-specific-credentials', 'delete_service_specific_credentials'), 

2503 ]) 

2504 COMPOUND_OPTIONS = { 

2505 'user-policies': ['attached-user-policies', 'inline-user-policies'], 

2506 } 

2507 

2508 schema = type_schema( 

2509 'delete', 

2510 options={ 

2511 'type': 'array', 

2512 'items': { 

2513 'type': 'string', 

2514 'enum': list(ORDERED_OPTIONS.keys()) + list(COMPOUND_OPTIONS.keys()), 

2515 } 

2516 }) 

2517 

2518 permissions = ( 

2519 'iam:ListAttachedUserPolicies', 

2520 'iam:ListAccessKeys', 

2521 'iam:ListGroupsForUser', 

2522 'iam:ListMFADevices', 

2523 'iam:ListServiceSpecificCredentials', 

2524 'iam:ListSigningCertificates', 

2525 'iam:ListSSHPublicKeys', 

2526 'iam:DeactivateMFADevice', 

2527 'iam:DeleteAccessKey', 

2528 'iam:DeleteLoginProfile', 

2529 'iam:DeleteSigningCertificate', 

2530 'iam:DeleteSSHPublicKey', 

2531 'iam:DeleteUser', 

2532 'iam:DeleteUserPolicy', 

2533 'iam:DetachUserPolicy', 

2534 'iam:RemoveUserFromGroup') 

2535 

2536 @staticmethod 

2537 def delete_console_access(client, r): 

2538 try: 

2539 client.delete_login_profile( 

2540 UserName=r['UserName']) 

2541 except ClientError as e: 

2542 if e.response['Error']['Code'] not in ('NoSuchEntity',): 

2543 raise 

2544 

2545 @staticmethod 

2546 def delete_access_keys(client, r): 

2547 response = client.list_access_keys(UserName=r['UserName']) 

2548 for access_key in response['AccessKeyMetadata']: 

2549 client.delete_access_key(UserName=r['UserName'], 

2550 AccessKeyId=access_key['AccessKeyId']) 

2551 

2552 @staticmethod 

2553 def delete_attached_user_policies(client, r): 

2554 response = client.list_attached_user_policies(UserName=r['UserName']) 

2555 for user_policy in response['AttachedPolicies']: 

2556 client.detach_user_policy( 

2557 UserName=r['UserName'], PolicyArn=user_policy['PolicyArn']) 

2558 

2559 @staticmethod 

2560 def delete_inline_user_policies(client, r): 

2561 response = client.list_user_policies(UserName=r['UserName']) 

2562 for user_policy_name in response['PolicyNames']: 

2563 client.delete_user_policy( 

2564 UserName=r['UserName'], PolicyName=user_policy_name) 

2565 

2566 @staticmethod 

2567 def delete_hw_mfa_devices(client, r): 

2568 response = client.list_mfa_devices(UserName=r['UserName']) 

2569 for mfa_device in response['MFADevices']: 

2570 client.deactivate_mfa_device( 

2571 UserName=r['UserName'], SerialNumber=mfa_device['SerialNumber']) 

2572 

2573 @staticmethod 

2574 def delete_groups(client, r): 

2575 response = client.list_groups_for_user(UserName=r['UserName']) 

2576 for user_group in response['Groups']: 

2577 client.remove_user_from_group( 

2578 UserName=r['UserName'], GroupName=user_group['GroupName']) 

2579 

2580 @staticmethod 

2581 def delete_ssh_keys(client, r): 

2582 response = client.list_ssh_public_keys(UserName=r['UserName']) 

2583 for key in response.get('SSHPublicKeys', ()): 

2584 client.delete_ssh_public_key( 

2585 UserName=r['UserName'], SSHPublicKeyId=key['SSHPublicKeyId']) 

2586 

2587 @staticmethod 

2588 def delete_signing_certificates(client, r): 

2589 response = client.list_signing_certificates(UserName=r['UserName']) 

2590 for cert in response.get('Certificates', ()): 

2591 client.delete_signing_certificate( 

2592 UserName=r['UserName'], CertificateId=cert['CertificateId']) 

2593 

2594 @staticmethod 

2595 def delete_service_specific_credentials(client, r): 

2596 # Service specific user credentials (codecommit) 

2597 response = client.list_service_specific_credentials(UserName=r['UserName']) 

2598 for screds in response.get('ServiceSpecificCredentials', ()): 

2599 client.delete_service_specific_credential( 

2600 UserName=r['UserName'], 

2601 ServiceSpecificCredentialId=screds['ServiceSpecificCredentialId']) 

2602 

2603 @staticmethod 

2604 def delete_user(client, r): 

2605 client.delete_user(UserName=r['UserName']) 

2606 

2607 def process(self, resources): 

2608 client = local_session(self.manager.session_factory).client('iam') 

2609 self.log.debug('Deleting user %s options: %s' % 

2610 (len(resources), self.data.get('options', 'all'))) 

2611 for r in resources: 

2612 self.process_user(client, r) 

2613 

2614 def process_user(self, client, r): 

2615 user_options = self.data.get('options', list(self.ORDERED_OPTIONS.keys())) 

2616 # resolve compound options 

2617 for cmd in self.COMPOUND_OPTIONS: 

2618 if cmd in user_options: 

2619 user_options += self.COMPOUND_OPTIONS[cmd] 

2620 # process options in ordered fashion 

2621 for cmd in self.ORDERED_OPTIONS: 

2622 if cmd in user_options: 

2623 op = getattr(self, self.ORDERED_OPTIONS[cmd]) 

2624 op(client, r) 

2625 if not self.data.get('options'): 

2626 self.delete_user(client, r) 

2627 

2628 

2629@User.action_registry.register('remove-keys') 

2630class UserRemoveAccessKey(BaseAction): 

2631 """Delete or disable user's access keys. 

2632 

2633 For example if we wanted to disable keys 90 days after creation and 

2634 delete them 180 days after creation: 

2635 

2636 :example: 

2637 

2638 .. code-block:: yaml 

2639 

2640 - name: iam-mfa-active-key-no-login 

2641 resource: iam-user 

2642 actions: 

2643 - type: remove-keys 

2644 disable: true 

2645 age: 90 

2646 - type: remove-keys 

2647 age: 180 

2648 """ 

2649 

2650 schema = type_schema( 

2651 'remove-keys', 

2652 matched={'type': 'boolean'}, 

2653 age={'type': 'number'}, 

2654 disable={'type': 'boolean'}) 

2655 permissions = ('iam:ListAccessKeys', 'iam:UpdateAccessKey', 

2656 'iam:DeleteAccessKey') 

2657 

2658 def validate(self): 

2659 if self.data.get('matched') and self.data.get('age'): 

2660 raise PolicyValidationError( 

2661 "policy:%s cant mix matched and age parameters") 

2662 ftypes = {f.type for f in self.manager.iter_filters()} 

2663 if 'credential' in ftypes and 'access-key' in ftypes: 

2664 raise PolicyValidationError( 

2665 "policy:%s cant mix credential and access-key filters w/ delete action") 

2666 return self 

2667 

2668 def process(self, resources): 

2669 client = local_session(self.manager.session_factory).client('iam') 

2670 

2671 age = self.data.get('age') 

2672 disable = self.data.get('disable') 

2673 matched = self.data.get('matched') 

2674 

2675 if age: 

2676 threshold_date = datetime.datetime.now(tz=tzutc()) - timedelta(age) 

2677 

2678 for r in resources: 

2679 if 'c7n:AccessKeys' not in r: 

2680 r['c7n:AccessKeys'] = client.list_access_keys( 

2681 UserName=r['UserName'])['AccessKeyMetadata'] 

2682 

2683 keys = r['c7n:AccessKeys'] 

2684 if matched: 

2685 m_keys = resolve_credential_keys( 

2686 r.get(CredentialReport.matched_annotation_key), 

2687 keys) 

2688 # It is possible for a _user_ to match multiple credential filters 

2689 # without having any single key match them all. 

2690 if not m_keys: 

2691 continue 

2692 keys = m_keys 

2693 

2694 for k in keys: 

2695 if age: 

2696 if not k['CreateDate'] < threshold_date: 

2697 continue 

2698 if disable: 

2699 client.update_access_key( 

2700 UserName=r['UserName'], 

2701 AccessKeyId=k['AccessKeyId'], 

2702 Status='Inactive') 

2703 else: 

2704 client.delete_access_key( 

2705 UserName=r['UserName'], 

2706 AccessKeyId=k['AccessKeyId']) 

2707 

2708 

2709@User.action_registry.register('delete-ssh-keys') 

2710class UserDeleteSSHKey(BaseAction): 

2711 """Delete or disable a user's SSH keys. 

2712 

2713 For example to delete keys after 90 days: 

2714 

2715 :example: 

2716 

2717 .. code-block:: yaml 

2718 

2719 - name: iam-user-delete-ssh-keys 

2720 resource: iam-user 

2721 actions: 

2722 - type: delete-ssh-keys 

2723 """ 

2724 

2725 schema = type_schema( 

2726 'delete-ssh-keys', 

2727 matched={'type': 'boolean'}, 

2728 disable={'type': 'boolean'}) 

2729 annotation_key = 'c7n:SSHKeys' 

2730 permissions = ('iam:ListSSHPublicKeys', 'iam:UpdateSSHPublicKey', 

2731 'iam:DeleteSSHPublicKey') 

2732 

2733 def process(self, resources): 

2734 client = local_session(self.manager.session_factory).client('iam') 

2735 

2736 for r in resources: 

2737 if self.annotation_key not in r: 

2738 r[self.annotation_key] = client.list_ssh_public_keys( 

2739 UserName=r['UserName'])['SSHPublicKeys'] 

2740 

2741 keys = (r.get(UserSSHKeyFilter.matched_annotation_key, []) 

2742 if self.data.get('matched') else r[self.annotation_key]) 

2743 

2744 for k in keys: 

2745 if self.data.get('disable'): 

2746 client.update_ssh_public_key( 

2747 UserName=r['UserName'], 

2748 SSHPublicKeyId=k['SSHPublicKeyId'], 

2749 Status='Inactive') 

2750 else: 

2751 client.delete_ssh_public_key( 

2752 UserName=r['UserName'], 

2753 SSHPublicKeyId=k['SSHPublicKeyId']) 

2754 

2755 

2756def resolve_credential_keys(m_keys, keys): 

2757 res = [] 

2758 for k in m_keys: 

2759 if k['c7n:match-type'] == 'credential': 

2760 c_date = parse_date(k['last_rotated']) 

2761 for ak in keys: 

2762 if c_date == ak['CreateDate']: 

2763 ak = dict(ak) 

2764 ak['c7n:match-type'] = 'access' 

2765 if ak not in res: 

2766 res.append(ak) 

2767 elif k not in res: 

2768 res.append(k) 

2769 return res 

2770 

2771 

2772################# 

2773# IAM Groups # 

2774################# 

2775 

2776 

2777@Group.filter_registry.register('has-specific-managed-policy') 

2778class SpecificIamGroupManagedPolicy(Filter): 

2779 """Filter IAM groups that have a specific policy attached 

2780 

2781 For example, if the user wants to check all groups with 'admin-policy': 

2782 

2783 :example: 

2784 

2785 .. code-block:: yaml 

2786 

2787 policies: 

2788 - name: iam-groups-have-admin 

2789 resource: iam-group 

2790 filters: 

2791 - type: has-specific-managed-policy 

2792 value: admin-policy 

2793 """ 

2794 

2795 schema = type_schema('has-specific-managed-policy', value={'type': 'string'}) 

2796 permissions = ('iam:ListAttachedGroupPolicies',) 

2797 

2798 def _managed_policies(self, client, resource): 

2799 return [r['PolicyName'] for r in client.list_attached_group_policies( 

2800 GroupName=resource['GroupName'])['AttachedPolicies']] 

2801 

2802 def process(self, resources, event=None): 

2803 c = local_session(self.manager.session_factory).client('iam') 

2804 if self.data.get('value'): 

2805 results = [] 

2806 for r in resources: 

2807 r["ManagedPolicies"] = self._managed_policies(c, r) 

2808 if self.data.get('value') in r["ManagedPolicies"]: 

2809 results.append(r) 

2810 return results 

2811 return [] 

2812 

2813 

2814@Group.filter_registry.register('has-users') 

2815class IamGroupUsers(Filter): 

2816 """Filter IAM groups that have users attached based on True/False value: 

2817 True: Filter all IAM groups with users assigned to it 

2818 False: Filter all IAM groups without any users assigned to it 

2819 

2820 :example: 

2821 

2822 .. code-block:: yaml 

2823 

2824 - name: empty-iam-group 

2825 resource: iam-group 

2826 filters: 

2827 - type: has-users 

2828 value: False 

2829 """ 

2830 schema = type_schema('has-users', value={'type': 'boolean'}) 

2831 permissions = ('iam:GetGroup',) 

2832 

2833 def _user_count(self, client, resource): 

2834 return len(client.get_group(GroupName=resource['GroupName'])['Users']) 

2835 

2836 def process(self, resources, events=None): 

2837 c = local_session(self.manager.session_factory).client('iam') 

2838 if self.data.get('value', True): 

2839 return [r for r in resources if self._user_count(c, r) > 0] 

2840 return [r for r in resources if self._user_count(c, r) == 0] 

2841 

2842 

2843@Group.filter_registry.register('has-inline-policy') 

2844class IamGroupInlinePolicy(Filter): 

2845 """Filter IAM groups that have an inline-policy based on boolean value: 

2846 True: Filter all groups that have an inline-policy attached 

2847 False: Filter all groups that do not have an inline-policy attached 

2848 

2849 :example: 

2850 

2851 .. code-block:: yaml 

2852 

2853 - name: iam-groups-with-inline-policy 

2854 resource: iam-group 

2855 filters: 

2856 - type: has-inline-policy 

2857 value: True 

2858 """ 

2859 schema = type_schema('has-inline-policy', value={'type': 'boolean'}) 

2860 permissions = ('iam:ListGroupPolicies',) 

2861 

2862 def _inline_policies(self, client, resource): 

2863 resource['c7n:InlinePolicies'] = client.list_group_policies( 

2864 GroupName=resource['GroupName'])['PolicyNames'] 

2865 return resource 

2866 

2867 def process(self, resources, events=None): 

2868 c = local_session(self.manager.session_factory).client('iam') 

2869 value = self.data.get('value', True) 

2870 res = [] 

2871 for r in resources: 

2872 r = self._inline_policies(c, r) 

2873 if len(r['c7n:InlinePolicies']) > 0 and value: 

2874 res.append(r) 

2875 if len(r['c7n:InlinePolicies']) == 0 and not value: 

2876 res.append(r) 

2877 return res 

2878 

2879 

2880@Group.action_registry.register('delete-inline-policies') 

2881class GroupInlinePolicyDelete(BaseAction): 

2882 """Delete inline policies embedded in an IAM group. 

2883 

2884 :example: 

2885 

2886 .. code-block:: yaml 

2887 

2888 - name: iam-delete-group-policies 

2889 resource: aws.iam-group 

2890 filters: 

2891 - type: value 

2892 key: GroupName 

2893 value: test 

2894 actions: 

2895 - type: delete-inline-policies 

2896 """ 

2897 schema = type_schema('delete-inline-policies') 

2898 permissions = ('iam:ListGroupPolicies', 'iam:DeleteGroupPolicy',) 

2899 

2900 def process(self, resources): 

2901 client = local_session(self.manager.session_factory).client('iam') 

2902 for r in resources: 

2903 self.process_group(client, r) 

2904 

2905 def process_group(self, client, r): 

2906 if 'c7n:InlinePolicies' not in r: 

2907 r['c7n:InlinePolicies'] = client.list_group_policies( 

2908 GroupName=r['GroupName'])['PolicyNames'] 

2909 for policy in r.get('c7n:InlinePolicies', []): 

2910 try: 

2911 self.manager.retry(client.delete_group_policy, 

2912 GroupName=r['GroupName'], PolicyName=policy) 

2913 except client.exceptions.NoSuchEntityException: 

2914 continue 

2915 

2916 

2917@Group.action_registry.register('delete') 

2918class UserGroupDelete(BaseAction): 

2919 """Delete an IAM User Group. 

2920 

2921 For example, if you want to delete a group named 'test'. 

2922 

2923 :example: 

2924 

2925 .. code-block:: yaml 

2926 

2927 - name: iam-delete-user-group 

2928 resource: aws.iam-group 

2929 filters: 

2930 - type: value 

2931 key: GroupName 

2932 value: test 

2933 actions: 

2934 - type: delete 

2935 force: True 

2936 """ 

2937 schema = type_schema('delete', force={'type': 'boolean'}) 

2938 permissions = ('iam:DeleteGroup', 'iam:RemoveUserFromGroup') 

2939 

2940 def process(self, resources): 

2941 client = local_session(self.manager.session_factory).client('iam') 

2942 for r in resources: 

2943 self.process_group(client, r) 

2944 

2945 def process_group(self, client, r): 

2946 error = None 

2947 force = self.data.get('force', False) 

2948 if force: 

2949 users = client.get_group(GroupName=r['GroupName']).get('Users', []) 

2950 for user in users: 

2951 client.remove_user_from_group( 

2952 UserName=user['UserName'], GroupName=r['GroupName']) 

2953 

2954 try: 

2955 client.delete_group(GroupName=r['GroupName']) 

2956 except client.exceptions.DeleteConflictException as e: 

2957 self.log.warning( 

2958 ("Group:%s cannot be deleted, " 

2959 "set force to remove all users from group") 

2960 % r['Arn']) 

2961 error = e 

2962 except (client.exceptions.NoSuchEntityException, 

2963 client.exceptions.UnmodifiableEntityException): 

2964 pass 

2965 if error: 

2966 raise error 

2967 

2968 

2969class SamlProviderDescribe(DescribeSource): 

2970 

2971 def augment(self, resources): 

2972 super().augment(resources) 

2973 for r in resources: 

2974 md = r.get('SAMLMetadataDocument') 

2975 if not md: 

2976 continue 

2977 root = sso_metadata(md) 

2978 r['IDPSSODescriptor'] = root['IDPSSODescriptor'] 

2979 return resources 

2980 

2981 def get_permissions(self): 

2982 return ('iam:GetSAMLProvider', 'iam:ListSAMLProviders') 

2983 

2984 

2985def sso_metadata(md): 

2986 root = ElementTree.fromstringlist(md) 

2987 d = {} 

2988 _sso_recurse(root, d) 

2989 return d 

2990 

2991 

2992def _sso_recurse(node, d): 

2993 d.update(node.attrib) 

2994 for c in node: 

2995 k = c.tag.split('}', 1)[-1] 

2996 cd = {} 

2997 if k in d: 

2998 if not isinstance(d[k], list): 

2999 d[k] = [d[k]] 

3000 d[k].append(cd) 

3001 else: 

3002 d[k] = cd 

3003 _sso_recurse(c, cd) 

3004 if node.text and node.text.strip(): 

3005 d['Value'] = node.text.strip() 

3006 

3007 

3008@resources.register('iam-saml-provider') 

3009class SamlProvider(QueryResourceManager): 

3010 """SAML SSO Provider 

3011 

3012 we parse and expose attributes of the SAML Metadata XML Document 

3013 as resources attribute for use with custodian's standard value filter. 

3014 """ 

3015 

3016 class resource_type(TypeInfo): 

3017 

3018 service = 'iam' 

3019 name = id = 'Arn' 

3020 enum_spec = ('list_saml_providers', 'SAMLProviderList', None) 

3021 detail_spec = ('get_saml_provider', 'SAMLProviderArn', 'Arn', None) 

3022 arn = 'Arn' 

3023 arn_type = 'saml-provider' 

3024 config_type = "AWS::IAM::SAMLProvider" 

3025 global_resource = True 

3026 

3027 source_mapping = {'describe': SamlProviderDescribe} 

3028 

3029 

3030class OpenIdDescribe(DescribeSource): 

3031 

3032 def get_permissions(self): 

3033 return ('iam:GetOpenIDConnectProvider', 'iam:ListOpenIDConnectProviders') 

3034 

3035 

3036@resources.register('iam-oidc-provider') 

3037class OpenIdProvider(QueryResourceManager): 

3038 

3039 class resource_type(TypeInfo): 

3040 

3041 service = 'iam' 

3042 name = id = 'Arn' 

3043 enum_spec = ('list_open_id_connect_providers', 'OpenIDConnectProviderList', None) 

3044 detail_spec = ('get_open_id_connect_provider', 'OpenIDConnectProviderArn', 'Arn', None) 

3045 config_type = cfn_type = "AWS::IAM::OIDCProvider" 

3046 arn = 'Arn' 

3047 arn_type = 'oidc-provider' 

3048 global_resource = True 

3049 

3050 source_mapping = {'describe': OpenIdDescribe} 

3051 

3052 

3053@OpenIdProvider.action_registry.register('delete') 

3054class OpenIdProviderDelete(BaseAction): 

3055 """Delete an OpenID Connect IAM Identity Provider 

3056 

3057 For example, if you want to automatically delete an OIDC IdP for example.com 

3058 

3059 :example: 

3060 

3061 .. code-block:: yaml 

3062 

3063 - name: aws-iam-oidc-provider-delete 

3064 resource: iam-oidc-provider 

3065 filters: 

3066 - type: value 

3067 key: Url 

3068 value: example.com 

3069 actions: 

3070 - type: delete 

3071 

3072 """ 

3073 schema = type_schema('delete') 

3074 permissions = ('iam:DeleteOpenIDConnectProvider',) 

3075 

3076 def process(self, resources): 

3077 client = local_session(self.manager.session_factory).client('iam') 

3078 for provider in resources: 

3079 self.manager.retry( 

3080 client.delete_open_id_connect_provider, 

3081 OpenIDConnectProviderArn=provider['Arn'], 

3082 ignore_err_codes=( 

3083 'NoSuchEntityException', 

3084 'DeleteConflictException', 

3085 ), 

3086 ) 

3087 

3088 

3089@SamlProvider.action_registry.register('delete') 

3090class SamlProviderDelete(BaseAction): 

3091 """Delete a SAML IAM Identity Provider 

3092 

3093 For example, if you want to automatically delete an SAML IdP for unknown-idp 

3094 

3095 :example: 

3096 

3097 .. code-block:: yaml 

3098 

3099 - name: aws-iam-saml-provider-delete 

3100 resource: iam-saml-provider 

3101 filters: 

3102 - type: value 

3103 key: Name 

3104 value: unknown-idp 

3105 actions: 

3106 - type: delete 

3107 

3108 """ 

3109 schema = type_schema('delete') 

3110 permissions = ('iam:DeleteSAMLProvider',) 

3111 

3112 def process(self, resources): 

3113 client = local_session(self.manager.session_factory).client('iam') 

3114 for provider in resources: 

3115 self.manager.retry( 

3116 client.delete_saml_provider, 

3117 SAMLProviderArn=provider['Arn'], 

3118 ignore_err_codes=( 

3119 'NoSuchEntityException', 

3120 ), 

3121 ) 

3122 

3123 

3124@InstanceProfile.filter_registry.register('has-specific-managed-policy') 

3125class SpecificIamProfileManagedPolicy(ValueFilter): 

3126 """Filter an IAM instance profile that contains an IAM role that has a specific managed IAM 

3127 policy. If an IAM instance profile does not contain an IAM role, then it will be treated 

3128 as not having the policy. 

3129 

3130 :example: 

3131 

3132 Check for instance profile roles with 'admin-policy' attached: 

3133 

3134 .. code-block:: yaml 

3135 

3136 policies: 

3137 - name: iam-profiles-have-admin 

3138 resource: aws.iam-profile 

3139 filters: 

3140 - type: has-specific-managed-policy 

3141 value: admin-policy 

3142 

3143 :example: 

3144 

3145 Check for instance profile roles with an attached policy matching 

3146 a given list: 

3147 

3148 .. code-block:: yaml 

3149 

3150 policies: 

3151 - name: iam-profiles-with-selected-policies 

3152 resource: aws.iam-profile 

3153 filters: 

3154 - type: has-specific-managed-policy 

3155 value: 

3156 - AmazonS3FullAccess 

3157 - AWSOrganizationsFullAccess 

3158 

3159 :example: 

3160 

3161 Check for instance profile roles with attached policy names matching 

3162 a pattern: 

3163 

3164 .. code-block:: yaml 

3165 

3166 policies: 

3167 - name: iam-profiles-with-full-access-policies 

3168 resource: aws.iam-profile 

3169 filters: 

3170 - type: has-specific-managed-policy 

3171 op: glob 

3172 value: "*FullAccess" 

3173 

3174 Check for instance profile roles with attached policy ARNs matching 

3175 a pattern: 

3176 

3177 .. code-block:: yaml 

3178 

3179 policies: 

3180 - name: iam-profiles-with-aws-full-access-policies 

3181 resource: aws.iam-profile 

3182 filters: 

3183 - type: has-specific-managed-policy 

3184 key: PolicyArn 

3185 op: regex 

3186 value: "arn:aws:iam::aws:policy/.*FullAccess" 

3187 """ 

3188 

3189 schema = type_schema('has-specific-managed-policy', rinherit=ValueFilter.schema) 

3190 permissions = ('iam:ListAttachedRolePolicies',) 

3191 annotation_key = 'c7n:AttachedPolicies' 

3192 matched_annotation_key = 'c7n:MatchedPolicies' 

3193 schema_alias = False 

3194 

3195 def __init__(self, data, manager=None): 

3196 # Preserve backward compatibility 

3197 if 'key' not in data: 

3198 data['key'] = 'PolicyName' 

3199 super(SpecificIamProfileManagedPolicy, self).__init__(data, manager) 

3200 

3201 # This code assumes a single IAM role in the profile and the annotation 

3202 # is added to the profile resource, not the role 

3203 def get_managed_policies(self, client, prof_set): 

3204 for prof in prof_set: 

3205 prof[self.annotation_key] = [] 

3206 for role in prof.get('Roles', []): 

3207 prof[self.annotation_key] = [ 

3208 role_policy 

3209 for role_policy 

3210 in client.list_attached_role_policies( 

3211 RoleName=role['RoleName'])['AttachedPolicies'] 

3212 ] 

3213 

3214 def process(self, resources, event=None): 

3215 client = local_session(self.manager.session_factory).client('iam') 

3216 with self.executor_factory(max_workers=2) as w: 

3217 augment_set = [r for r in resources if self.annotation_key not in r] 

3218 self.log.debug( 

3219 "Querying %d roles' attached policies" % len(augment_set)) 

3220 list(w.map( 

3221 functools.partial(self.get_managed_policies, client), 

3222 chunks(augment_set, 50))) 

3223 

3224 matched = [] 

3225 for r in resources: 

3226 matched_keys = [k for k in r[self.annotation_key] if self.match(k)] 

3227 self.merge_annotation(r, self.matched_annotation_key, matched_keys) 

3228 if matched_keys: 

3229 matched.append(r) 

3230 return matched 

3231 

3232 

3233######################### 

3234# IAM Access Keys # 

3235######################### 

3236 

3237 

3238@resources.register('iam-access-key') 

3239class AccessKey(ChildResourceManager): 

3240 

3241 class resource_type(TypeInfo): 

3242 service = 'iam' 

3243 # Access keys don't have ARNs - they use AccessKeyId as identifier 

3244 # Using 'access-key' as arn_type for consistency but ARN construction will not be used 

3245 arn_type = 'access-key' 

3246 id = name = 'AccessKeyId' 

3247 date = 'CreateDate' 

3248 # Denotes this resource type exists across regions 

3249 global_resource = True 

3250 enum_spec = ('list_access_keys', 'AccessKeys', None) 

3251 parent_spec = ('iam-user', 'UserName', None) 

3252 # No detail spec needed as list_access_keys returns full metadata 

3253 cfn_type = config_type = "AWS::IAM::AccessKey" 

3254 # config_id = 'AccessKeyId' 

3255 

3256 source_mapping = { 

3257 'config': ConfigSource 

3258 }