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

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

1359 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 ConfigSource, QueryResourceManager, DescribeSource, TypeInfo 

30from c7n.resolver import ValuesFrom 

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

32from c7n.utils import ( 

33 get_partition, local_session, type_schema, chunks, filter_empty, QueryParser, 

34 select_keys 

35) 

36 

37from c7n.resources.aws import Arn 

38from c7n.resources.securityhub import OtherResourcePostFinding 

39 

40 

41class DescribeGroup(DescribeSource): 

42 

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

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

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

46 resources = [] 

47 for rid in resource_ids: 

48 try: 

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

50 except client.exceptions.NoSuchEntityException: 

51 continue 

52 group = result.pop('Group') 

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

54 resources.append(group) 

55 return resources 

56 

57 

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

59class Group(QueryResourceManager): 

60 

61 class resource_type(TypeInfo): 

62 service = 'iam' 

63 arn_type = 'group' 

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

65 id = name = 'GroupName' 

66 date = 'CreateDate' 

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

68 # Denotes this resource type exists across regions 

69 global_resource = True 

70 arn = 'Arn' 

71 

72 source_mapping = { 

73 'describe': DescribeGroup, 

74 'config': ConfigSource 

75 } 

76 

77 

78class DescribeRole(DescribeSource): 

79 

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

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

82 resources = [] 

83 for rid in resource_ids: 

84 if rid.startswith('arn'): 

85 rid = Arn.parse(rid).resource 

86 try: 

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

88 except client.exceptions.NoSuchEntityException: 

89 continue 

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

91 return resources 

92 

93 

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

95class Role(QueryResourceManager): 

96 

97 class resource_type(TypeInfo): 

98 service = 'iam' 

99 arn_type = 'role' 

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

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

102 id = name = 'RoleName' 

103 config_id = 'RoleId' 

104 date = 'CreateDate' 

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

106 # Denotes this resource type exists across regions 

107 global_resource = True 

108 arn = 'Arn' 

109 

110 source_mapping = { 

111 'describe': DescribeRole, 

112 'config': ConfigSource 

113 } 

114 

115 

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

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

118 

119 

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

121class RolePostFinding(OtherResourcePostFinding): 

122 

123 resource_type = 'AwsIamRole' 

124 

125 def format_resource(self, r): 

126 envelope, payload = self.format_envelope(r) 

127 payload.update(self.filter_empty( 

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

129 'MaxSessionDuration', 'Path', 'RoleId', 

130 'RoleName']))) 

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

132 payload['AssumeRolePolicyDocument']) 

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

134 return envelope 

135 

136 

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

138class RoleTag(Tag): 

139 """Tag an iam role.""" 

140 

141 permissions = ('iam:TagRole',) 

142 

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

144 for role in roles: 

145 try: 

