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

1344 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

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 date = 'CreateDate' 

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

105 # Denotes this resource type exists across regions 

106 global_resource = True 

107 arn = 'Arn' 

108 

109 source_mapping = { 

110 'describe': DescribeRole, 

111 'config': ConfigSource 

112 } 

113 

114 

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

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

117 

118 

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

120class RolePostFinding(OtherResourcePostFinding): 

121 

122 resource_type = 'AwsIamRole' 

123 

124 def format_resource(self, r): 

125 envelope, payload = self.format_envelope(r) 

126 payload.update(self.filter_empty( 

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

128 'MaxSessionDuration', 'Path', 'RoleId', 

129 'RoleName']))) 

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

131 payload['AssumeRolePolicyDocument']) 

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

133 return envelope 

134 

135 

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

137class RoleTag(Tag): 

138 """Tag an iam role.""" 

139 

140 permissions = ('iam:TagRole',) 

141 

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

143 for role in roles: 

144 try: 

145 self.manager.retry( 

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

147 except client.exceptions.NoSuchEntityException: 

148 continue 

149 

150 

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

152class RoleRemoveTag(RemoveTag): 

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

154 

155 permissions = ('iam:UntagRole',) 

156 

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

158 for role in roles: 

159 try: 

160 self.manager.retry( 

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

162 except client.exceptions.NoSuchEntityException: 

163 continue 

164 

165 

166class SetBoundary(BaseAction): 

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

168 

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

170 """ 

171 

172 schema = type_schema( 

173 'set-boundary', 

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

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

176 

177 def validate(self): 

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

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

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

181 

182 def process(self, resources): 

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

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

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

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

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

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

189 self.manager.account_id, policy) 

190 for r in resources: 

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

192 try: 

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

194 except client.exceptions.NoSuchEntityException: 

195 continue 

196 

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

198 raise NotImplementedError() 

199 

200 

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

202class RoleSetBoundary(SetBoundary): 

203 

204 def get_permissions(self): 

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

206 return ('iam:PutRolePermissionsBoundary',) 

207 return ('iam:DeleteRolePermissionsBoundary',) 

208 

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

210 if state: 

211 return client.put_role_permissions_boundary, { 

212 'RoleName': resource['RoleName'], 

213 'PermissionsBoundary': policy} 

214 else: 

215 return client.delete_role_permissions_boundary, { 

216 'RoleName': resource['RoleName']} 

217 

218 

219class DescribeUser(DescribeSource): 

220 

221 def augment(self, resources): 

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

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

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

225 results = [] 

226 for r in resources: 

227 ru = self.manager.retry( 

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

229 ignore_err_codes=client.exceptions.NoSuchEntityException) 

230 if ru: 

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

232 return list(filter(None, results)) 

233 

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

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

236 results = [] 

237 

238 for r in resource_ids: 

239 try: 

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

241 except client.exceptions.NoSuchEntityException: 

242 continue 

243 return results 

244 

245 

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

247class User(QueryResourceManager): 

248 

249 class resource_type(TypeInfo): 

250 service = 'iam' 

251 arn_type = 'user' 

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

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

254 id = name = 'UserName' 

255 date = 'CreateDate' 

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

257 # Denotes this resource type exists across regions 

258 global_resource = True 

259 arn = 'Arn' 

260 config_id = 'UserId' 

261 

262 source_mapping = { 

263 'describe': DescribeUser, 

264 'config': ConfigSource 

265 } 

266 

267 

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

269class UserTag(Tag): 

270 """Tag an iam user.""" 

271 

272 permissions = ('iam:TagUser',) 

273 

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

275 for u in users: 

276 try: 

277 self.manager.retry( 

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

279 except client.exceptions.NoSuchEntityException: 

280 continue 

281 

282 

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

284class UserRemoveTag(RemoveTag): 

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

286 

287 permissions = ('iam:UntagUser',) 

288 

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

290 for u in users: 

291 try: 

292 self.manager.retry( 

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

294 except client.exceptions.NoSuchEntityException: 

295 continue 

296 

297 

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

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

300 

301 

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

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

304 

305 

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

307class SetGroups(BaseAction): 

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

309 

310 :example: 

311 

312 .. code-block:: yaml 

313 

314 - name: iam-user-add-remove 

315 resource: iam-user 

316 filters: 

317 - type: value 

318 key: UserName 

319 value: Bob 

320 actions: 

321 - type: set-groups 

322 state: remove 

323 group: Admin 

324 

325 """ 

326 schema = type_schema( 

327 'set-groups', 

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

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

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

331 ) 

332 

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

334 

335 def validate(self): 

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

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

338 % (self.manager.data)) 

339 

340 def process(self, resources): 

341 group_name = self.data['group'] 

342 state = self.data['state'] 

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

344 op_map = { 

345 'add': client.add_user_to_group, 

346 'remove': client.remove_user_from_group 

347 } 

348 for r in resources: 

349 try: 

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

351 except client.exceptions.NoSuchEntityException: 

352 continue 

353 

354 

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

356class UserSetBoundary(SetBoundary): 

357 

358 def get_permissions(self): 

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

360 return ('iam:PutUserPermissionsBoundary',) 

361 return ('iam:DeleteUserPermissionsBoundary',) 

362 

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

364 if state: 

365 return client.put_user_permissions_boundary, { 

366 'UserName': resource['UserName'], 

367 'PermissionsBoundary': policy} 

368 else: 

369 return client.delete_user_permissions_boundary, { 

370 'UserName': resource['UserName']} 

371 

372 

373class DescribePolicy(DescribeSource): 

374 

375 def resources(self, query=None): 

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

377 query = query or {} 

378 if qfilters: 

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

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

381 

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

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

384 results = [] 

385 

386 for r in resource_ids: 

387 try: 

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

389 except ClientError as e: 

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

391 continue 

392 return results 

393 

394 def augment(self, resources): 

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

396 

397 

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

399class Policy(QueryResourceManager): 

400 

401 class resource_type(TypeInfo): 

402 service = 'iam' 

403 arn_type = 'policy' 

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

405 id = 'PolicyId' 

406 name = 'PolicyName' 

407 date = 'CreateDate' 

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

409 # Denotes this resource type exists across regions 

410 global_resource = True 

411 arn = 'Arn' 

412 universal_taggable = object() 

413 

414 source_mapping = { 

415 'describe': DescribePolicy, 

416 'config': ConfigSource 

417 } 

418 

419 

420class PolicyQueryParser(QueryParser): 

421 

422 QuerySchema = { 

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

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

425 'PathPrefix': str, 

426 'OnlyAttached': bool 

427 } 

428 multi_value = False 

429 value_key = 'Value' 

430 

431 

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

433class InstanceProfile(QueryResourceManager): 

434 

435 class resource_type(TypeInfo): 

436 service = 'iam' 

437 arn_type = 'instance-profile' 

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

439 name = id = 'InstanceProfileName' 

440 date = 'CreateDate' 

441 # Denotes this resource type exists across regions 

442 global_resource = True 

443 arn = 'Arn' 

444 cfn_type = 'AWS::IAM::InstanceProfile' 

445 

446 

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

448class ServerCertificate(QueryResourceManager): 

449 

450 class resource_type(TypeInfo): 

451 service = 'iam' 

452 arn_type = 'server-certificate' 

453 enum_spec = ('list_server_certificates', 

454 'ServerCertificateMetadataList', 

455 None) 

456 name = id = 'ServerCertificateName' 

457 config_type = "AWS::IAM::ServerCertificate" 

458 name = 'ServerCertificateName' 

459 date = 'Expiration' 

460 # Denotes this resource type exists across regions 

461 global_resource = True 

462 

463 

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

465class CertificateDelete(BaseAction): 

466 """Delete an IAM Certificate 

467 

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

469 

470 :example: 

471 

472 .. code-block:: yaml 

473 

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

475 resource: iam-certificate 

476 filters: 

477 - type: value 

478 key: Expiration 

479 value_type: expiration 

480 op: greater-than 

481 value: 0 

482 actions: 

483 - type: delete 

484 

485 """ 

486 schema = type_schema('delete') 

487 permissions = ('iam:DeleteServerCertificate',) 

488 

489 def process(self, resources): 

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

491 for cert in resources: 

492 self.manager.retry( 

493 client.delete_server_certificate, 

494 ServerCertificateName=cert['ServerCertificateName'], 

495 ignore_err_codes=( 

496 'NoSuchEntityException', 

497 'DeleteConflictException', 

498 ), 

499 ) 

500 

501 

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

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

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

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

506class ServiceUsage(Filter): 

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

508 

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

510 is against the last 365 days of data. 

511 

512 Each service access record is evaluated against all specified 

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

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

515 

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

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

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

519 means all service access records have to match. 

520 

521 

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

523 

524 :example: 

525 

526 .. code-block:: yaml 

527 

528 - name: usage-unused-users 

529 resource: iam-user 

530 filters: 

531 - type: usage 

532 match-operator: all 

533 LastAuthenticated: null 

534 

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

536 

537 :example: 

538 

539 .. code-block:: yaml 

540 

541 - name: unused-users 

542 resource: iam-user 

543 filters: 

544 - type: usage 

545 ServiceNamespace: dynamodb 

546 TotalAuthenticatedEntities: 1 

547 LastAuthenticated: 

548 type: value 

549 value_type: age 

550 op: less-than 

551 value: 30 

552 match-operator: any 

553 

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

555 

556 """ 

557 

558 JOB_COMPLETE = 'COMPLETED' 

559 SERVICE_ATTR = { 

560 'ServiceName', 'ServiceNamespace', 'TotalAuthenticatedEntities', 

561 'LastAuthenticated', 'LastAuthenticatedEntity'} 

562 

563 schema_alias = True 

564 schema_attr = { 

565 sa: {'oneOf': [ 

566 {'type': 'string'}, 

567 {'type': 'boolean'}, 

568 {'type': 'number'}, 

569 {'type': 'null'}, 

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

571 for sa in sorted(SERVICE_ATTR)} 

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

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

574 schema = type_schema( 

575 'usage', 

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

577 **schema_attr) 

578 permissions = ('iam:GenerateServiceLastAccessedDetails', 

579 'iam:GetServiceLastAccessedDetails') 

580 

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

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

583 

584 job_resource_map = {} 

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

586 try: 

587 jid = self.manager.retry( 

588 client.generate_service_last_accessed_details, 

589 Arn=arn)['JobId'] 

590 job_resource_map[jid] = r 

591 except client.exceptions.NoSuchEntityException: 

592 continue 

593 

594 conf = dict(self.data) 

595 conf.pop('match-operator') 

596 saf = MultiAttrFilter(conf) 

597 saf.multi_attrs = self.SERVICE_ATTR 

598 

599 results = [] 

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

601 

602 while job_resource_map: 

603 job_results_map = {} 

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

605 result = self.manager.retry( 

606 client.get_service_last_accessed_details, JobId=jid) 

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

608 continue 

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

610 

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

612 r = job_resource_map.pop(jid) 

613 saf_matches = saf.process(saf_results) 

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

615 results.append(r) 

616 elif match_operator != 'all' and saf_matches: 

617 results.append(r) 

618 

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

620 

621 return results 

622 

623 

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

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

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

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

628class CheckPermissions(Filter): 

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

630 

631 :example: 

632 

633 Find users that can create other users 

634 

635 .. code-block:: yaml 

636 

637 policies: 

638 - name: super-users 

639 resource: aws.iam-user 

640 filters: 

641 - type: check-permissions 

642 match: allowed 

643 actions: 

644 - iam:CreateUser 

645 

646 :example: 

647 

648 Find users with access to all services and actions 

649 

650 .. code-block:: yaml 

651 

652 policies: 

653 - name: admin-users 

654 resource: aws.iam-user 

655 filters: 

656 - type: check-permissions 

657 match: allowed 

658 actions: 

659 - '*:*' 

660 

661 By default permission boundaries are checked. 

662 """ 

663 

664 schema = type_schema( 

665 'check-permissions', **{ 

666 'match': {'oneOf': [ 

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

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

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

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

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

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

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

674 schema_alias = True 

675 policy_annotation = 'c7n:policy' 

676 eval_annotation = 'c7n:perm-matches' 

677 

678 def validate(self): 

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

680 # 

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

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

683 # 

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

685 # runtime exceptions. 

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

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

688 raise PolicyValidationError( 

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

690 % (action,)) 

691 return self 

692 

693 def get_permissions(self): 

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

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

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

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

698 # for simulating w/ permission boundaries 

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

700 return perms 

701 

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

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

704 actions = self.data['actions'] 

705 matcher = self.get_eval_matcher() 

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

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

708 

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

710 self.simulation_boundary_override = ''' 

711 { 

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

713 "Statement": [{ 

714 "Effect": "Allow", 

715 "Action": "*", 

716 "Resource": "*" 

717 }] 

718 } 

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

720 results = [] 

721 eval_cache = {} 

722 for arn, r in arn_resources: 

723 if arn is None: 

724 continue 

725 if arn in eval_cache: 

726 evaluations = eval_cache[arn] 

727 else: 

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

729 eval_cache[arn] = evaluations 

730 if not evaluations: 

731 continue 

732 matches = [] 

733 matched = [] 

734 for e in evaluations: 

735 match = matcher(e) 

736 if match: 

737 matched.append(e) 

738 matches.append(match) 

739 if operator(matches): 

740 r[self.eval_annotation] = matched 

741 results.append(r) 

742 return results 

743 

744 def get_iam_arns(self, resources): 

745 return self.manager.get_arns(resources) 

746 

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

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

749 policy = r.get(self.policy_annotation) 

750 if policy is None: 

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

752 PolicyArn=r['Arn'], 

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

754 evaluations = self.manager.retry( 

755 client.simulate_custom_policy, 

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

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

758 return evaluations 

759 

760 params = dict( 

761 PolicySourceArn=arn, 

762 ActionNames=actions, 

763 ignore_err_codes=('NoSuchEntity',)) 

764 

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

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

767 # as the boundary. 

768 # 

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

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

771 # of using existing boundaries. 

772 if self.simulation_boundary_override: 

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

774 

775 evaluations = (self.manager.retry( 

776 client.simulate_principal_policy, 

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

778 return evaluations 

779 

780 def get_eval_matcher(self): 

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

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

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

784 else: 

785 values = ['allowed'] 

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

787 'EvalDecision', 'value': values, 

788 'op': 'in'}) 

789 else: 

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

791 vf.annotate = False 

792 return vf 

793 

794 

795class IamRoleUsage(Filter): 

796 

797 def get_permissions(self): 

798 perms = list(itertools.chain(*[ 

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

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

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

802 return perms 

803 

804 def service_role_usage(self): 

805 results = set() 

806 results.update(self.scan_lambda_roles()) 

807 results.update(self.scan_ecs_roles()) 

808 results.update(self.collect_profile_roles()) 

809 return results 

810 

811 def instance_profile_usage(self): 

812 results = set() 

813 results.update(self.scan_asg_roles()) 

814 results.update(self.scan_ec2_roles()) 

815 return results 

816 

817 def scan_lambda_roles(self): 

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

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

820 

821 def scan_ecs_roles(self): 

822 results = [] 

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

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

825 services = client.list_services( 

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

827 if services: 

828 for service in client.describe_services( 

829 cluster=cluster['clusterName'], 

830 services=services)['services']: 

831 if 'roleArn' in service: 

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

833 return results 

834 

835 def collect_profile_roles(self): 

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

837 profiles = set() 

838 profiles.update(self.scan_asg_roles()) 

839 profiles.update(self.scan_ec2_roles()) 

840 

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

842 iprofiles = manager.resources() 

843 results = [] 

844 for p in iprofiles: 

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

846 continue 

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

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

849 return results 

850 

851 def scan_asg_roles(self): 

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

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

854 'IamInstanceProfile' in r)] 

855 

856 def scan_ec2_roles(self): 

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

858 results = [] 

859 for e in manager.resources(): 

860 # do not include instances that have been recently terminated 

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

862 continue 

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

864 if not profile_arn: 

865 continue 

866 # split arn to get the profile name 

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

868 return results 

869 

870 

871################### 

872# IAM Roles # 

873################### 

874 

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

876class UsedIamRole(IamRoleUsage): 

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

878 

879 Checks for usage on EC2, Lambda, ECS only 

880 

881 :example: 

882 

883 .. code-block:: yaml 

884 

885 policies: 

886 - name: iam-role-in-use 

887 resource: iam-role 

888 filters: 

889 - type: used 

890 state: true 

891 """ 

892 

893 schema = type_schema( 

894 'used', 

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

896 

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

898 roles = self.service_role_usage() 

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

900 return [r for r in resources if ( 

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

902 

903 return [r for r in resources if ( 

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

905 

906 

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

908class UnusedIamRole(IamRoleUsage): 

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

910 

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

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

913 

914 Checks for usage on EC2, Lambda, ECS only 

915 

916 :example: 

917 

918 .. code-block:: yaml 

919 

920 policies: 

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

922 resource: iam-role 

923 filters: 

924 - type: used 

925 state: false 

926 """ 

927 deprecations = ( 

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

929 ) 

930 

931 schema = type_schema('unused') 

932 

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

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

935 

936 

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

938class RoleCrossAccountAccess(CrossAccountAccessFilter): 

939 

940 policy_attribute = 'AssumeRolePolicyDocument' 

941 permissions = ('iam:ListRoles',) 

942 

943 schema = type_schema( 

944 'cross-account', 

945 # white list accounts 

946 whitelist_from=ValuesFrom.schema, 

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

948 

949 

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

951class IamRoleInlinePolicy(Filter): 

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

953 True: Filter roles that have an inline-policy 

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

955 

956 :example: 

957 

958 .. code-block:: yaml 

959 

960 policies: 

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

962 resource: iam-role 

963 filters: 

964 - type: has-inline-policy 

965 value: True 

966 """ 

967 

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

969 permissions = ('iam:ListRolePolicies',) 

970 

971 def _inline_policies(self, client, resource): 

972 policies = client.list_role_policies( 

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

974 resource['c7n:InlinePolicies'] = policies 

975 return resource 

976 

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

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

979 res = [] 

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

981 for r in resources: 

982 r = self._inline_policies(c, r) 

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

984 res.append(r) 

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

986 res.append(r) 

987 return res 

988 

989 

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

991class SpecificIamRoleManagedPolicy(ValueFilter): 

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

993 

994 :example: 

995 

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

997 

998 .. code-block:: yaml 

999 

1000 policies: 

1001 - name: iam-roles-have-admin 

1002 resource: aws.iam-role 

1003 filters: 

1004 - type: has-specific-managed-policy 

1005 value: admin-policy 

1006 

1007 :example: 

1008 

1009 Check for roles with an attached policy matching 

1010 a given list: 

1011 

1012 .. code-block:: yaml 

1013 

1014 policies: 

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

1016 resource: aws.iam-role 

1017 filters: 

1018 - type: has-specific-managed-policy 

1019 op: in 

1020 value: 

1021 - AmazonS3FullAccess 

1022 - AWSOrganizationsFullAccess 

1023 

1024 :example: 

1025 

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

1027 

1028 .. code-block:: yaml 

1029 

1030 policies: 

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

1032 resource: aws.iam-role 

1033 filters: 

1034 - type: has-specific-managed-policy 

1035 op: glob 

1036 value: "*FullAccess" 

1037 

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

1039 

1040 .. code-block:: yaml 

1041 

1042 policies: 

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

1044 resource: aws.iam-role 

1045 filters: 

1046 - type: has-specific-managed-policy 

1047 key: PolicyArn 

1048 op: regex 

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

1050 """ 

1051 

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

1053 permissions = ('iam:ListAttachedRolePolicies',) 

1054 annotation_key = 'c7n:AttachedPolicies' 

1055 matched_annotation_key = 'c7n:MatchedPolicies' 

1056 schema_alias = False 

1057 

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

1059 # Preserve backward compatibility 

1060 if 'key' not in data: 

1061 data['key'] = 'PolicyName' 

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

1063 

1064 def get_managed_policies(self, client, role_set): 

1065 for role in role_set: 

1066 role[self.annotation_key] = [ 

1067 role_policy 

1068 for role_policy 

1069 in client.list_attached_role_policies( 

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

1071 ] 

1072 

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

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

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

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

1077 self.log.debug( 

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

1079 list(w.map( 

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

1081 chunks(augment_set, 50))) 

1082 

1083 matched = [] 

1084 for r in resources: 

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

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

1087 if matched_keys: 

1088 matched.append(r) 

1089 return matched 

1090 

1091 

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

1093class NoSpecificIamRoleManagedPolicy(Filter): 

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

1095 

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

1097 

1098 :example: 

1099 

1100 .. code-block:: yaml 

1101 

1102 policies: 

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

1104 resource: iam-role 

1105 filters: 

1106 - type: no-specific-managed-policy 

1107 value: ip-restriction 

1108 """ 

1109 

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

1111 permissions = ('iam:ListAttachedRolePolicies',) 

1112 

1113 def _managed_policies(self, client, resource): 

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

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

1116 

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

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

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

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

1121 self._managed_policies(c, r)] 

1122 return [] 

1123 

1124 

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

1126class SetPolicy(BaseAction): 

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

1128 

1129 You will identify the policy by its arn. 

1130 

1131 Returns a list of roles modified by the action. 

1132 

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

1134 

1135 :example: 

1136 

1137 .. code-block:: yaml 

1138 

1139 - name: iam-attach-role-policy 

1140 resource: iam-role 

1141 filters: 

1142 - type: no-specific-managed-policy 

1143 value: my-iam-policy 

1144 actions: 

1145 - type: set-policy 

1146 state: detached 

1147 arn: "*" 

1148 - type: set-policy 

1149 state: attached 

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

1151 

1152 """ 

1153 schema = type_schema( 

1154 'set-policy', 

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

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

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

1158 

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

1160 

1161 def validate(self): 

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

1163 raise PolicyValidationError( 

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

1165 

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

1167 client.attach_role_policy( 

1168 RoleName=resource['RoleName'], 

1169 PolicyArn=policy_arn 

1170 ) 

1171 

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

1173 try: 

1174 client.detach_role_policy( 

1175 RoleName=resource['RoleName'], 

1176 PolicyArn=policy_arn 

1177 ) 

1178 except client.exceptions.NoSuchEntityException: 

1179 return 

1180 

1181 def list_attached_policies(self, client, resource): 

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

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

1184 return policy_arns 

1185 

1186 def process(self, resources): 

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

1188 policy_arn = self.data['arn'] 

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

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

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

1192 self.manager.account_id, policy_arn) 

1193 state = self.data['state'] 

1194 for r in resources: 

1195 if state == 'attached': 

1196 self.attach_policy(client, r, policy_arn) 

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

1198 self.detach_policy(client, r, policy_arn) 

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

1200 try: 

1201 self.detach_all_policies(client, r) 

1202 except client.exceptions.NoSuchEntityException: 

1203 continue 

1204 

1205 def detach_all_policies(self, client, resource): 

1206 policy_arns = self.list_attached_policies(client, resource) 

1207 for parn in policy_arns: 

1208 self.detach_policy(client, resource, parn) 

1209 

1210 

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

1212class SetUserPolicy(SetPolicy): 

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

1214 

1215 You will identify the policy by its arn. 

1216 

1217 Returns a list of roles modified by the action. 

1218 

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

1220 detaching all exisitng policies: 

1221 

1222 :example: 

1223 

1224 .. code-block:: yaml 

1225 

1226 - name: iam-attach-user-policy 

1227 resource: iam-user 

1228 filters: 

1229 - type: value 

1230 key: UserName 

1231 op: not-in 

1232 value: 

1233 - AdminUser1 

1234 - AdminUser2 

1235 actions: 

1236 - type: set-policy 

1237 state: detached 

1238 arn: arn:aws:iam::aws:policy/AdministratorAccess 

1239 

1240 """ 

1241 

1242 permissions = ( 

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

1244 

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

1246 client.attach_user_policy( 

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

1248 

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

1250 try: 

1251 client.detach_user_policy( 

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

1253 except client.exceptions.NoSuchEntityException: 

1254 return 

1255 

1256 def list_attached_policies(self, client, resource): 

1257 attached_policies = client.list_attached_user_policies( 

1258 UserName=resource["UserName"] 

1259 ) 

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

1261 return policy_arns 

1262 

1263 

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

1265class SetGroupPolicy(SetPolicy): 

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

1267 

1268 You will identify the policy by its arn. 

1269 

1270 Returns a list of roles modified by the action. 

1271 

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

1273 detaching all exisitng policies: 

1274 

1275 :example: 

1276 

1277 .. code-block:: yaml 

1278 

1279 - name: iam-attach-group-policy 

1280 resource: iam-group 

1281 actions: 

1282 - type: set-policy 

1283 state: detached 

1284 arn: "*" 

1285 - type: set-policy 

1286 state: attached 

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

1288 

1289 """ 

1290 

1291 permissions = ( 

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

1293 

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

1295 client.attach_group_policy( 

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

1297 

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

1299 try: 

1300 client.detach_group_policy( 

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

1302 except client.exceptions.NoSuchEntityException: 

1303 return 

1304 

1305 def list_attached_policies(self, client, resource): 

1306 attached_policies = client.list_attached_group_policies( 

1307 GroupName=resource["GroupName"] 

1308 ) 

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

1310 return policy_arns 

1311 

1312 

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

1314class RoleDelete(BaseAction): 

1315 """Delete an IAM Role. 

1316 

1317 To delete IAM Role you must first delete the policies 

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

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

1320 

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

1322 

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

1324 policies that are associated with the role would be detached 

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

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

1327 

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

1329 

1330 :example: 

1331 

1332 .. code-block:: yaml 

1333 

1334 - name: iam-delete-unused-role 

1335 resource: iam-role 

1336 filters: 

1337 - type: usage 

1338 match-operator: all 

1339 LastAuthenticated: null 

1340 actions: 

1341 - type: delete 

1342 force: true 

1343 

1344 """ 

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

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

1347 

1348 def detach_inline_policies(self, client, r): 

1349 policies = (self.manager.retry( 

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

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

1352 for p in policies: 

1353 self.manager.retry( 

1354 client.delete_role_policy, 

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

1356 ignore_err_codes=('NoSuchEntityException',)) 

1357 

1358 def delete_instance_profiles(self, client, r): 

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

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

1361 profile_names = [] 

1362 profiles = self.manager.retry( 

1363 client.list_instance_profiles_for_role, 

1364 RoleName=r['RoleName'], 

1365 ignore_err_codes=('NoSuchEntityException',)) 

1366 if profiles: 

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

1368 for p in profile_names: 

1369 self.manager.retry( 

1370 client.remove_role_from_instance_profile, 

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

1372 ignore_err_codes=('NoSuchEntityException',)) 

1373 self.manager.retry( 

1374 client.delete_instance_profile, 

1375 InstanceProfileName=p, 

1376 ignore_err_codes=('NoSuchEntityException',)) 

1377 

1378 def process(self, resources): 

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

1380 error = None 

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

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

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

1384 policy_setter.process(resources) 

1385 

1386 for r in resources: 

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

1388 self.detach_inline_policies(client, r) 

1389 self.delete_instance_profiles(client, r) 

1390 try: 

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

1392 except client.exceptions.DeleteConflictException as e: 

1393 self.log.warning( 

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

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

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

1397 error = e 

1398 except (client.exceptions.NoSuchEntityException, 

1399 client.exceptions.UnmodifiableEntityException): 

1400 continue 

1401 if error: 

1402 raise error 

1403 

1404 

1405###################### 

1406# IAM Policies # 

1407###################### 

1408 

1409 

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

1411class UsedIamPolicies(Filter): 

1412 """Filter IAM policies that are being used 

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

1414 

1415 :example: 

1416 

1417 .. code-block:: yaml 

1418 

1419 policies: 

1420 - name: iam-policy-used 

1421 resource: iam-policy 

1422 filters: 

1423 - type: used 

1424 """ 

1425 

1426 schema = type_schema('used') 

1427 permissions = ('iam:ListPolicies',) 

1428 

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

1430 return [r for r in resources if 

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

1432 

1433 

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

1435class UnusedIamPolicies(Filter): 

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

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

1438 

1439 :example: 

1440 

1441 .. code-block:: yaml 

1442 

1443 policies: 

1444 - name: iam-policy-unused 

1445 resource: iam-policy 

1446 filters: 

1447 - type: unused 

1448 """ 

1449 

1450 schema = type_schema('unused') 

1451 permissions = ('iam:ListPolicies',) 

1452 

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

1454 return [r for r in resources if 

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

1456 

1457 

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

1459class AllowAllIamPolicies(Filter): 

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

1461 

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

1463 policy must exist with the following requirements. 

1464 

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

1466 

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

1468 For example: 

1469 

1470 .. code-block:: json 

1471 

1472 { 

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

1474 "Statement": [{ 

1475 "Action": "*", 

1476 "Resource": "*", 

1477 "Effect": "Allow" 

1478 }] 

1479 } 

1480 

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

1482 'NotAction'. 

1483 

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

1485 allow all: 

1486 

1487 .. code-block:: yaml 

1488 

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

1490 resource: iam-policy 

1491 filters: 

1492 - type: used 

1493 - type: has-allow-all 

1494 

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

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

1497 above. 

1498 

1499 """ 

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

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

1502 

1503 def has_allow_all_policy(self, client, resource): 

1504 statements = client.get_policy_version( 

1505 PolicyArn=resource['Arn'], 

1506 VersionId=resource['DefaultVersionId'] 

1507 )['PolicyVersion']['Document']['Statement'] 

1508 if isinstance(statements, dict): 

1509 statements = [statements] 

1510 

1511 for s in statements: 

1512 if ('Condition' not in s and 

1513 'Action' in s and 

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

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

1516 'Resource' in s and 

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

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

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

1520 return True 

1521 return False 

1522 

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

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

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

1526 self.log.info( 

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

1528 len(results), len(resources)) 

1529 return results 

1530 

1531 

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

1533class PolicyDelete(BaseAction): 

1534 """Delete an IAM Policy. 

1535 

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

1537 

1538 :example: 

1539 

1540 .. code-block:: yaml 

1541 

1542 - name: iam-delete-unused-policies 

1543 resource: iam-policy 

1544 filters: 

1545 - type: unused 

1546 actions: 

1547 - delete 

1548 

1549 """ 

1550 schema = type_schema('delete') 

1551 permissions = ('iam:DeletePolicy',) 

1552 

1553 def process(self, resources): 

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

1555 

1556 rcount = len(resources) 

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

1558 if len(resources) != rcount: 

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

1560 rcount, len(resources)) 

1561 

1562 for r in resources: 

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

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

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

1566 for v in versions: 

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

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

1569 

1570 

1571############################### 

1572# IAM Instance Profiles # 

1573############################### 

1574 

1575 

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

1577class UsedInstanceProfiles(IamRoleUsage): 

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

1579 

1580 :example: 

1581 

1582 .. code-block:: yaml 

1583 

1584 policies: 

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

1586 resource: iam-profile 

1587 filters: 

1588 - type: used 

1589 """ 

1590 

1591 schema = type_schema('used') 

1592 

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

1594 results = [] 

1595 profiles = self.instance_profile_usage() 

1596 for r in resources: 

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

1598 results.append(r) 

1599 self.log.info( 

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

1601 len(results), len(resources))) 

1602 return results 

1603 

1604 

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

1606class UnusedInstanceProfiles(IamRoleUsage): 

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

1608 

1609 :example: 

1610 

1611 .. code-block:: yaml 

1612 

1613 policies: 

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

1615 resource: iam-profile 

1616 filters: 

1617 - type: unused 

1618 """ 

1619 

1620 schema = type_schema('unused') 

1621 

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

1623 results = [] 

1624 profiles = self.instance_profile_usage() 

1625 for r in resources: 

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

1627 results.append(r) 

1628 self.log.info( 

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

1630 len(results), len(resources))) 

1631 return results 

1632 

1633 

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

1635class InstanceProfileSetRole(BaseAction): 

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

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

1638 

1639 :example: 

1640 

1641 .. code-block:: yaml 

1642 

1643 policies: 

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

1645 resource: iam-profile 

1646 actions: 

1647 - type: set-role 

1648 role: my-test-role 

1649 """ 

1650 

1651 schema = type_schema('set-role', 

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

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

1654 

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

1656 self.manager.retry( 

1657 client.add_role_to_instance_profile, 

1658 InstanceProfileName=resource['InstanceProfileName'], 

1659 RoleName=role 

1660 ) 

1661 return 

1662 

1663 def remove_role(self, client, resource): 

1664 self.manager.retry( 

1665 client.remove_role_from_instance_profile, 

1666 InstanceProfileName=resource['InstanceProfileName'], 

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

1668 ) 

1669 return 

1670 

1671 def process(self, resources): 

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

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

1674 for r in resources: 

1675 if not role: 

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

1677 continue 

1678 else: 

1679 self.remove_role(client, r) 

1680 else: 

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

1682 self.add_role(client, r, role) 

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

1684 continue 

1685 else: 

1686 self.remove_role(client, r) 

1687 self.add_role(client, r, role) 

1688 

1689 

1690################### 

1691# IAM Users # 

1692################### 

1693 

1694class CredentialReport(Filter): 

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

1696 

1697 The IAM Credential report aggregates multiple pieces of 

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

1699 querying multiple aspects of a user that would otherwise require 

1700 per user api calls. 

1701 

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

1703 

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

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

1706 last month 

1707 

1708 .. code-block:: yaml 

1709 

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

1711 resource: iam-user 

1712 filters: 

1713 - type: credential 

1714 key: mfa_active 

1715 value: true 

1716 - type: credential 

1717 key: password_last_used 

1718 value: absent 

1719 - type: credential 

1720 key: access_keys.last_used_date 

1721 value_type: age 

1722 value: 30 

1723 op: less-than 

1724 

1725 Credential Report Transforms 

1726 

1727 We perform some default transformations from the raw 

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

1729 are turned into array of dictionaries for matching 

1730 purposes with their common prefixes stripped. 

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

1732 into boolean values. 

1733 

1734 """ 

1735 schema = type_schema( 

1736 'credential', 

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

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

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

1740 'enum': [ 

1741 'user', 

1742 'arn', 

1743 'user_creation_time', 

1744 'password_enabled', 

1745 'password_last_used', 

1746 'password_last_changed', 

1747 'password_next_rotation', 

1748 'mfa_active', 

1749 'access_keys', 

1750 'access_keys.active', 

1751 'access_keys.last_used_date', 

1752 'access_keys.last_used_region', 

1753 'access_keys.last_used_service', 

1754 'access_keys.last_rotated', 

1755 'certs', 

1756 'certs.active', 

1757 'certs.last_rotated', 

1758 ]}, 

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

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

1761 report_generate={ 

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

1763 'default': True, 

1764 'type': 'boolean'}, 

1765 report_delay={ 

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

1767 'default': 10, 

1768 'type': 'number'}, 

1769 report_max_age={ 

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

1771 'default': 60 * 60 * 24, 

1772 'type': 'number'}) 

1773 

1774 list_sub_objects = ( 

1775 ('access_key_1_', 'access_keys'), 

1776 ('access_key_2_', 'access_keys'), 

1777 ('cert_1_', 'certs'), 

1778 ('cert_2_', 'certs')) 

1779 

1780 # for access keys only 

1781 matched_annotation_key = 'c7n:matched-keys' 

1782 

1783 permissions = ('iam:GenerateCredentialReport', 

1784 'iam:GetCredentialReport') 

1785 

1786 def get_value_or_schema_default(self, k): 

1787 if k in self.data: 

1788 return self.data[k] 

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

1790 

1791 def get_credential_report(self): 

1792 cache = self.manager._cache 

1793 with cache: 

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

1795 report = cache.get(cache_key) 

1796 

1797 if report: 

1798 return report 

1799 data = self.fetch_credential_report() 

1800 report = {} 

1801 if isinstance(data, bytes): 

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

1803 else: 

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

1805 headers = next(reader) 

1806 for line in reader: 

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

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

1809 cache.save(cache_key, report) 

1810 

1811 return report 

1812 

1813 @classmethod 

1814 def process_user_record(cls, info): 

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

1816 keys = list(info.keys()) 

1817 # Value conversion 

1818 for k in keys: 

1819 v = info[k] 

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

1821 info[k] = None 

1822 elif v == 'false': 

1823 info[k] = False 

1824 elif v == 'true': 

1825 info[k] = True 

1826 # Object conversion 

1827 for p, t in cls.list_sub_objects: 

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

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

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

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

1832 return info 

1833 

1834 def fetch_credential_report(self): 

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

1836 try: 

1837 report = client.get_credential_report() 

1838 except ClientError as e: 

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

1840 report = None 

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

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

1843 # for it again. 

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

1845 report = client.get_credential_report() 

1846 else: 

1847 raise 

1848 if report: 

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

1850 seconds=self.get_value_or_schema_default( 

1851 'report_max_age')) 

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

1853 threshold = threshold.replace(tzinfo=None) 

1854 if report['GeneratedTime'] < threshold: 

1855 report = None 

1856 if report is None: 

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

1858 raise ValueError("Credential Report Not Present") 

1859 client.generate_credential_report() 

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

1861 report = client.get_credential_report() 

1862 return report['Content'] 

1863 

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

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

1866 self.matcher_config = dict(self.data) 

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

1868 return [] 

1869 

1870 def match(self, resource, info): 

1871 if info is None: 

1872 return False 

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

1874 if '.' not in k: 

1875 vf = ValueFilter(self.data) 

1876 vf.annotate = False 

1877 return vf(info) 

1878 

1879 # access key matching 

1880 prefix, sk = k.split('.', 1) 

1881 vf = ValueFilter(self.matcher_config) 

1882 vf.annotate = False 

1883 

1884 # annotation merging with previous respecting block operators 

1885 k_matched = [] 

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

1887 if vf.match(v): 

1888 k_matched.append(v) 

1889 

1890 for k in k_matched: 

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

1892 

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

1894 return bool(k_matched) 

1895 

1896 

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

1898class UserCredentialReport(CredentialReport): 

1899 

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

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

1902 report = self.get_credential_report() 

1903 if report is None: 

1904 return [] 

1905 results = [] 

1906 for r in resources: 

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

1908 if self.match(r, info): 

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

1910 results.append(r) 

1911 return results 

1912 

1913 

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

1915class IamUserInlinePolicy(Filter): 

1916 """ 

1917 Filter IAM users that have an inline-policy attached 

1918 

1919 True: Filter users that have an inline-policy 

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

1921 """ 

1922 

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

1924 permissions = ('iam:ListUserPolicies',) 

1925 

1926 def _inline_policies(self, client, resource): 

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

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

1929 return resource 

1930 

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

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

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

1934 res = [] 

1935 for r in resources: 

1936 r = self._inline_policies(c, r) 

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

1938 res.append(r) 

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

1940 res.append(r) 

1941 return res 

1942 

1943 

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

1945class UserPolicy(ValueFilter): 

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

1947 

1948 :example: 

1949 

1950 .. code-block:: yaml 

1951 

1952 policies: 

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

1954 resource: iam-user 

1955 filters: 

1956 - type: policy 

1957 key: PolicyName 

1958 value: AdministratorAccess 

1959 include-via: true 

1960 """ 

1961 

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

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

1964 schema_alias = False 

1965 permissions = ( 

1966 'iam:ListAttachedUserPolicies', 

1967 'iam:ListGroupsForUser', 

1968 'iam:ListAttachedGroupPolicies', 

1969 ) 

1970 

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

1972 for u in user_set: 

1973 if search_key in u: 

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

1975 if searched is not None: 

1976 return searched 

1977 

1978 return None 

1979 

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

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

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

1983 

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

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

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

1987 ug['AttachedPolicies'] = ug_searched['AttachedPolicies'] 

1988 else: 

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

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

1991 

1992 for ap in ug['AttachedPolicies']: 

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

1994 if not p_searched: 

1995 p_searched = self.find_in_user_set( 

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

1997 ) 

1998 if p_searched: 

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

2000 else: 

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

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

2003 

2004 return u 

2005 

2006 def user_policies(self, user_set): 

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

2008 for u in user_set: 

2009 if 'c7n:Policies' not in u: 

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

2011 aps = client.list_attached_user_policies( 

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

2013 for ap in aps: 

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

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

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

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

2018 

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

2020 user_set = chunks(resources, size=50) 

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

2022 self.log.debug( 

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

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

2025 

2026 matched = [] 

2027 for r in resources: 

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

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

2030 matched.append(r) 

2031 return matched 

2032 

2033 

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

2035class GroupMembership(ValueFilter): 

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

2037 

2038 :example: 

2039 

2040 .. code-block:: yaml 

2041 

2042 policies: 

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

2044 resource: iam-user 

2045 filters: 

2046 - type: group 

2047 key: GroupName 

2048 value: Admins 

2049 """ 

2050 

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

2052 schema_alias = False 

2053 permissions = ('iam:ListGroupsForUser',) 

2054 

2055 def get_user_groups(self, client, user_set): 

2056 for u in user_set: 

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

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

2059 

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

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

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

2063 futures = [] 

2064 for user_set in chunks( 

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

2066 futures.append( 

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

2068 for f in as_completed(futures): 

2069 pass 

2070 

2071 matched = [] 

2072 for r in resources: 

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

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

2075 matched.append(r) 

2076 return matched 

2077 

2078 

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

2080class UserAccessKey(ValueFilter): 

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

2082 

2083 By default multiple uses of this filter will match 

2084 on any user key satisfying either filter. To find 

2085 specific keys that match multiple access-key filters, 

2086 use `match-operator: and` 

2087 

2088 :example: 

2089 

2090 .. code-block:: yaml 

2091 

2092 policies: 

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

2094 resource: iam-user 

2095 filters: 

2096 - type: access-key 

2097 key: Status 

2098 value: Active 

2099 - type: access-key 

2100 match-operator: and 

2101 key: CreateDate 

2102 value_type: age 

2103 value: 90 

2104 """ 

2105 

2106 schema = type_schema( 

2107 'access-key', 

2108 rinherit=ValueFilter.schema, 

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

2110 schema_alias = False 

2111 permissions = ('iam:ListAccessKeys',) 

2112 annotation_key = 'c7n:AccessKeys' 

2113 matched_annotation_key = 'c7n:matched-keys' 

2114 annotate = False 

2115 

2116 def get_user_keys(self, client, user_set): 

2117 for u in user_set: 

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

2119 client.list_access_keys, 

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

2121 

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

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

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

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

2126 self.log.debug( 

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

2128 list(w.map( 

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

2130 chunks(augment_set, 50))) 

2131 

2132 matched = [] 

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

2134 for r in resources: 

2135 keys = r[self.annotation_key] 

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

2137 keys = r[self.matched_annotation_key] 

2138 k_matched = [] 

2139 for k in keys: 

2140 if self.match(k): 

2141 k_matched.append(k) 

2142 for k in k_matched: 

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

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

2145 if k_matched: 

2146 matched.append(r) 

2147 return matched 

2148 

2149 

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

2151class UserSSHKeyFilter(ValueFilter): 

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

2153 

2154 :example: 

2155 

2156 .. code-block:: yaml 

2157 

2158 policies: 

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

2160 resource: iam-user 

2161 filters: 

2162 - type: ssh-key 

2163 key: Status 

2164 value: Active 

2165 - type: ssh-key 

2166 key: UploadDate 

2167 value_type: age 

2168 value: 90 

2169 """ 

2170 

2171 schema = type_schema( 

2172 'ssh-key', 

2173 rinherit=ValueFilter.schema) 

2174 schema_alias = False 

2175 permissions = ('iam:ListSSHPublicKeys',) 

2176 annotation_key = 'c7n:SSHKeys' 

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

2178 annotate = False 

2179 

2180 def get_user_ssh_keys(self, client, user_set): 

2181 for u in user_set: 

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

2183 client.list_ssh_public_keys, 

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

2185 

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

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

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

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

2190 self.log.debug( 

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

2192 list(w.map( 

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

2194 chunks(augment_set, 50))) 

2195 

2196 matched = [] 

2197 for r in resources: 

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

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

2200 if matched_keys: 

2201 matched.append(r) 

2202 return matched 

2203 

2204 

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

2206class UserLoginProfile(ValueFilter): 

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

2208 

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

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

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

2212 

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

2214 

2215 :example: 

2216 

2217 .. code-block: yaml 

2218 

2219 policies: 

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

2221 resource: iam-user 

2222 filters: 

2223 - type: login-profile 

2224 """ 

2225 

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

2227 permissions = ('iam:GetLoginProfile',) 

2228 annotation_key = 'c7n:LoginProfile' 

2229 

2230 def user_login_profiles(self, user_set): 

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

2232 for u in user_set: 

2233 u[self.annotation_key] = False 

2234 try: 

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

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

2237 u[self.annotation_key] = True 

2238 except ClientError as e: 

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

2240 raise 

2241 

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

2243 user_set = chunks(resources, size=50) 

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

2245 self.log.debug( 

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

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

2248 

2249 matched = [] 

2250 for r in resources: 

2251 if r[self.annotation_key]: 

2252 matched.append(r) 

2253 return matched 

2254 

2255 

2256# Mfa-device filter for iam-users 

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

2258class UserMfaDevice(ValueFilter): 

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

2260 

2261 :example: 

2262 

2263 .. code-block:: yaml 

2264 

2265 policies: 

2266 - name: mfa-enabled-users 

2267 resource: iam-user 

2268 filters: 

2269 - type: mfa-device 

2270 key: UserName 

2271 value: not-null 

2272 """ 

2273 

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

2275 schema_alias = False 

2276 permissions = ('iam:ListMFADevices',) 

2277 

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

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

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

2281 

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

2283 

2284 def _user_mfa_devices(resource): 

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

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

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

2288 

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

2290 query_resources = [ 

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

2292 self.log.debug( 

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

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

2295 

2296 matched = [] 

2297 for r in resources: 

2298 if self.match(r): 

2299 matched.append(r) 

2300 

2301 return matched 

2302 

2303 

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

2305class UserFinding(OtherResourcePostFinding): 

2306 

2307 def format_resource(self, r): 

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

2309 details = { 

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

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

2312 ), 

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

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

2315 } 

2316 accesskey = { 

2317 "Type": "AwsIamAccessKey", 

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

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

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

2321 } 

2322 return filter_empty(accesskey) 

2323 else: 

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

2325 

2326 

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

2328class UserDelete(BaseAction): 

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

2330 

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

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

2333 

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

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

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

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

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

2339 

2340 :example: 

2341 

2342 .. code-block:: yaml 

2343 

2344 # using a 'credential' filter' 

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

2346 resource: iam-user 

2347 filters: 

2348 - type: credential 

2349 key: user 

2350 op: not-in 

2351 value: 

2352 - valid-user-1 

2353 - valid-user-2 

2354 actions: 

2355 - delete 

2356 

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

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

2359 resource: iam-user 

2360 filters: 

2361 - type: value 

2362 key: UserName 

2363 op: not-in 

2364 value: 

2365 - valid-user-1 

2366 - valid-user-2 

2367 actions: 

2368 - delete 

2369 

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

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

2372 resource: iam-user 

2373 filters: 

2374 - type: value 

2375 key: Arn 

2376 op: not-in 

2377 value: 

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

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

2380 actions: 

2381 - delete 

2382 

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

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

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

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

2387 

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

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

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

2391 

2392 :example: 

2393 

2394 .. code-block:: yaml 

2395 

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

2397 comment: | 

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

2399 the username is included in whitelist 

2400 resource: iam-user 

2401 filters: 

2402 - type: value 

2403 key: UserName 

2404 op: not-in 

2405 value: 

2406 - valid-user-1 

2407 - valid-user-2 

2408 - type: credential 

2409 key: password_enabled 

2410 value: true 

2411 actions: 

2412 - type: delete 

2413 options: 

2414 - console-access 

2415 

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

2417 comment: | 

2418 deletes multiple options from test_user 

2419 resource: iam-user 

2420 filters: 

2421 - UserName: test_user 

2422 actions: 

2423 - type: delete 

2424 options: 

2425 - mfa-devices 

2426 - access-keys 

2427 - ssh-keys 

2428 """ 

2429 

2430 ORDERED_OPTIONS = OrderedDict([ 

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

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

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

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

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

2436 ('groups', 'delete_groups'), 

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

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

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

2440 ]) 

2441 COMPOUND_OPTIONS = { 

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

2443 } 

2444 

2445 schema = type_schema( 

2446 'delete', 

2447 options={ 

2448 'type': 'array', 

2449 'items': { 

2450 'type': 'string', 

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

2452 } 

2453 }) 

2454 

2455 permissions = ( 

2456 'iam:ListAttachedUserPolicies', 

2457 'iam:ListAccessKeys', 

2458 'iam:ListGroupsForUser', 

2459 'iam:ListMFADevices', 

2460 'iam:ListServiceSpecificCredentials', 

2461 'iam:ListSigningCertificates', 

2462 'iam:ListSSHPublicKeys', 

2463 'iam:DeactivateMFADevice', 

2464 'iam:DeleteAccessKey', 

2465 'iam:DeleteLoginProfile', 

2466 'iam:DeleteSigningCertificate', 

2467 'iam:DeleteSSHPublicKey', 

2468 'iam:DeleteUser', 

2469 'iam:DeleteUserPolicy', 

2470 'iam:DetachUserPolicy', 

2471 'iam:RemoveUserFromGroup') 

2472 

2473 @staticmethod 

2474 def delete_console_access(client, r): 

2475 try: 

2476 client.delete_login_profile( 

2477 UserName=r['UserName']) 

2478 except ClientError as e: 

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

2480 raise 

2481 

2482 @staticmethod 

2483 def delete_access_keys(client, r): 

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

2485 for access_key in response['AccessKeyMetadata']: 

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

2487 AccessKeyId=access_key['AccessKeyId']) 

2488 

2489 @staticmethod 

2490 def delete_attached_user_policies(client, r): 

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

2492 for user_policy in response['AttachedPolicies']: 

2493 client.detach_user_policy( 

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

2495 

2496 @staticmethod 

2497 def delete_inline_user_policies(client, r): 

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

2499 for user_policy_name in response['PolicyNames']: 

2500 client.delete_user_policy( 

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

2502 

2503 @staticmethod 

2504 def delete_hw_mfa_devices(client, r): 

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

2506 for mfa_device in response['MFADevices']: 

2507 client.deactivate_mfa_device( 

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

2509 

2510 @staticmethod 

2511 def delete_groups(client, r): 

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

2513 for user_group in response['Groups']: 

2514 client.remove_user_from_group( 

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

2516 

2517 @staticmethod 

2518 def delete_ssh_keys(client, r): 

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

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

2521 client.delete_ssh_public_key( 

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

2523 

2524 @staticmethod 

2525 def delete_signing_certificates(client, r): 

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

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

2528 client.delete_signing_certificate( 

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

2530 

2531 @staticmethod 

2532 def delete_service_specific_credentials(client, r): 

2533 # Service specific user credentials (codecommit) 

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

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

2536 client.delete_service_specific_credential( 

2537 UserName=r['UserName'], 

2538 ServiceSpecificCredentialId=screds['ServiceSpecificCredentialId']) 

2539 

2540 @staticmethod 

2541 def delete_user(client, r): 

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

2543 

2544 def process(self, resources): 

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

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

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

2548 for r in resources: 

2549 self.process_user(client, r) 

2550 

2551 def process_user(self, client, r): 

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

2553 # resolve compound options 

2554 for cmd in self.COMPOUND_OPTIONS: 

2555 if cmd in user_options: 

2556 user_options += self.COMPOUND_OPTIONS[cmd] 

2557 # process options in ordered fashion 

2558 for cmd in self.ORDERED_OPTIONS: 

2559 if cmd in user_options: 

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

2561 op(client, r) 

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

2563 self.delete_user(client, r) 

2564 

2565 

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

2567class UserRemoveAccessKey(BaseAction): 

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

2569 

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

2571 delete them after 180 days of nonuse: 

2572 

2573 :example: 

2574 

2575 .. code-block:: yaml 

2576 

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

2578 resource: iam-user 

2579 actions: 

2580 - type: remove-keys 

2581 disable: true 

2582 age: 90 

2583 - type: remove-keys 

2584 age: 180 

2585 """ 

2586 

2587 schema = type_schema( 

2588 'remove-keys', 

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

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

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

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

2593 'iam:DeleteAccessKey') 

2594 

2595 def validate(self): 

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

2597 raise PolicyValidationError( 

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

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

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

2601 raise PolicyValidationError( 

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

2603 return self 

2604 

2605 def process(self, resources): 

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

2607 

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

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

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

2611 

2612 if age: 

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

2614 

2615 for r in resources: 

2616 if 'c7n:AccessKeys' not in r: 

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

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

2619 

2620 keys = r['c7n:AccessKeys'] 

2621 if matched: 

2622 m_keys = resolve_credential_keys( 

2623 r.get(CredentialReport.matched_annotation_key), 

2624 keys) 

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

2626 # without having any single key match them all. 

2627 if not m_keys: 

2628 continue 

2629 keys = m_keys 

2630 

2631 for k in keys: 

2632 if age: 

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

2634 continue 

2635 if disable: 

2636 client.update_access_key( 

2637 UserName=r['UserName'], 

2638 AccessKeyId=k['AccessKeyId'], 

2639 Status='Inactive') 

2640 else: 

2641 client.delete_access_key( 

2642 UserName=r['UserName'], 

2643 AccessKeyId=k['AccessKeyId']) 

2644 

2645 

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

2647class UserDeleteSSHKey(BaseAction): 

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

2649 

2650 For example to delete keys after 90 days: 

2651 

2652 :example: 

2653 

2654 .. code-block:: yaml 

2655 

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

2657 resource: iam-user 

2658 actions: 

2659 - type: delete-ssh-keys 

2660 """ 

2661 

2662 schema = type_schema( 

2663 'delete-ssh-keys', 

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

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

2666 annotation_key = 'c7n:SSHKeys' 

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

2668 'iam:DeleteSSHPublicKey') 

2669 

2670 def process(self, resources): 

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

2672 

2673 for r in resources: 

2674 if self.annotation_key not in r: 

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

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

2677 

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

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

2680 

2681 for k in keys: 

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

2683 client.update_ssh_public_key( 

2684 UserName=r['UserName'], 

2685 SSHPublicKeyId=k['SSHPublicKeyId'], 

2686 Status='Inactive') 

2687 else: 

2688 client.delete_ssh_public_key( 

2689 UserName=r['UserName'], 

2690 SSHPublicKeyId=k['SSHPublicKeyId']) 

2691 

2692 

2693def resolve_credential_keys(m_keys, keys): 

2694 res = [] 

2695 for k in m_keys: 

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

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

2698 for ak in keys: 

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

2700 ak = dict(ak) 

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

2702 if ak not in res: 

2703 res.append(ak) 

2704 elif k not in res: 

2705 res.append(k) 

2706 return res 

2707 

2708 

2709################# 

2710# IAM Groups # 

2711################# 

2712 

2713 

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

2715class SpecificIamGroupManagedPolicy(Filter): 

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

2717 

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

2719 

2720 :example: 

2721 

2722 .. code-block:: yaml 

2723 

2724 policies: 

2725 - name: iam-groups-have-admin 

2726 resource: iam-group 

2727 filters: 

2728 - type: has-specific-managed-policy 

2729 value: admin-policy 

2730 """ 

2731 

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

2733 permissions = ('iam:ListAttachedGroupPolicies',) 

2734 

2735 def _managed_policies(self, client, resource): 

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

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

2738 

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

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

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

2742 results = [] 

2743 for r in resources: 

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

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

2746 results.append(r) 

2747 return results 

2748 return [] 

2749 

2750 

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

2752class IamGroupUsers(Filter): 

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

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

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

2756 

2757 :example: 

2758 

2759 .. code-block:: yaml 

2760 

2761 - name: empty-iam-group 

2762 resource: iam-group 

2763 filters: 

2764 - type: has-users 

2765 value: False 

2766 """ 

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

2768 permissions = ('iam:GetGroup',) 

2769 

2770 def _user_count(self, client, resource): 

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

2772 

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

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

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

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

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

2778 

2779 

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

2781class IamGroupInlinePolicy(Filter): 

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

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

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

2785 

2786 :example: 

2787 

2788 .. code-block:: yaml 

2789 

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

2791 resource: iam-group 

2792 filters: 

2793 - type: has-inline-policy 

2794 value: True 

2795 """ 

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

2797 permissions = ('iam:ListGroupPolicies',) 

2798 

2799 def _inline_policies(self, client, resource): 

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

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

2802 return resource 

2803 

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

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

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

2807 res = [] 

2808 for r in resources: 

2809 r = self._inline_policies(c, r) 

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

2811 res.append(r) 

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

2813 res.append(r) 

2814 return res 

2815 

2816 

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

2818class GroupInlinePolicyDelete(BaseAction): 

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

2820 

2821 :example: 

2822 

2823 .. code-block:: yaml 

2824 

2825 - name: iam-delete-group-policies 

2826 resource: aws.iam-group 

2827 filters: 

2828 - type: value 

2829 key: GroupName 

2830 value: test 

2831 actions: 

2832 - type: delete-inline-policies 

2833 """ 

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

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

2836 

2837 def process(self, resources): 

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

2839 for r in resources: 

2840 self.process_group(client, r) 

2841 

2842 def process_group(self, client, r): 

2843 if 'c7n:InlinePolicies' not in r: 

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

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

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

2847 try: 

2848 self.manager.retry(client.delete_group_policy, 

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

2850 except client.exceptions.NoSuchEntityException: 

2851 continue 

2852 

2853 

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

2855class UserGroupDelete(BaseAction): 

2856 """Delete an IAM User Group. 

2857 

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

2859 

2860 :example: 

2861 

2862 .. code-block:: yaml 

2863 

2864 - name: iam-delete-user-group 

2865 resource: aws.iam-group 

2866 filters: 

2867 - type: value 

2868 key: GroupName 

2869 value: test 

2870 actions: 

2871 - type: delete 

2872 force: True 

2873 """ 

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

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

2876 

2877 def process(self, resources): 

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

2879 for r in resources: 

2880 self.process_group(client, r) 

2881 

2882 def process_group(self, client, r): 

2883 error = None 

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

2885 if force: 

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

2887 for user in users: 

2888 client.remove_user_from_group( 

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

2890 

2891 try: 

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

2893 except client.exceptions.DeleteConflictException as e: 

2894 self.log.warning( 

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

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

2897 % r['Arn']) 

2898 error = e 

2899 except (client.exceptions.NoSuchEntityException, 

2900 client.exceptions.UnmodifiableEntityException): 

2901 pass 

2902 if error: 

2903 raise error 

2904 

2905 

2906class SamlProviderDescribe(DescribeSource): 

2907 

2908 def augment(self, resources): 

2909 super().augment(resources) 

2910 for r in resources: 

2911 md = r.get('SAMLMetadataDocument') 

2912 if not md: 

2913 continue 

2914 root = sso_metadata(md) 

2915 r['IDPSSODescriptor'] = root['IDPSSODescriptor'] 

2916 return resources 

2917 

2918 def get_permissions(self): 

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

2920 

2921 

2922def sso_metadata(md): 

2923 root = ElementTree.fromstringlist(md) 

2924 d = {} 

2925 _sso_recurse(root, d) 

2926 return d 

2927 

2928 

2929def _sso_recurse(node, d): 

2930 d.update(node.attrib) 

2931 for c in node: 

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

2933 cd = {} 

2934 if k in d: 

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

2936 d[k] = [d[k]] 

2937 d[k].append(cd) 

2938 else: 

2939 d[k] = cd 

2940 _sso_recurse(c, cd) 

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

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

2943 

2944 

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

2946class SamlProvider(QueryResourceManager): 

2947 """SAML SSO Provider 

2948 

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

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

2951 """ 

2952 

2953 class resource_type(TypeInfo): 

2954 

2955 service = 'iam' 

2956 name = id = 'Arn' 

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

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

2959 arn = 'Arn' 

2960 arn_type = 'saml-provider' 

2961 config_type = "AWS::IAM::SAMLProvider" 

2962 global_resource = True 

2963 

2964 source_mapping = {'describe': SamlProviderDescribe} 

2965 

2966 

2967class OpenIdDescribe(DescribeSource): 

2968 

2969 def get_permissions(self): 

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

2971 

2972 

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

2974class OpenIdProvider(QueryResourceManager): 

2975 

2976 class resource_type(TypeInfo): 

2977 

2978 service = 'iam' 

2979 name = id = 'Arn' 

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

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

2982 arn = 'Arn' 

2983 arn_type = 'oidc-provider' 

2984 global_resource = True 

2985 

2986 source_mapping = {'describe': OpenIdDescribe} 

2987 

2988 

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

2990class OpenIdProviderDelete(BaseAction): 

2991 """Delete an OpenID Connect IAM Identity Provider 

2992 

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

2994 

2995 :example: 

2996 

2997 .. code-block:: yaml 

2998 

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

3000 resource: iam-oidc-provider 

3001 filters: 

3002 - type: value 

3003 key: Url 

3004 value: example.com 

3005 actions: 

3006 - type: delete 

3007 

3008 """ 

3009 schema = type_schema('delete') 

3010 permissions = ('iam:DeleteOpenIDConnectProvider',) 

3011 

3012 def process(self, resources): 

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

3014 for provider in resources: 

3015 self.manager.retry( 

3016 client.delete_open_id_connect_provider, 

3017 OpenIDConnectProviderArn=provider['Arn'], 

3018 ignore_err_codes=( 

3019 'NoSuchEntityException', 

3020 'DeleteConflictException', 

3021 ), 

3022 ) 

3023 

3024 

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

3026class SpecificIamProfileManagedPolicy(ValueFilter): 

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

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

3029 as not having the policy. 

3030 

3031 :example: 

3032 

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

3034 

3035 .. code-block:: yaml 

3036 

3037 policies: 

3038 - name: iam-profiles-have-admin 

3039 resource: aws.iam-profile 

3040 filters: 

3041 - type: has-specific-managed-policy 

3042 value: admin-policy 

3043 

3044 :example: 

3045 

3046 Check for instance profile roles with an attached policy matching 

3047 a given list: 

3048 

3049 .. code-block:: yaml 

3050 

3051 policies: 

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

3053 resource: aws.iam-profile 

3054 filters: 

3055 - type: has-specific-managed-policy 

3056 value: 

3057 - AmazonS3FullAccess 

3058 - AWSOrganizationsFullAccess 

3059 

3060 :example: 

3061 

3062 Check for instance profile roles with attached policy names matching 

3063 a pattern: 

3064 

3065 .. code-block:: yaml 

3066 

3067 policies: 

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

3069 resource: aws.iam-profile 

3070 filters: 

3071 - type: has-specific-managed-policy 

3072 op: glob 

3073 value: "*FullAccess" 

3074 

3075 Check for instance profile roles with attached policy ARNs matching 

3076 a pattern: 

3077 

3078 .. code-block:: yaml 

3079 

3080 policies: 

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

3082 resource: aws.iam-profile 

3083 filters: 

3084 - type: has-specific-managed-policy 

3085 key: PolicyArn 

3086 op: regex 

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

3088 """ 

3089 

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

3091 permissions = ('iam:ListAttachedRolePolicies',) 

3092 annotation_key = 'c7n:AttachedPolicies' 

3093 matched_annotation_key = 'c7n:MatchedPolicies' 

3094 schema_alias = False 

3095 

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

3097 # Preserve backward compatibility 

3098 if 'key' not in data: 

3099 data['key'] = 'PolicyName' 

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

3101 

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

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

3104 def get_managed_policies(self, client, prof_set): 

3105 for prof in prof_set: 

3106 prof[self.annotation_key] = [] 

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

3108 prof[self.annotation_key] = [ 

3109 role_policy 

3110 for role_policy 

3111 in client.list_attached_role_policies( 

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

3113 ] 

3114 

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

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

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

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

3119 self.log.debug( 

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

3121 list(w.map( 

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

3123 chunks(augment_set, 50))) 

3124 

3125 matched = [] 

3126 for r in resources: 

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

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

3129 if matched_keys: 

3130 matched.append(r) 

3131 return matched