146 self.manager.retry( 

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

148 except client.exceptions.NoSuchEntityException: 

149 continue 

150 

151 

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

153class RoleRemoveTag(RemoveTag): 

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

155 

156 permissions = ('iam:UntagRole',) 

157 

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

159 for role in roles: 

160 try: 

161 self.manager.retry( 

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

163 except client.exceptions.NoSuchEntityException: 

164 continue 

165 

166 

167class SetBoundary(BaseAction): 

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

169 

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

171 """ 

172 

173 schema = type_schema( 

174 'set-boundary', 

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

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

177 

178 def validate(self): 

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

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

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

182 

183 def process(self, resources): 

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

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

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

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

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

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

190 self.manager.account_id, policy) 

191 for r in resources: 

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

193 try: 

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

195 except client.exceptions.NoSuchEntityException: 

196 continue 

197 

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

199 raise NotImplementedError() 

200 

201 

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

203class RoleSetBoundary(SetBoundary): 

204 

205 def get_permissions(self): 

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

207 return ('iam:PutRolePermissionsBoundary',) 

208 return ('iam:DeleteRolePermissionsBoundary',) 

209 

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

211 if state: 

212 return client.put_role_permissions_boundary, { 

213 'RoleName': resource['RoleName'], 

214 'PermissionsBoundary': policy} 

215 else: 

216 return client.delete_role_permissions_boundary, { 

217 'RoleName': resource['RoleName']} 

218 

219 

220class DescribeUser(DescribeSource): 

221 

222 def augment(self, resources): 

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

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

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

226 results = [] 

227 for r in resources: 

228 ru = self.manager.retry( 

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

230 ignore_err_codes=client.exceptions.NoSuchEntityException) 

231 if ru: 

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

233 return list(filter(None, results)) 

234 

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

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

237 results = [] 

238 

239 for r in resource_ids: 

240 try: 

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

242 except client.exceptions.NoSuchEntityException: 

243 continue 

244 return results 

245 

246 

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

248class User(QueryResourceManager): 

249 

250 class resource_type(TypeInfo): 

251 service = 'iam' 

252 arn_type = 'user' 

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

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

255 id = name = 'UserName' 

256 date = 'CreateDate' 

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

258 # Denotes this resource type exists across regions 

259 global_resource = True 

260 arn = 'Arn' 

261 config_id = 'UserId' 

262 

263 source_mapping = { 

264 'describe': DescribeUser, 

265 'config': ConfigSource 

266 } 

267 

268 

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

270class UserTag(Tag): 

271 """Tag an iam user.""" 

272 

273 permissions = ('iam:TagUser',) 

274 

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

276 for u in users: 

277 try: 

278 self.manager.retry( 

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

280 except client.exceptions.NoSuchEntityException: 

281 continue 

282 

283 

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

285class UserRemoveTag(RemoveTag): 

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

287 

288 permissions = ('iam:UntagUser',) 

289 

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

291 for u in users: 

292 try: 

293 self.manager.retry( 

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

295 except client.exceptions.NoSuchEntityException: 

296 continue 

297 

298 

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

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

301 

302 

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

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

305 

306 

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

308class SetGroups(BaseAction): 

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

310 

311 :example: 

312 

313 .. code-block:: yaml 

314 

315 - name: iam-user-add-remove 

316 resource: iam-user 

317 filters: 

318 - type: value 

319 key: UserName 

320 value: Bob 

321 actions: 

322 - type: set-groups 

323 state: remove 

324 group: Admin 

325 

326 """ 

327 schema = type_schema( 

328 'set-groups', 

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

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

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

332 ) 

333 

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

335 

336 def validate(self): 

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

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

339 % (self.manager.data)) 

340 

341 def process(self, resources): 

342 group_name = self.data['group'] 

343 state = self.data['state'] 

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

345 op_map = { 

346 'add': client.add_user_to_group, 

347 'remove': client.remove_user_from_group 

348 } 

349 for r in resources: 

350 try: 

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

352 except client.exceptions.NoSuchEntityException: 

353 continue 

354 

355 

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

357class UserSetBoundary(SetBoundary): 

358 

359 def get_permissions(self): 

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

361 return ('iam:PutUserPermissionsBoundary',) 

362 return ('iam:DeleteUserPermissionsBoundary',) 

363 

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

365 if state: 

366 return client.put_user_permissions_boundary, { 

367 'UserName': resource['UserName'], 

368 'PermissionsBoundary': policy} 

369 else: 

370 return client.delete_user_permissions_boundary, { 

371 'UserName': resource['UserName']} 

372 

373 

374class DescribePolicy(DescribeSource): 

375 

376 def resources(self, query=None): 

377 qfilters = PolicyQueryParser.parse(self.manager.data.get('query', [])) 

378 query = query or {} 

379 if qfilters: 

380 query = {t['Name']: t['Value'] for t in qfilters} 

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

382 

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

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

385 results = [] 

386 

387 for r in resource_ids: 

388 try: 

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

390 except ClientError as e: 

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

392 continue 

393 return results 

394 

395 def augment(self, resources): 

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

397 

398 

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

400class Policy(QueryResourceManager): 

401 

402 class resource_type(TypeInfo): 

403 service = 'iam' 

404 arn_type = 'policy' 

405 enum_spec = ('list_policies', 'Policies', {'Scope': 'Local'}) 

406 id = 'PolicyId' 

407 name = 'PolicyName' 

408 date = 'CreateDate' 

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

410 # Denotes this resource type exists across regions 

411 global_resource = True 

412 arn = 'Arn' 

413 universal_taggable = object() 

414 

415 source_mapping = { 

416 'describe': DescribePolicy, 

417 'config': ConfigSource 

418 } 

419 

420 

421class PolicyQueryParser(QueryParser): 

422 

423 QuerySchema = { 

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

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

426 'PathPrefix': str, 

427 'OnlyAttached': bool 

428 } 

429 multi_value = False 

430 value_key = 'Value' 

431 

432 

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

434class InstanceProfile(QueryResourceManager): 

435 

436 class resource_type(TypeInfo): 

437 service = 'iam' 

438 arn_type = 'instance-profile' 

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

440 name = id = 'InstanceProfileName' 

441 date = 'CreateDate' 

442 # Denotes this resource type exists across regions 

443 global_resource = True 

444 arn = 'Arn' 

445 cfn_type = 'AWS::IAM::InstanceProfile' 

446 

447 

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

449class ServerCertificate(QueryResourceManager): 

450 

451 class resource_type(TypeInfo): 

452 service = 'iam' 

453 arn_type = 'server-certificate' 

454 enum_spec = ('list_server_certificates', 

455 'ServerCertificateMetadataList', 

456 None) 

457 name = id = 'ServerCertificateName' 

458 config_type = "AWS::IAM::ServerCertificate" 

459 name = 'ServerCertificateName' 

460 date = 'Expiration' 

461 # Denotes this resource type exists across regions 

462 global_resource = True 

463 

464 

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

466class CertificateDelete(BaseAction): 

467 """Delete an IAM Certificate 

468 

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

470 

471 :example: 

472 

473 .. code-block:: yaml 

474 

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

476 resource: iam-certificate 

477 filters: 

478 - type: value 

479 key: Expiration 

480 value_type: expiration 

481 op: greater-than 

482 value: 0 

483 actions: 

484 - type: delete 

485 

486 """ 

487 schema = type_schema('delete') 

488 permissions = ('iam:DeleteServerCertificate',) 

489 

490 def process(self, resources): 

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

492 for cert in resources: 

493 self.manager.retry( 

494 client.delete_server_certificate, 

495 ServerCertificateName=cert['ServerCertificateName'], 

496 ignore_err_codes=( 

497 'NoSuchEntityException', 

498 'DeleteConflictException', 

499 ), 

500 ) 

501 

502 

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

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

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

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

507class ServiceUsage(Filter): 

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

509 

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

511 is against the last 365 days of data. 

512 

513 Each service access record is evaluated against all specified 

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

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

516 

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

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

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

520 means all service access records have to match. 

521 

522 

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

524 

525 :example: 

526 

527 .. code-block:: yaml 

528 

529 - name: usage-unused-users 

530 resource: iam-user 

531 filters: 

532 - type: usage 

533 match-operator: all 

534 LastAuthenticated: null 

535 

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

537 

538 :example: 

539 

540 .. code-block:: yaml 

541 

542 - name: unused-users 

543 resource: iam-user 

544 filters: 

545 - type: usage 

546 ServiceNamespace: dynamodb 

547 TotalAuthenticatedEntities: 1 

548 LastAuthenticated: 

549 type: value 

550 value_type: age 

551 op: less-than 

552 value: 30 

553 match-operator: any 

554 

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

556 

557 """ 

558 

559 JOB_COMPLETE = 'COMPLETED' 

560 SERVICE_ATTR = { 

561 'ServiceName', 'ServiceNamespace', 'TotalAuthenticatedEntities', 

562 'LastAuthenticated', 'LastAuthenticatedEntity'} 

563 

564 schema_alias = True 

565 schema_attr = { 

566 sa: {'oneOf': [ 

567 {'type': 'string'}, 

568 {'type': 'boolean'}, 

569 {'type': 'number'}, 

570 {'type': 'null'}, 

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

572 for sa in sorted(SERVICE_ATTR)} 

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

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

575 schema = type_schema( 

576 'usage', 

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

578 **schema_attr) 

579 permissions = ('iam:GenerateServiceLastAccessedDetails', 

580 'iam:GetServiceLastAccessedDetails') 

581 

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

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

584 

585 job_resource_map = {} 

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

587 try: 

588 jid = self.manager.retry( 

589 client.generate_service_last_accessed_details, 

590 Arn=arn)['JobId'] 

591 job_resource_map[jid] = r 

592 except client.exceptions.NoSuchEntityException: 

593 continue 

594 

595 conf = dict(self.data) 

596 conf.pop('match-operator') 

597 saf = MultiAttrFilter(conf) 

598 saf.multi_attrs = self.SERVICE_ATTR 

599 

600 results = [] 

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

602 

603 while job_resource_map: 

604 job_results_map = {} 

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

606 result = self.manager.retry( 

607 client.get_service_last_accessed_details, JobId=jid) 

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

609 continue 

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

611 

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

613 r = job_resource_map.pop(jid) 

614 saf_matches = saf.process(saf_results) 

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

616 results.append(r) 

617 elif match_operator != 'all' and saf_matches: 

618 results.append(r) 

619 

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

621 

622 return results 

623 

624 

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

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

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

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

629class CheckPermissions(Filter): 

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

631 

632 :example: 

633 

634 Find users that can create other users 

635 

636 .. code-block:: yaml 

637 

638 policies: 

639 - name: super-users 

640 resource: aws.iam-user 

641 filters: 

642 - type: check-permissions 

643 match: allowed 

644 actions: 

645 - iam:CreateUser 

646 

647 :example: 

648 

649 Find users with access to all services and actions 

650 

651 .. code-block:: yaml 

652 

653 policies: 

654 - name: admin-users 

655 resource: aws.iam-user 

656 filters: 

657 - type: check-permissions 

658 match: allowed 

659 actions: 

660 - '*:*' 

661 

662 By default permission boundaries are checked. 

663 """ 

664 

665 schema = type_schema( 

666 'check-permissions', **{ 

667 'match': {'oneOf': [ 

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

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

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

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

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

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

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

675 schema_alias = True 

676 policy_annotation = 'c7n:policy' 

677 eval_annotation = 'c7n:perm-matches' 

678 

679 def validate(self): 

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

681 # 

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

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

684 # 

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

686 # runtime exceptions. 

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

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

689 raise PolicyValidationError( 

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

691 % (action,)) 

692 return self 

693 

694 def get_permissions(self): 

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

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

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

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

699 # for simulating w/ permission boundaries 

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

701 return perms 

702 

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

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

705 actions = self.data['actions'] 

706 matcher = self.get_eval_matcher() 

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

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

709 

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

711 self.simulation_boundary_override = ''' 

712 { 

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

714 "Statement": [{ 

715 "Effect": "Allow", 

716 "Action": "*", 

717 "Resource": "*" 

718 }] 

719 } 

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

721 results = [] 

722 eval_cache = {} 

723 for arn, r in arn_resources: 

724 if arn is None: 

725 continue 

726 if arn in eval_cache: 

727 evaluations = eval_cache[arn] 

728 else: 

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

730 eval_cache[arn] = evaluations 

731 if not evaluations: 

732 continue 

733 matches = [] 

734 matched = [] 

735 for e in evaluations: 

736 match = matcher(e) 

737 if match: 

738 matched.append(e) 

739 matches.append(match) 

740 if operator(matches): 

741 r[self.eval_annotation] = matched 

742 results.append(r) 

743 return results 

744 

745 def get_iam_arns(self, resources): 

746 return self.manager.get_arns(resources) 

747 

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

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

750 policy = r.get(self.policy_annotation) 

751 if policy is None: 

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

753 PolicyArn=r['Arn'], 

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

755 evaluations = self.manager.retry( 

756 client.simulate_custom_policy, 

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

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

759 return evaluations 

760 

761 params = dict( 

762 PolicySourceArn=arn, 

763 ActionNames=actions, 

764 ignore_err_codes=('NoSuchEntity',)) 

765 

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

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

768 # as the boundary. 

769 # 

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

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

772 # of using existing boundaries. 

773 if self.simulation_boundary_override: 

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

775 

776 evaluations = (self.manager.retry( 

777 client.simulate_principal_policy, 

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

779 return evaluations 

780 

781 def get_eval_matcher(self): 

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

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

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

785 else: 

786 values = ['allowed'] 

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

788 'EvalDecision', 'value': values, 

789 'op': 'in'}) 

790 else: 

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

792 vf.annotate = False 

793 return vf 

794 

795 

796class IamRoleUsage(Filter): 

797 

798 def get_permissions(self): 

799 perms = list(itertools.chain(*[ 

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

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

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

803 return perms 

804 

805 def service_role_usage(self): 

806 results = set() 

807 results.update(self.scan_lambda_roles()) 

808 results.update(self.scan_ecs_roles()) 

809 results.update(self.collect_profile_roles()) 

810 return results 

811 

812 def instance_profile_usage(self): 

813 results = set() 

814 results.update(self.scan_asg_roles()) 

815 results.update(self.scan_ec2_roles()) 

816 return results 

817 

818 def scan_lambda_roles(self): 

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

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

821 

822 def scan_ecs_roles(self): 

823 results = [] 

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

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

826 services = client.list_services( 

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

828 if services: 

829 for service in client.describe_services( 

830 cluster=cluster['clusterName'], 

831 services=services)['services']: 

832 if 'roleArn' in service: 

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

834 return results 

835 

836 def collect_profile_roles(self): 

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

838 profiles = set() 

839 profiles.update(self.scan_asg_roles()) 

840 profiles.update(self.scan_ec2_roles()) 

841 

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

843 iprofiles = manager.resources() 

844 results = [] 

845 for p in iprofiles: 

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

847 continue 

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

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

850 return results 

851 

852 def scan_asg_roles(self): 

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

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

855 'IamInstanceProfile' in r)] 

856 

857 def scan_ec2_roles(self): 

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

859 results = [] 

860 for e in manager.resources(): 

861 # do not include instances that have been recently terminated 

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

863 continue 

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

865 if not profile_arn: 

866 continue 

867 # split arn to get the profile name 

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

869 return results 

870 

871 

872################### 

873# IAM Roles # 

874################### 

875 

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

877class UsedIamRole(IamRoleUsage): 

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

879 

880 Checks for usage on EC2, Lambda, ECS only 

881 

882 :example: 

883 

884 .. code-block:: yaml 

885 

886 policies: 

887 - name: iam-role-in-use 

888 resource: iam-role 

889 filters: 

890 - type: used 

891 state: true 

892 """ 

893 

894 schema = type_schema( 

895 'used', 

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

897 

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

899 roles = self.service_role_usage() 

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

901 return [r for r in resources if ( 

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

903 

904 return [r for r in resources if ( 

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

906 

907 

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

909class UnusedIamRole(IamRoleUsage): 

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

911 

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

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

914 

915 Checks for usage on EC2, Lambda, ECS only 

916 

917 :example: 

918 

919 .. code-block:: yaml 

920 

921 policies: 

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

923 resource: iam-role 

924 filters: 

925 - type: used 

926 state: false 

927 """ 

928 deprecations = ( 

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

930 ) 

931 

932 schema = type_schema('unused') 

933 

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

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

936 

937 

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

939class RoleCrossAccountAccess(CrossAccountAccessFilter): 

940 

941 policy_attribute = 'AssumeRolePolicyDocument' 

942 permissions = ('iam:ListRoles',) 

943 

944 schema = type_schema( 

945 'cross-account', 

946 # white list accounts 

947 whitelist_from=ValuesFrom.schema, 

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

949 

950 

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

952class IamRoleInlinePolicy(Filter): 

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

954 True: Filter roles that have an inline-policy 

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

956 

957 :example: 

958 

959 .. code-block:: yaml 

960 

961 policies: 

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

963 resource: iam-role 

964 filters: 

965 - type: has-inline-policy 

966 value: True 

967 """ 

968 

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

970 permissions = ('iam:ListRolePolicies',) 

971 

972 def _inline_policies(self, client, resource): 

973 policies = client.list_role_policies( 

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

975 resource['c7n:InlinePolicies'] = policies 

976 return resource 

977 

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

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

980 res = [] 

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

982 for r in resources: 

983 r = self._inline_policies(c, r) 

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

985 res.append(r) 

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

987 res.append(r) 

988 return res 

989 

990 

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

992class SpecificIamRoleManagedPolicy(ValueFilter): 

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

994 

995 :example: 

996 

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

998 

999 .. code-block:: yaml 

1000 

1001 policies: 

1002 - name: iam-roles-have-admin 

1003 resource: aws.iam-role 

1004 filters: 

1005 - type: has-specific-managed-policy 

1006 value: admin-policy 

1007 

1008 :example: 

1009 

1010 Check for roles with an attached policy matching 

1011 a given list: 

1012 

1013 .. code-block:: yaml 

1014 

1015 policies: 

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

1017 resource: aws.iam-role 

1018 filters: 

1019 - type: has-specific-managed-policy 

1020 op: in 

1021 value: 

1022 - AmazonS3FullAccess 

1023 - AWSOrganizationsFullAccess 

1024 

1025 :example: 

1026 

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

1028 

1029 .. code-block:: yaml 

1030 

1031 policies: 

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

1033 resource: aws.iam-role 

1034 filters: 

1035 - type: has-specific-managed-policy 

1036 op: glob 

1037 value: "*FullAccess" 

1038 

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

1040 

1041 .. code-block:: yaml 

1042 

1043 policies: 

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

1045 resource: aws.iam-role 

1046 filters: 

1047 - type: has-specific-managed-policy 

1048 key: PolicyArn 

1049 op: regex 

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

1051 """ 

1052 

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

1054 permissions = ('iam:ListAttachedRolePolicies',) 

1055 annotation_key = 'c7n:AttachedPolicies' 

1056 matched_annotation_key = 'c7n:MatchedPolicies' 

1057 schema_alias = False 

1058 

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

1060 # Preserve backward compatibility 

1061 if 'key' not in data: 

1062 data['key'] = 'PolicyName' 

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

1064 

1065 def get_managed_policies(self, client, role_set): 

1066 for role in role_set: 

1067 role[self.annotation_key] = [ 

1068 role_policy 

1069 for role_policy 

1070 in client.list_attached_role_policies( 

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

1072 ] 

1073 

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

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

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

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

1078 self.log.debug( 

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

1080 list(w.map( 

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

1082 chunks(augment_set, 50))) 

1083 

1084 matched = [] 

1085 for r in resources: 

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

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

1088 if matched_keys: 

1089 matched.append(r) 

1090 return matched 

1091 

1092 

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

1094class NoSpecificIamRoleManagedPolicy(Filter): 

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

1096 

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

1098 

1099 :example: 

1100 

1101 .. code-block:: yaml 

1102 

1103 policies: 

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

1105 resource: iam-role 

1106 filters: 

1107 - type: no-specific-managed-policy 

1108 value: ip-restriction 

1109 """ 

1110 

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

1112 permissions = ('iam:ListAttachedRolePolicies',) 

1113 

1114 def _managed_policies(self, client, resource): 

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

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

1117 

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

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

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

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

1122 self._managed_policies(c, r)] 

1123 return [] 

1124 

1125 

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

1127class SetPolicy(BaseAction): 

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

1129 

1130 You will identify the policy by its arn. 

1131 

1132 Returns a list of roles modified by the action. 

1133 

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

1135 

1136 :example: 

1137 

1138 .. code-block:: yaml 

1139 

1140 - name: iam-attach-role-policy 

1141 resource: iam-role 

1142 filters: 

1143 - type: no-specific-managed-policy 

1144 value: my-iam-policy 

1145 actions: 

1146 - type: set-policy 

1147 state: detached 

1148 arn: "*" 

1149 - type: set-policy 

1150 state: attached 

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

1152 

1153 """ 

1154 schema = type_schema( 

1155 'set-policy', 

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

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

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

1159 

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

1161 

1162 def validate(self): 

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

1164 raise PolicyValidationError( 

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

1166 

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

1168 client.attach_role_policy( 

1169 RoleName=resource['RoleName'], 

1170 PolicyArn=policy_arn 

1171 ) 

1172 

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

1174 try: 

1175 client.detach_role_policy( 

1176 RoleName=resource['RoleName'], 

1177 PolicyArn=policy_arn 

1178 ) 

1179 except client.exceptions.NoSuchEntityException: 

1180 return 

1181 

1182 def list_attached_policies(self, client, resource): 

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

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

1185 return policy_arns 

1186 

1187 def process(self, resources): 

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

1189 policy_arn = self.data['arn'] 

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

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

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

1193 self.manager.account_id, policy_arn) 

1194 state = self.data['state'] 

1195 for r in resources: 

1196 if state == 'attached': 

1197 self.attach_policy(client, r, policy_arn) 

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

1199 self.detach_policy(client, r, policy_arn) 

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

1201 try: 

1202 self.detach_all_policies(client, r) 

1203 except client.exceptions.NoSuchEntityException: 

1204 continue 

1205 

1206 def detach_all_policies(self, client, resource): 

1207 policy_arns = self.list_attached_policies(client, resource) 

1208 for parn in policy_arns: 

1209 self.detach_policy(client, resource, parn) 

1210 

1211 

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

1213class SetUserPolicy(SetPolicy): 

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

1215 

1216 You will identify the policy by its arn. 

1217 

1218 Returns a list of roles modified by the action. 

1219 

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

1221 detaching all exisitng policies: 

1222 

1223 :example: 

1224 

1225 .. code-block:: yaml 

1226 

1227 - name: iam-attach-user-policy 

1228 resource: iam-user 

1229 filters: 

1230 - type: value 

1231 key: UserName 

1232 op: not-in 

1233 value: 

1234 - AdminUser1 

1235 - AdminUser2 

1236 actions: 

1237 - type: set-policy 

1238 state: detached 

1239 arn: arn:aws:iam::aws:policy/AdministratorAccess 

1240 

1241 """ 

1242 

1243 permissions = ( 

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

1245 

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

1247 client.attach_user_policy( 

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

1249 

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

1251 try: 

1252 client.detach_user_policy( 

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

1254 except client.exceptions.NoSuchEntityException: 

1255 return 

1256 

1257 def list_attached_policies(self, client, resource): 

1258 attached_policies = client.list_attached_user_policies( 

1259 UserName=resource["UserName"] 

1260 ) 

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

1262 return policy_arns 

1263 

1264 

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

1266class SetGroupPolicy(SetPolicy): 

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

1268 

1269 You will identify the policy by its arn. 

1270 

1271 Returns a list of roles modified by the action. 

1272 

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

1274 detaching all exisitng policies: 

1275 

1276 :example: 

1277 

1278 .. code-block:: yaml 

1279 

1280 - name: iam-attach-group-policy 

1281 resource: iam-group 

1282 actions: 

1283 - type: set-policy 

1284 state: detached 

1285 arn: "*" 

1286 - type: set-policy 

1287 state: attached 

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

1289 

1290 """ 

1291 

1292 permissions = ( 

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

1294 

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

1296 client.attach_group_policy( 

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

1298 

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

1300 try: 

1301 client.detach_group_policy( 

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

1303 except client.exceptions.NoSuchEntityException: 

1304 return 

1305 

1306 def list_attached_policies(self, client, resource): 

1307 attached_policies = client.list_attached_group_policies( 

1308 GroupName=resource["GroupName"] 

1309 ) 

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

1311 return policy_arns 

1312 

1313 

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

1315class RoleDelete(BaseAction): 

1316 """Delete an IAM Role. 

1317 

1318 To delete IAM Role you must first delete the policies 

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

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

1321 

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

1323 

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

1325 policies that are associated with the role would be detached 

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

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

1328 

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

1330 

1331 :example: 

1332 

1333 .. code-block:: yaml 

1334 

1335 - name: iam-delete-unused-role 

1336 resource: iam-role 

1337 filters: 

1338 - type: usage 

1339 match-operator: all 

1340 LastAuthenticated: null 

1341 actions: 

1342 - type: delete 

1343 force: true 

1344 

1345 """ 

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

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

1348 

1349 def detach_inline_policies(self, client, r): 

1350 policies = (self.manager.retry( 

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

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

1353 for p in policies: 

1354 self.manager.retry( 

1355 client.delete_role_policy, 

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

1357 ignore_err_codes=('NoSuchEntityException',)) 

1358 

1359 def delete_instance_profiles(self, client, r): 

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

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

1362 profile_names = [] 

1363 profiles = self.manager.retry( 

1364 client.list_instance_profiles_for_role, 

1365 RoleName=r['RoleName'], 

1366 ignore_err_codes=('NoSuchEntityException',)) 

1367 if profiles: 

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

1369 for p in profile_names: 

1370 self.manager.retry( 

1371 client.remove_role_from_instance_profile, 

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

1373 ignore_err_codes=('NoSuchEntityException',)) 

1374 self.manager.retry( 

1375 client.delete_instance_profile, 

1376 InstanceProfileName=p, 

1377 ignore_err_codes=('NoSuchEntityException',)) 

1378 

1379 def process(self, resources): 

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

1381 error = None 

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

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

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

1385 policy_setter.process(resources) 

1386 

1387 for r in resources: 

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

1389 self.detach_inline_policies(client, r) 

1390 self.delete_instance_profiles(client, r) 

1391 try: 

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

1393 except client.exceptions.DeleteConflictException as e: 

1394 self.log.warning( 

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

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

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

1398 error = e 

1399 except (client.exceptions.NoSuchEntityException, 

1400 client.exceptions.UnmodifiableEntityException): 

1401 continue 

1402 if error: 

1403 raise error 

1404 

1405 

1406###################### 

1407# IAM Policies # 

1408###################### 

1409 

1410 

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

1412class UsedIamPolicies(Filter): 

1413 """Filter IAM policies that are being used 

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

1415 

1416 :example: 

1417 

1418 .. code-block:: yaml 

1419 

1420 policies: 

1421 - name: iam-policy-used 

1422 resource: iam-policy 

1423 filters: 

1424 - type: used 

1425 """ 

1426 

1427 schema = type_schema('used') 

1428 permissions = ('iam:ListPolicies',) 

1429 

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

1431 return [r for r in resources if 

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

1433 

1434 

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

1436class UnusedIamPolicies(Filter): 

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

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

1439 

1440 :example: 

1441 

1442 .. code-block:: yaml 

1443 

1444 policies: 

1445 - name: iam-policy-unused 

1446 resource: iam-policy 

1447 filters: 

1448 - type: unused 

1449 """ 

1450 

1451 schema = type_schema('unused') 

1452 permissions = ('iam:ListPolicies',) 

1453 

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

1455 return [r for r in resources if 

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

1457 

1458 

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

1460class AllowAllIamPolicies(Filter): 

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

1462 

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

1464 policy must exist with the following requirements. 

1465 

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

1467 

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

1469 For example: 

1470 

1471 .. code-block:: json 

1472 

1473 { 

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

1475 "Statement": [{ 

1476 "Action": "*", 

1477 "Resource": "*", 

1478 "Effect": "Allow" 

1479 }] 

1480 } 

1481 

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

1483 'NotAction'. 

1484 

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

1486 allow all: 

1487 

1488 .. code-block:: yaml 

1489 

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

1491 resource: iam-policy 

1492 filters: 

1493 - type: used 

1494 - type: has-allow-all 

1495 

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

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

1498 above. 

1499 

1500 """ 

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

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

1503 

1504 def has_allow_all_policy(self, client, resource): 

1505 statements = client.get_policy_version( 

1506 PolicyArn=resource['Arn'], 

1507 VersionId=resource['DefaultVersionId'] 

1508 )['PolicyVersion']['Document']['Statement'] 

1509 if isinstance(statements, dict): 

1510 statements = [statements] 

1511 

1512 for s in statements: 

1513 if ('Condition' not in s and 

1514 'Action' in s and 

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

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

1517 'Resource' in s and 

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

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

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

1521 return True 

1522 return False 

1523 

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

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

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

1527 self.log.info( 

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

1529 len(results), len(resources)) 

1530 return results 

1531 

1532 

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

1534class PolicyDelete(BaseAction): 

1535 """Delete an IAM Policy. 

1536 

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

1538 

1539 :example: 

1540 

1541 .. code-block:: yaml 

1542 

1543 - name: iam-delete-unused-policies 

1544 resource: iam-policy 

1545 filters: 

1546 - type: unused 

1547 actions: 

1548 - delete 

1549 

1550 """ 

1551 schema = type_schema('delete') 

1552 permissions = ('iam:DeletePolicy',) 

1553 

1554 def process(self, resources): 

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

1556 

1557 rcount = len(resources) 

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

1559 if len(resources) != rcount: 

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

1561 rcount, len(resources)) 

1562 

1563 for r in resources: 

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

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

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

1567 for v in versions: 

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

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

1570 

1571 

1572############################### 

1573# IAM Instance Profiles # 

1574############################### 

1575 

1576 

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

1578class UsedInstanceProfiles(IamRoleUsage): 

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

1580 

1581 :example: 

1582 

1583 .. code-block:: yaml 

1584 

1585 policies: 

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

1587 resource: iam-profile 

1588 filters: 

1589 - type: used 

1590 """ 

1591 

1592 schema = type_schema('used') 

1593 

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

1595 results = [] 

1596 profiles = self.instance_profile_usage() 

1597 for r in resources: 

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

1599 results.append(r) 

1600 self.log.info( 

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

1602 len(results), len(resources))) 

1603 return results 

1604 

1605 

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

1607class UnusedInstanceProfiles(IamRoleUsage): 

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

1609 

1610 :example: 

1611 

1612 .. code-block:: yaml 

1613 

1614 policies: 

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

1616 resource: iam-profile 

1617 filters: 

1618 - type: unused 

1619 """ 

1620 

1621 schema = type_schema('unused') 

1622 

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

1624 results = [] 

1625 profiles = self.instance_profile_usage() 

1626 for r in resources: 

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

1628 results.append(r) 

1629 self.log.info( 

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

1631 len(results), len(resources))) 

1632 return results 

1633 

1634 

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

1636class InstanceProfileSetRole(BaseAction): 

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

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

1639 

1640 :example: 

1641 

1642 .. code-block:: yaml 

1643 

1644 policies: 

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

1646 resource: iam-profile 

1647 actions: 

1648 - type: set-role 

1649 role: my-test-role 

1650 """ 

1651 

1652 schema = type_schema('set-role', 

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

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

1655 

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

1657 self.manager.retry( 

1658 client.add_role_to_instance_profile, 

1659 InstanceProfileName=resource['InstanceProfileName'], 

1660 RoleName=role 

1661 ) 

1662 return 

1663 

1664 def remove_role(self, client, resource): 

1665 self.manager.retry( 

1666 client.remove_role_from_instance_profile, 

1667 InstanceProfileName=resource['InstanceProfileName'], 

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

1669 ) 

1670 return 

1671 

1672 def process(self, resources): 

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

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

1675 for r in resources: 

1676 if not role: 

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

1678 continue 

1679 else: 

1680 self.remove_role(client, r) 

1681 else: 

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

1683 self.add_role(client, r, role) 

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

1685 continue 

1686 else: 

1687 self.remove_role(client, r) 

1688 self.add_role(client, r, role) 

1689 

1690 

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

1692class SetInstanceProfileRolePolicy(SetPolicy): 

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

1694 

1695 You will identify the policy by its arn. 

1696 

1697 Returns a list of roles modified by the action. 

1698 

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

1700 detaching all exisitng policies: 

1701 

1702 :example: 

1703 

1704 .. code-block:: yaml 

1705 

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

1707 resource: iam-profile 

1708 filters: 

1709 - not: 

1710 - type: has-specific-managed-policy 

1711 value: my-iam-policy 

1712 actions: 

1713 - type: set-policy 

1714 state: attached 

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

1716 

1717 """ 

1718 

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

1720 

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

1722 client.attach_role_policy( 

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

1724 PolicyArn=policy_arn 

1725 ) 

1726 

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

1728 try: 

1729 client.detach_role_policy( 

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

1731 PolicyArn=policy_arn 

1732 ) 

1733 except client.exceptions.NoSuchEntityException: 

1734 return 

1735 

1736 def list_attached_policies(self, client, resource): 

1737 attached_policies = client.list_attached_role_policies( 

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

1739 ) 

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

1741 return policy_arns 

1742 

1743 

1744################### 

1745# IAM Users # 

1746################### 

1747 

1748class CredentialReport(Filter): 

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

1750 

1751 The IAM Credential report aggregates multiple pieces of 

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

1753 querying multiple aspects of a user that would otherwise require 

1754 per user api calls. 

1755 

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

1757 

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

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

1760 last month 

1761 

1762 .. code-block:: yaml 

1763 

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

1765 resource: iam-user 

1766 filters: 

1767 - type: credential 

1768 key: mfa_active 

1769 value: true 

1770 - type: credential 

1771 key: password_last_used 

1772 value: absent 

1773 - type: credential 

1774 key: access_keys.last_used_date 

1775 value_type: age 

1776 value: 30 

1777 op: less-than 

1778 

1779 Credential Report Transforms 

1780 

1781 We perform some default transformations from the raw 

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

1783 are turned into array of dictionaries for matching 

1784 purposes with their common prefixes stripped. 

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

1786 into boolean values. 

1787 

1788 """ 

1789 schema = type_schema( 

1790 'credential', 

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

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

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

1794 'enum': [ 

1795 'user', 

1796 'arn', 

1797 'user_creation_time', 

1798 'password_enabled', 

1799 'password_last_used', 

1800 'password_last_changed', 

1801 'password_next_rotation', 

1802 'mfa_active', 

1803 'access_keys', 

1804 'access_keys.active', 

1805 'access_keys.last_used_date', 

1806 'access_keys.last_used_region', 

1807 'access_keys.last_used_service', 

1808 'access_keys.last_rotated', 

1809 'certs', 

1810 'certs.active', 

1811 'certs.last_rotated', 

1812 ]}, 

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

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

1815 report_generate={ 

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

1817 'default': True, 

1818 'type': 'boolean'}, 

1819 report_delay={ 

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

1821 'default': 10, 

1822 'type': 'number'}, 

1823 report_max_age={ 

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

1825 'default': 60 * 60 * 24, 

1826 'type': 'number'}) 

1827 

1828 list_sub_objects = ( 

1829 ('access_key_1_', 'access_keys'), 

1830 ('access_key_2_', 'access_keys'), 

1831 ('cert_1_', 'certs'), 

1832 ('cert_2_', 'certs')) 

1833 

1834 # for access keys only 

1835 matched_annotation_key = 'c7n:matched-keys' 

1836 

1837 permissions = ('iam:GenerateCredentialReport', 

1838 'iam:GetCredentialReport') 

1839 

1840 def get_value_or_schema_default(self, k): 

1841 if k in self.data: 

1842 return self.data[k] 

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

1844 

1845 def get_credential_report(self): 

1846 cache = self.manager._cache 

1847 with cache: 

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

1849 report = cache.get(cache_key) 

1850 

1851 if report: 

1852 return report 

1853 data = self.fetch_credential_report() 

1854 report = {} 

1855 if isinstance(data, bytes): 

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

1857 else: 

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

1859 headers = next(reader) 

1860 for line in reader: 

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

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

1863 cache.save(cache_key, report) 

1864 

1865 return report 

1866 

1867 @classmethod 

1868 def process_user_record(cls, info): 

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

1870 keys = list(info.keys()) 

1871 # Value conversion 

1872 for k in keys: 

1873 v = info[k] 

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

1875 info[k] = None 

1876 elif v == 'false': 

1877 info[k] = False 

1878 elif v == 'true': 

1879 info[k] = True 

1880 # Object conversion 

1881 for p, t in cls.list_sub_objects: 

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

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

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

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

1886 return info 

1887 

1888 def fetch_credential_report(self): 

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

1890 try: 

1891 report = client.get_credential_report() 

1892 except ClientError as e: 

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

1894 report = None 

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

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

1897 # for it again. 

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

1899 report = client.get_credential_report() 

1900 else: 

1901 raise 

1902 if report: 

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

1904 seconds=self.get_value_or_schema_default( 

1905 'report_max_age')) 

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

1907 threshold = threshold.replace(tzinfo=None) 

1908 if report['GeneratedTime'] < threshold: 

1909 report = None 

1910 if report is None: 

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

1912 raise ValueError("Credential Report Not Present") 

1913 client.generate_credential_report() 

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

1915 report = client.get_credential_report() 

1916 return report['Content'] 

1917 

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

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

1920 self.matcher_config = dict(self.data) 

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

1922 return [] 

1923 

1924 def match(self, resource, info): 

1925 if info is None: 

1926 return False 

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

1928 if '.' not in k: 

1929 vf = ValueFilter(self.data) 

1930 vf.annotate = False 

1931 return vf(info) 

1932 

1933 # access key matching 

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

1935 vf = ValueFilter(self.matcher_config) 

1936 vf.annotate = False 

1937 

1938 # annotation merging with previous respecting block operators 

1939 k_matched = [] 

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

1941 if vf.match(v): 

1942 k_matched.append(v) 

1943 

1944 for k in k_matched: 

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

1946 

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

1948 return bool(k_matched) 

1949 

1950 

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

1952class UserCredentialReport(CredentialReport): 

1953 

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

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

1956 report = self.get_credential_report() 

1957 if report is None: 

1958 return [] 

1959 results = [] 

1960 for r in resources: 

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

1962 if self.match(r, info): 

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

1964 results.append(r) 

1965 return results 

1966 

1967 

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

1969class IamUserInlinePolicy(Filter): 

1970 """ 

1971 Filter IAM users that have an inline-policy attached 

1972 

1973 True: Filter users that have an inline-policy 

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

1975 """ 

1976 

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

1978 permissions = ('iam:ListUserPolicies',) 

1979 

1980 def _inline_policies(self, client, resource): 

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

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

1983 return resource 

1984 

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

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

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

1988 res = [] 

1989 for r in resources: 

1990 r = self._inline_policies(c, r) 

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

1992 res.append(r) 

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

1994 res.append(r) 

1995 return res 

1996 

1997 

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

1999class UserPolicy(ValueFilter): 

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

2001 

2002 :example: 

2003 

2004 .. code-block:: yaml 

2005 

2006 policies: 

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

2008 resource: iam-user 

2009 filters: 

2010 - type: policy 

2011 key: PolicyName 

2012 value: AdministratorAccess 

2013 include-via: true 

2014 """ 

2015 

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

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

2018 schema_alias = False 

2019 permissions = ( 

2020 'iam:ListAttachedUserPolicies', 

2021 'iam:ListGroupsForUser', 

2022 'iam:ListAttachedGroupPolicies', 

2023 ) 

2024 

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

2026 for u in user_set: 

2027 if search_key in u: 

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

2029 if searched is not None: 

2030 return searched 

2031 

2032 return None 

2033 

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

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

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

2037 

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

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

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

2041 ug['AttachedPolicies'] = ug_searched['AttachedPolicies'] 

2042 else: 

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

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

2045 

2046 for ap in ug['AttachedPolicies']: 

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

2048 if not p_searched: 

2049 p_searched = self.find_in_user_set( 

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

2051 ) 

2052 if p_searched: 

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

2054 else: 

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

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

2057 

2058 return u 

2059 

2060 def user_policies(self, user_set): 

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

2062 for u in user_set: 

2063 if 'c7n:Policies' not in u: 

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

2065 aps = client.list_attached_user_policies( 

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

2067 for ap in aps: 

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

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

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

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

2072 

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

2074 user_set = chunks(resources, size=50) 

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

2076 self.log.debug( 

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

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

2079 

2080 matched = [] 

2081 for r in resources: 

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

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

2084 matched.append(r) 

2085 return matched 

2086 

2087 

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

2089class GroupMembership(ValueFilter): 

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

2091 

2092 :example: 

2093 

2094 .. code-block:: yaml 

2095 

2096 policies: 

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

2098 resource: iam-user 

2099 filters: 

2100 - type: group 

2101 key: GroupName 

2102 value: Admins 

2103 """ 

2104 

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

2106 schema_alias = False 

2107 permissions = ('iam:ListGroupsForUser',) 

2108 

2109 def get_user_groups(self, client, user_set): 

2110 for u in user_set: 

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

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

2113 

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

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

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

2117 futures = [] 

2118 for user_set in chunks( 

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

2120 futures.append( 

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

2122 for f in as_completed(futures): 

2123 pass 

2124 

2125 matched = [] 

2126 for r in resources: 

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

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

2129 matched.append(r) 

2130 return matched 

2131 

2132 

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

2134class UserAccessKey(ValueFilter): 

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

2136 

2137 By default multiple uses of this filter will match 

2138 on any user key satisfying either filter. To find 

2139 specific keys that match multiple access-key filters, 

2140 use `match-operator: and` 

2141 

2142 :example: 

2143 

2144 .. code-block:: yaml 

2145 

2146 policies: 

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

2148 resource: iam-user 

2149 filters: 

2150 - type: access-key 

2151 key: Status 

2152 value: Active 

2153 - type: access-key 

2154 match-operator: and 

2155 key: CreateDate 

2156 value_type: age 

2157 value: 90 

2158 """ 

2159 

2160 schema = type_schema( 

2161 'access-key', 

2162 rinherit=ValueFilter.schema, 

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

2164 schema_alias = False 

2165 permissions = ('iam:ListAccessKeys',) 

2166 annotation_key = 'c7n:AccessKeys' 

2167 matched_annotation_key = 'c7n:matched-keys' 

2168 annotate = False 

2169 

2170 def get_user_keys(self, client, user_set): 

2171 for u in user_set: 

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

2173 client.list_access_keys, 

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

2175 

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

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

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

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

2180 self.log.debug( 

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

2182 list(w.map( 

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

2184 chunks(augment_set, 50))) 

2185 

2186 matched = [] 

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

2188 for r in resources: 

2189 keys = r[self.annotation_key] 

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

2191 keys = r[self.matched_annotation_key] 

2192 k_matched = [] 

2193 for k in keys: 

2194 if self.match(k): 

2195 k_matched.append(k) 

2196 for k in k_matched: 

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

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

2199 if k_matched: 

2200 matched.append(r) 

2201 return matched 

2202 

2203 

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

2205class UserSSHKeyFilter(ValueFilter): 

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

2207 

2208 :example: 

2209 

2210 .. code-block:: yaml 

2211 

2212 policies: 

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

2214 resource: iam-user 

2215 filters: 

2216 - type: ssh-key 

2217 key: Status 

2218 value: Active 

2219 - type: ssh-key 

2220 key: UploadDate 

2221 value_type: age 

2222 value: 90 

2223 """ 

2224 

2225 schema = type_schema( 

2226 'ssh-key', 

2227 rinherit=ValueFilter.schema) 

2228 schema_alias = False 

2229 permissions = ('iam:ListSSHPublicKeys',) 

2230 annotation_key = 'c7n:SSHKeys' 

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

2232 annotate = False 

2233 

2234 def get_user_ssh_keys(self, client, user_set): 

2235 for u in user_set: 

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

2237 client.list_ssh_public_keys, 

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

2239 

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

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

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

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

2244 self.log.debug( 

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

2246 list(w.map( 

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

2248 chunks(augment_set, 50))) 

2249 

2250 matched = [] 

2251 for r in resources: 

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

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

2254 if matched_keys: 

2255 matched.append(r) 

2256 return matched 

2257 

2258 

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

2260class UserLoginProfile(ValueFilter): 

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

2262 

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

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

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

2266 

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

2268 

2269 :example: 

2270 

2271 .. code-block: yaml 

2272 

2273 policies: 

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

2275 resource: iam-user 

2276 filters: 

2277 - type: login-profile 

2278 """ 

2279 

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

2281 permissions = ('iam:GetLoginProfile',) 

2282 annotation_key = 'c7n:LoginProfile' 

2283 

2284 def user_login_profiles(self, user_set): 

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

2286 for u in user_set: 

2287 u[self.annotation_key] = False 

2288 try: 

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

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

2291 u[self.annotation_key] = True 

2292 except ClientError as e: 

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

2294 raise 

2295 

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

2297 user_set = chunks(resources, size=50) 

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

2299 self.log.debug( 

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

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

2302 

2303 matched = [] 

2304 for r in resources: 

2305 if r[self.annotation_key]: 

2306 matched.append(r) 

2307 return matched 

2308 

2309 

2310# Mfa-device filter for iam-users 

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

2312class UserMfaDevice(ValueFilter): 

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

2314 

2315 :example: 

2316 

2317 .. code-block:: yaml 

2318 

2319 policies: 

2320 - name: mfa-enabled-users 

2321 resource: iam-user 

2322 filters: 

2323 - type: mfa-device 

2324 key: UserName 

2325 value: not-null 

2326 """ 

2327 

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

2329 schema_alias = False 

2330 permissions = ('iam:ListMFADevices',) 

2331 

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

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

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

2335 

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

2337 

2338 def _user_mfa_devices(resource): 

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

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

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

2342 

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

2344 query_resources = [ 

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

2346 self.log.debug( 

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

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

2349 

2350 matched = [] 

2351 for r in resources: 

2352 if self.match(r): 

2353 matched.append(r) 

2354 

2355 return matched 

2356 

2357 

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

2359class UserFinding(OtherResourcePostFinding): 

2360 

2361 def format_resource(self, r): 

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

2363 details = { 

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

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

2366 ), 

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

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

2369 } 

2370 accesskey = { 

2371 "Type": "AwsIamAccessKey", 

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

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

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

2375 } 

2376 return filter_empty(accesskey) 

2377 else: 

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

2379 

2380 

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

2382class UserDelete(BaseAction): 

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

2384 

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

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

2387 

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

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

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

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

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

2393 

2394 :example: 

2395 

2396 .. code-block:: yaml 

2397 

2398 # using a 'credential' filter' 

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

2400 resource: iam-user 

2401 filters: 

2402 - type: credential 

2403 key: user 

2404 op: not-in 

2405 value: 

2406 - valid-user-1 

2407 - valid-user-2 

2408 actions: 

2409 - delete 

2410 

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

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

2413 resource: iam-user 

2414 filters: 

2415 - type: value 

2416 key: UserName 

2417 op: not-in 

2418 value: 

2419 - valid-user-1 

2420 - valid-user-2 

2421 actions: 

2422 - delete 

2423 

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

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

2426 resource: iam-user 

2427 filters: 

2428 - type: value 

2429 key: Arn 

2430 op: not-in 

2431 value: 

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

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

2434 actions: 

2435 - delete 

2436 

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

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

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

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

2441 

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

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

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

2445 

2446 :example: 

2447 

2448 .. code-block:: yaml 

2449 

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

2451 comment: | 

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

2453 the username is included in whitelist 

2454 resource: iam-user 

2455 filters: 

2456 - type: value 

2457 key: UserName 

2458 op: not-in 

2459 value: 

2460 - valid-user-1 

2461 - valid-user-2 

2462 - type: credential 

2463 key: password_enabled 

2464 value: true 

2465 actions: 

2466 - type: delete 

2467 options: 

2468 - console-access 

2469 

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

2471 comment: | 

2472 deletes multiple options from test_user 

2473 resource: iam-user 

2474 filters: 

2475 - UserName: test_user 

2476 actions: 

2477 - type: delete 

2478 options: 

2479 - mfa-devices 

2480 - access-keys 

2481 - ssh-keys 

2482 """ 

2483 

2484 ORDERED_OPTIONS = OrderedDict([ 

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

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

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

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

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

2490 ('groups', 'delete_groups'), 

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

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

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

2494 ]) 

2495 COMPOUND_OPTIONS = { 

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

2497 } 

2498 

2499 schema = type_schema( 

2500 'delete', 

2501 options={ 

2502 'type': 'array', 

2503 'items': { 

2504 'type': 'string', 

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

2506 } 

2507 }) 

2508 

2509 permissions = ( 

2510 'iam:ListAttachedUserPolicies', 

2511 'iam:ListAccessKeys', 

2512 'iam:ListGroupsForUser', 

2513 'iam:ListMFADevices', 

2514 'iam:ListServiceSpecificCredentials', 

2515 'iam:ListSigningCertificates', 

2516 'iam:ListSSHPublicKeys', 

2517 'iam:DeactivateMFADevice', 

2518 'iam:DeleteAccessKey', 

2519 'iam:DeleteLoginProfile', 

2520 'iam:DeleteSigningCertificate', 

2521 'iam:DeleteSSHPublicKey', 

2522 'iam:DeleteUser', 

2523 'iam:DeleteUserPolicy', 

2524 'iam:DetachUserPolicy', 

2525 'iam:RemoveUserFromGroup') 

2526 

2527 @staticmethod 

2528 def delete_console_access(client, r): 

2529 try: 

2530 client.delete_login_profile( 

2531 UserName=r['UserName']) 

2532 except ClientError as e: 

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

2534 raise 

2535 

2536 @staticmethod 

2537 def delete_access_keys(client, r): 

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

2539 for access_key in response['AccessKeyMetadata']: 

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

2541 AccessKeyId=access_key['AccessKeyId']) 

2542 

2543 @staticmethod 

2544 def delete_attached_user_policies(client, r): 

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

2546 for user_policy in response['AttachedPolicies']: 

2547 client.detach_user_policy( 

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

2549 

2550 @staticmethod 

2551 def delete_inline_user_policies(client, r): 

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

2553 for user_policy_name in response['PolicyNames']: 

2554 client.delete_user_policy( 

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

2556 

2557 @staticmethod 

2558 def delete_hw_mfa_devices(client, r): 

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

2560 for mfa_device in response['MFADevices']: 

2561 client.deactivate_mfa_device( 

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

2563 

2564 @staticmethod 

2565 def delete_groups(client, r): 

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

2567 for user_group in response['Groups']: 

2568 client.remove_user_from_group( 

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

2570 

2571 @staticmethod 

2572 def delete_ssh_keys(client, r): 

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

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

2575 client.delete_ssh_public_key( 

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

2577 

2578 @staticmethod 

2579 def delete_signing_certificates(client, r): 

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

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

2582 client.delete_signing_certificate( 

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

2584 

2585 @staticmethod 

2586 def delete_service_specific_credentials(client, r): 

2587 # Service specific user credentials (codecommit) 

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

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

2590 client.delete_service_specific_credential( 

2591 UserName=r['UserName'], 

2592 ServiceSpecificCredentialId=screds['ServiceSpecificCredentialId']) 

2593 

2594 @staticmethod 

2595 def delete_user(client, r): 

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

2597 

2598 def process(self, resources): 

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

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

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

2602 for r in resources: 

2603 self.process_user(client, r) 

2604 

2605 def process_user(self, client, r): 

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

2607 # resolve compound options 

2608 for cmd in self.COMPOUND_OPTIONS: 

2609 if cmd in user_options: 

2610 user_options += self.COMPOUND_OPTIONS[cmd] 

2611 # process options in ordered fashion 

2612 for cmd in self.ORDERED_OPTIONS: 

2613 if cmd in user_options: 

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

2615 op(client, r) 

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

2617 self.delete_user(client, r) 

2618 

2619 

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

2621class UserRemoveAccessKey(BaseAction): 

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

2623 

2624 For example if we wanted to disable keys after 90 days of non-use and 

2625 delete them after 180 days of nonuse: 

2626 

2627 :example: 

2628 

2629 .. code-block:: yaml 

2630 

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

2632 resource: iam-user 

2633 actions: 

2634 - type: remove-keys 

2635 disable: true 

2636 age: 90 

2637 - type: remove-keys 

2638 age: 180 

2639 """ 

2640 

2641 schema = type_schema( 

2642 'remove-keys', 

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

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

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

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

2647 'iam:DeleteAccessKey') 

2648 

2649 def validate(self): 

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

2651 raise PolicyValidationError( 

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

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

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

2655 raise PolicyValidationError( 

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

2657 return self 

2658 

2659 def process(self, resources): 

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

2661 

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

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

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

2665 

2666 if age: 

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

2668 

2669 for r in resources: 

2670 if 'c7n:AccessKeys' not in r: 

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

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

2673 

2674 keys = r['c7n:AccessKeys'] 

2675 if matched: 

2676 m_keys = resolve_credential_keys( 

2677 r.get(CredentialReport.matched_annotation_key), 

2678 keys) 

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

2680 # without having any single key match them all. 

2681 if not m_keys: 

2682 continue 

2683 keys = m_keys 

2684 

2685 for k in keys: 

2686 if age: 

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

2688 continue 

2689 if disable: 

2690 client.update_access_key( 

2691 UserName=r['UserName'], 

2692 AccessKeyId=k['AccessKeyId'], 

2693 Status='Inactive') 

2694 else: 

2695 client.delete_access_key( 

2696 UserName=r['UserName'], 

2697 AccessKeyId=k['AccessKeyId']) 

2698 

2699 

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

2701class UserDeleteSSHKey(BaseAction): 

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

2703 

2704 For example to delete keys after 90 days: 

2705 

2706 :example: 

2707 

2708 .. code-block:: yaml 

2709 

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

2711 resource: iam-user 

2712 actions: 

2713 - type: delete-ssh-keys 

2714 """ 

2715 

2716 schema = type_schema( 

2717 'delete-ssh-keys', 

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

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

2720 annotation_key = 'c7n:SSHKeys' 

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

2722 'iam:DeleteSSHPublicKey') 

2723 

2724 def process(self, resources): 

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

2726 

2727 for r in resources: 

2728 if self.annotation_key not in r: 

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

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

2731 

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

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

2734 

2735 for k in keys: 

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

2737 client.update_ssh_public_key( 

2738 UserName=r['UserName'], 

2739 SSHPublicKeyId=k['SSHPublicKeyId'], 

2740 Status='Inactive') 

2741 else: 

2742 client.delete_ssh_public_key( 

2743 UserName=r['UserName'], 

2744 SSHPublicKeyId=k['SSHPublicKeyId']) 

2745 

2746 

2747def resolve_credential_keys(m_keys, keys): 

2748 res = [] 

2749 for k in m_keys: 

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

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

2752 for ak in keys: 

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

2754 ak = dict(ak) 

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

2756 if ak not in res: 

2757 res.append(ak) 

2758 elif k not in res: 

2759 res.append(k) 

2760 return res 

2761 

2762 

2763################# 

2764# IAM Groups # 

2765################# 

2766 

2767 

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

2769class SpecificIamGroupManagedPolicy(Filter): 

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

2771 

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

2773 

2774 :example: 

2775 

2776 .. code-block:: yaml 

2777 

2778 policies: 

2779 - name: iam-groups-have-admin 

2780 resource: iam-group 

2781 filters: 

2782 - type: has-specific-managed-policy 

2783 value: admin-policy 

2784 """ 

2785 

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

2787 permissions = ('iam:ListAttachedGroupPolicies',) 

2788 

2789 def _managed_policies(self, client, resource): 

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

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

2792 

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

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

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

2796 results = [] 

2797 for r in resources: 

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

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

2800 results.append(r) 

2801 return results 

2802 return [] 

2803 

2804 

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

2806class IamGroupUsers(Filter): 

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

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

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

2810 

2811 :example: 

2812 

2813 .. code-block:: yaml 

2814 

2815 - name: empty-iam-group 

2816 resource: iam-group 

2817 filters: 

2818 - type: has-users 

2819 value: False 

2820 """ 

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

2822 permissions = ('iam:GetGroup',) 

2823 

2824 def _user_count(self, client, resource): 

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

2826 

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

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

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

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

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

2832 

2833 

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

2835class IamGroupInlinePolicy(Filter): 

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

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

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

2839 

2840 :example: 

2841 

2842 .. code-block:: yaml 

2843 

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

2845 resource: iam-group 

2846 filters: 

2847 - type: has-inline-policy 

2848 value: True 

2849 """ 

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

2851 permissions = ('iam:ListGroupPolicies',) 

2852 

2853 def _inline_policies(self, client, resource): 

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

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

2856 return resource 

2857 

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

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

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

2861 res = [] 

2862 for r in resources: 

2863 r = self._inline_policies(c, r) 

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

2865 res.append(r) 

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

2867 res.append(r) 

2868 return res 

2869 

2870 

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

2872class GroupInlinePolicyDelete(BaseAction): 

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

2874 

2875 :example: 

2876 

2877 .. code-block:: yaml 

2878 

2879 - name: iam-delete-group-policies 

2880 resource: aws.iam-group 

2881 filters: 

2882 - type: value 

2883 key: GroupName 

2884 value: test 

2885 actions: 

2886 - type: delete-inline-policies 

2887 """ 

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

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

2890 

2891 def process(self, resources): 

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

2893 for r in resources: 

2894 self.process_group(client, r) 

2895 

2896 def process_group(self, client, r): 

2897 if 'c7n:InlinePolicies' not in r: 

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

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

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

2901 try: 

2902 self.manager.retry(client.delete_group_policy, 

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

2904 except client.exceptions.NoSuchEntityException: 

2905 continue 

2906 

2907 

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

2909class UserGroupDelete(BaseAction): 

2910 """Delete an IAM User Group. 

2911 

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

2913 

2914 :example: 

2915 

2916 .. code-block:: yaml 

2917 

2918 - name: iam-delete-user-group 

2919 resource: aws.iam-group 

2920 filters: 

2921 - type: value 

2922 key: GroupName 

2923 value: test 

2924 actions: 

2925 - type: delete 

2926 force: True 

2927 """ 

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

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

2930 

2931 def process(self, resources): 

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

2933 for r in resources: 

2934 self.process_group(client, r) 

2935 

2936 def process_group(self, client, r): 

2937 error = None 

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

2939 if force: 

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

2941 for user in users: 

2942 client.remove_user_from_group( 

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

2944 

2945 try: 

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

2947 except client.exceptions.DeleteConflictException as e: 

2948 self.log.warning( 

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

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

2951 % r['Arn']) 

2952 error = e 

2953 except (client.exceptions.NoSuchEntityException, 

2954 client.exceptions.UnmodifiableEntityException): 

2955 pass 

2956 if error: 

2957 raise error 

2958 

2959 

2960class SamlProviderDescribe(DescribeSource): 

2961 

2962 def augment(self, resources): 

2963 super().augment(resources) 

2964 for r in resources: 

2965 md = r.get('SAMLMetadataDocument') 

2966 if not md: 

2967 continue 

2968 root = sso_metadata(md) 

2969 r['IDPSSODescriptor'] = root['IDPSSODescriptor'] 

2970 return resources 

2971 

2972 def get_permissions(self): 

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

2974 

2975 

2976def sso_metadata(md): 

2977 root = ElementTree.fromstringlist(md) 

2978 d = {} 

2979 _sso_recurse(root, d) 

2980 return d 

2981 

2982 

2983def _sso_recurse(node, d): 

2984 d.update(node.attrib) 

2985 for c in node: 

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

2987 cd = {} 

2988 if k in d: 

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

2990 d[k] = [d[k]] 

2991 d[k].append(cd) 

2992 else: 

2993 d[k] = cd 

2994 _sso_recurse(c, cd) 

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

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

2997 

2998 

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

3000class SamlProvider(QueryResourceManager): 

3001 """SAML SSO Provider 

3002 

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

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

3005 """ 

3006 

3007 class resource_type(TypeInfo): 

3008 

3009 service = 'iam' 

3010 name = id = 'Arn' 

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

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

3013 arn = 'Arn' 

3014 arn_type = 'saml-provider' 

3015 config_type = "AWS::IAM::SAMLProvider" 

3016 global_resource = True 

3017 

3018 source_mapping = {'describe': SamlProviderDescribe} 

3019 

3020 

3021class OpenIdDescribe(DescribeSource): 

3022 

3023 def get_permissions(self): 

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

3025 

3026 

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

3028class OpenIdProvider(QueryResourceManager): 

3029 

3030 class resource_type(TypeInfo): 

3031 

3032 service = 'iam' 

3033 name = id = 'Arn' 

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

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

3036 arn = 'Arn' 

3037 arn_type = 'oidc-provider' 

3038 global_resource = True 

3039 

3040 source_mapping = {'describe': OpenIdDescribe} 

3041 

3042 

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

3044class OpenIdProviderDelete(BaseAction): 

3045 """Delete an OpenID Connect IAM Identity Provider 

3046 

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

3048 

3049 :example: 

3050 

3051 .. code-block:: yaml 

3052 

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

3054 resource: iam-oidc-provider 

3055 filters: 

3056 - type: value 

3057 key: Url 

3058 value: example.com 

3059 actions: 

3060 - type: delete 

3061 

3062 """ 

3063 schema = type_schema('delete') 

3064 permissions = ('iam:DeleteOpenIDConnectProvider',) 

3065 

3066 def process(self, resources): 

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

3068 for provider in resources: 

3069 self.manager.retry( 

3070 client.delete_open_id_connect_provider, 

3071 OpenIDConnectProviderArn=provider['Arn'], 

3072 ignore_err_codes=( 

3073 'NoSuchEntityException', 

3074 'DeleteConflictException', 

3075 ), 

3076 ) 

3077 

3078 

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

3080class SpecificIamProfileManagedPolicy(ValueFilter): 

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

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

3083 as not having the policy. 

3084 

3085 :example: 

3086 

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

3088 

3089 .. code-block:: yaml 

3090 

3091 policies: 

3092 - name: iam-profiles-have-admin 

3093 resource: aws.iam-profile 

3094 filters: 

3095 - type: has-specific-managed-policy 

3096 value: admin-policy 

3097 

3098 :example: 

3099 

3100 Check for instance profile roles with an attached policy matching 

3101 a given list: 

3102 

3103 .. code-block:: yaml 

3104 

3105 policies: 

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

3107 resource: aws.iam-profile 

3108 filters: 

3109 - type: has-specific-managed-policy 

3110 value: 

3111 - AmazonS3FullAccess 

3112 - AWSOrganizationsFullAccess 

3113 

3114 :example: 

3115 

3116 Check for instance profile roles with attached policy names matching 

3117 a pattern: 

3118 

3119 .. code-block:: yaml 

3120 

3121 policies: 

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

3123 resource: aws.iam-profile 

3124 filters: 

3125 - type: has-specific-managed-policy 

3126 op: glob 

3127 value: "*FullAccess" 

3128 

3129 Check for instance profile roles with attached policy ARNs matching 

3130 a pattern: 

3131 

3132 .. code-block:: yaml 

3133 

3134 policies: 

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

3136 resource: aws.iam-profile 

3137 filters: 

3138 - type: has-specific-managed-policy 

3139 key: PolicyArn 

3140 op: regex 

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

3142 """ 

3143 

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

3145 permissions = ('iam:ListAttachedRolePolicies',) 

3146 annotation_key = 'c7n:AttachedPolicies' 

3147 matched_annotation_key = 'c7n:MatchedPolicies' 

3148 schema_alias = False 

3149 

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

3151 # Preserve backward compatibility 

3152 if 'key' not in data: 

3153 data['key'] = 'PolicyName' 

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

3155 

3156 # This code assumes a single IAM role in the profile and the annotation 

3157 # is added to the profile resource, not the role 

3158 def get_managed_policies(self, client, prof_set): 

3159 for prof in prof_set: 

3160 prof[self.annotation_key] = [] 

3161 for role in prof.get('Roles', []): 

3162 prof[self.annotation_key] = [ 

3163 role_policy 

3164 for role_policy 

3165 in client.list_attached_role_policies( 

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

3167 ] 

3168 

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

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

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

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

3173 self.log.debug( 

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

3175 list(w.map( 

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

3177 chunks(augment_set, 50))) 

3178 

3179 matched = [] 

3180 for r in resources: 

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

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

3183 if matched_keys: 

3184 matched.append(r) 

3185 return matched