Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/iam.py: 38%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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
13# Used to parse saml provider metadata configuration.
14from xml.etree import ElementTree # nosec nosemgrep
16from concurrent.futures import as_completed
17from dateutil.tz import tzutc
18from dateutil.parser import parse as parse_date
20from botocore.exceptions import ClientError
22from c7n import deprecated
23from c7n.actions import BaseAction
24from c7n.exceptions import PolicyValidationError
25from c7n.filters import ValueFilter, Filter
26from c7n.filters.multiattr import MultiAttrFilter
27from c7n.filters.iamaccess import CrossAccountAccessFilter
28from c7n.manager import resources
29from c7n.query import (
30 ChildResourceManager,
31 ConfigSource,
32 DescribeSource,
33 QueryResourceManager,
34 TypeInfo,
35)
36from c7n.resolver import ValuesFrom
37from c7n.tags import TagActionFilter, TagDelayedAction, Tag, RemoveTag, universal_augment
38from c7n.utils import (
39 get_partition, local_session, type_schema, chunks, filter_empty, QueryParser,
40 select_keys
41)
43from c7n.resources.aws import Arn
44from c7n.resources.securityhub import OtherResourcePostFinding
47class DescribeGroup(DescribeSource):
49 def get_resources(self, resource_ids, cache=True):
50 """For IAM Groups on events, resource ids are Group Names."""
51 client = local_session(self.manager.session_factory).client('iam')
52 resources = []
53 for rid in resource_ids:
54 try:
55 result = self.manager.retry(client.get_group, GroupName=rid)
56 except client.exceptions.NoSuchEntityException:
57 continue
58 group = result.pop('Group')
59 group['c7n:Users'] = result['Users']
60 resources.append(group)
61 return resources
64@resources.register('iam-group')
65class Group(QueryResourceManager):
67 class resource_type(TypeInfo):
68 service = 'iam'
69 arn_type = 'group'
70 enum_spec = ('list_groups', 'Groups', None)
71 id = name = 'GroupName'
72 date = 'CreateDate'
73 cfn_type = config_type = "AWS::IAM::Group"
74 # Denotes this resource type exists across regions
75 global_resource = True
76 arn = 'Arn'
78 source_mapping = {
79 'describe': DescribeGroup,
80 'config': ConfigSource
81 }
84class DescribeRole(DescribeSource):
86 def get_resources(self, resource_ids, cache=True):
87 client = local_session(self.manager.session_factory).client('iam')
88 resources = []
89 for rid in resource_ids:
90 if rid.startswith('arn:'):
91 rid = Arn.parse(rid).resource
92 try:
93 result = self.manager.retry(client.get_role, RoleName=rid)
94 except client.exceptions.NoSuchEntityException:
95 continue
96 resources.append(result.pop('Role'))
97 return resources
100@resources.register('iam-role')
101class Role(QueryResourceManager):
103 class resource_type(TypeInfo):
104 service = 'iam'
105 arn_type = 'role'
106 enum_spec = ('list_roles', 'Roles', None)
107 detail_spec = ('get_role', 'RoleName', 'RoleName', 'Role')
108 id = name = 'RoleName'
109 config_id = 'RoleId'
110 date = 'CreateDate'
111 cfn_type = config_type = "AWS::IAM::Role"
112 # Denotes this resource type exists across regions
113 global_resource = True
114 arn = 'Arn'
116 source_mapping = {
117 'describe': DescribeRole,
118 'config': ConfigSource
119 }
122Role.action_registry.register('mark-for-op', TagDelayedAction)
123Role.filter_registry.register('marked-for-op', TagActionFilter)
126@Role.action_registry.register('post-finding')
127class RolePostFinding(OtherResourcePostFinding):
129 resource_type = 'AwsIamRole'
131 def format_resource(self, r):
132 envelope, payload = self.format_envelope(r)
133 payload.update(self.filter_empty(
134 select_keys(r, ['AssumeRolePolicyDocument', 'CreateDate',
135 'MaxSessionDuration', 'Path', 'RoleId',
136 'RoleName'])))
137 payload['AssumeRolePolicyDocument'] = json.dumps(
138 payload['AssumeRolePolicyDocument'])
139 payload['CreateDate'] = payload['CreateDate'].isoformat()
140 return envelope
143@Role.action_registry.register('tag')
144class RoleTag(Tag):
145 """Tag an iam role."""
147 permissions = ('iam:TagRole',)
149 def process_resource_set(self, client, roles, tags):
150 for role in roles:
151 try:
152 self.manager.retry(
153 client.tag_role, RoleName=role['RoleName'], Tags=tags)
154 except client.exceptions.NoSuchEntityException:
155 continue
158@Role.action_registry.register('remove-tag')
159class RoleRemoveTag(RemoveTag):
160 """Remove tags from an iam role."""
162 permissions = ('iam:UntagRole',)
164 def process_resource_set(self, client, roles, tags):
165 for role in roles:
166 try:
167 self.manager.retry(
168 client.untag_role, RoleName=role['RoleName'], TagKeys=tags)
169 except client.exceptions.NoSuchEntityException:
170 continue
173class SetBoundary(BaseAction):
174 """Set IAM Permission boundary on an IAM Role or User.
176 A role or user can only have a single permission boundary set.
177 """
179 schema = type_schema(
180 'set-boundary',
181 state={'enum': ['present', 'absent']},
182 policy={'type': 'string'})
184 def validate(self):
185 state = self.data.get('state', 'present') == 'present'
186 if state and not self.data.get('policy'):
187 raise PolicyValidationError("set-boundary requires policy arn")
189 def process(self, resources):
190 state = self.data.get('state', 'present') == 'present'
191 client = self.manager.session_factory().client('iam')
192 policy = self.data.get('policy')
193 if policy and not policy.startswith('arn'):
194 policy = 'arn:{}:iam::{}:policy/{}'.format(
195 get_partition(self.manager.config.region),
196 self.manager.account_id, policy)
197 for r in resources:
198 method, params = self.get_method(client, state, policy, r)
199 try:
200 self.manager.retry(method, **params)
201 except client.exceptions.NoSuchEntityException:
202 continue
204 def get_method(self, client, state, policy, resource):
205 raise NotImplementedError()
208@Role.action_registry.register('set-boundary')
209class RoleSetBoundary(SetBoundary):
211 def get_permissions(self):
212 if self.data.get('state', True):
213 return ('iam:PutRolePermissionsBoundary',)
214 return ('iam:DeleteRolePermissionsBoundary',)
216 def get_method(self, client, state, policy, resource):
217 if state:
218 return client.put_role_permissions_boundary, {
219 'RoleName': resource['RoleName'],
220 'PermissionsBoundary': policy}
221 else:
222 return client.delete_role_permissions_boundary, {
223 'RoleName': resource['RoleName']}
226class DescribeUser(DescribeSource):
228 def augment(self, resources):
229 # iam has a race condition, where listing will potentially return a
230 # new user prior it to its availability to get user
231 client = local_session(self.manager.session_factory).client('iam')
232 results = []
233 for r in resources:
234 ru = self.manager.retry(
235 client.get_user, UserName=r['UserName'],
236 ignore_err_codes=client.exceptions.NoSuchEntityException)
237 if ru:
238 results.append(ru['User'])
239 return list(filter(None, results))
241 def get_resources(self, resource_ids, cache=True):
242 client = local_session(self.manager.session_factory).client('iam')
243 results = []
245 for r in resource_ids:
246 try:
247 results.append(client.get_user(UserName=r)['User'])
248 except client.exceptions.NoSuchEntityException:
249 continue
250 return results
253@resources.register('iam-user')
254class User(QueryResourceManager):
256 class resource_type(TypeInfo):
257 service = 'iam'
258 arn_type = 'user'
259 detail_spec = ('get_user', 'UserName', 'UserName', 'User')
260 enum_spec = ('list_users', 'Users', None)
261 id = name = 'UserName'
262 date = 'CreateDate'
263 cfn_type = config_type = "AWS::IAM::User"
264 # Denotes this resource type exists across regions
265 global_resource = True
266 arn = 'Arn'
267 config_id = 'UserId'
269 source_mapping = {
270 'describe': DescribeUser,
271 'config': ConfigSource
272 }
275@User.action_registry.register('tag')
276class UserTag(Tag):
277 """Tag an iam user."""
279 permissions = ('iam:TagUser',)
281 def process_resource_set(self, client, users, tags):
282 for u in users:
283 try:
284 self.manager.retry(
285 client.tag_user, UserName=u['UserName'], Tags=tags)
286 except client.exceptions.NoSuchEntityException:
287 continue
290@User.action_registry.register('remove-tag')
291class UserRemoveTag(RemoveTag):
292 """Remove tags from an iam user."""
294 permissions = ('iam:UntagUser',)
296 def process_resource_set(self, client, users, tags):
297 for u in users:
298 try:
299 self.manager.retry(
300 client.untag_user, UserName=u['UserName'], TagKeys=tags)
301 except client.exceptions.NoSuchEntityException:
302 continue
305User.action_registry.register('mark-for-op', TagDelayedAction)
306User.filter_registry.register('marked-for-op', TagActionFilter)
309Role.action_registry.register('mark-for-op', TagDelayedAction)
310Role.filter_registry.register('marked-for-op', TagActionFilter)
313@User.action_registry.register('set-groups')
314class SetGroups(BaseAction):
315 """Set a specific IAM user as added/removed from a group
317 :example:
319 .. code-block:: yaml
321 - name: iam-user-add-remove
322 resource: iam-user
323 filters:
324 - type: value
325 key: UserName
326 value: Bob
327 actions:
328 - type: set-groups
329 state: remove
330 group: Admin
332 """
333 schema = type_schema(
334 'set-groups',
335 state={'enum': ['add', 'remove']},
336 group={'type': 'string'},
337 required=['state', 'group']
338 )
340 permissions = ('iam:AddUserToGroup', 'iam:RemoveUserFromGroup',)
342 def validate(self):
343 if self.data.get('group') == '':
344 raise PolicyValidationError('group cannot be empty on %s'
345 % (self.manager.data))
347 def process(self, resources):
348 group_name = self.data['group']
349 state = self.data['state']
350 client = local_session(self.manager.session_factory).client('iam')
351 op_map = {
352 'add': client.add_user_to_group,
353 'remove': client.remove_user_from_group
354 }
355 for r in resources:
356 try:
357 op_map[state](GroupName=group_name, UserName=r['UserName'])
358 except client.exceptions.NoSuchEntityException:
359 continue
362@User.action_registry.register('set-boundary')
363class UserSetBoundary(SetBoundary):
365 def get_permissions(self):
366 if self.data.get('state', True):
367 return ('iam:PutUserPermissionsBoundary',)
368 return ('iam:DeleteUserPermissionsBoundary',)
370 def get_method(self, client, state, policy, resource):
371 if state:
372 return client.put_user_permissions_boundary, {
373 'UserName': resource['UserName'],
374 'PermissionsBoundary': policy}
375 else:
376 return client.delete_user_permissions_boundary, {
377 'UserName': resource['UserName']}
380class DescribePolicy(DescribeSource):
382 def resources(self, query=None):
383 queries = PolicyQueryParser.parse(self.manager.data.get('query', []))
384 query = query or {}
385 for q in queries:
386 query.update(q)
387 if 'Scope' not in query:
388 query['Scope'] = 'Local'
389 return super(DescribePolicy, self).resources(query=query)
391 def get_resources(self, resource_ids, cache=True):
392 client = local_session(self.manager.session_factory).client('iam')
393 results = []
395 for r in resource_ids:
396 try:
397 results.append(client.get_policy(PolicyArn=r)['Policy'])
398 except ClientError as e:
399 if e.response['Error']['Code'] == 'NoSuchEntityException':
400 continue
401 return results
403 def augment(self, resources):
404 return universal_augment(self.manager, super().augment(resources))
407@resources.register('iam-policy')
408class Policy(QueryResourceManager):
410 class resource_type(TypeInfo):
411 service = 'iam'
412 arn_type = 'policy'
413 enum_spec = ('list_policies', 'Policies', None)
414 id = 'PolicyId'
415 name = 'PolicyName'
416 date = 'CreateDate'
417 cfn_type = config_type = "AWS::IAM::Policy"
418 # Denotes this resource type exists across regions
419 global_resource = True
420 arn = 'Arn'
421 universal_taggable = object()
423 source_mapping = {
424 'describe': DescribePolicy,
425 'config': ConfigSource
426 }
429class PolicyQueryParser(QueryParser):
431 QuerySchema = {
432 'Scope': ('All', 'AWS', 'Local'),
433 'PolicyUsageFilter': ('PermissionsPolicy', 'PermissionsBoundary'),
434 'PathPrefix': str,
435 'OnlyAttached': bool,
436 'MaxItems': int,
437 }
438 multi_value = False
439 type_name = 'IAM Policy'
442@resources.register('iam-profile')
443class InstanceProfile(QueryResourceManager):
445 class resource_type(TypeInfo):
446 service = 'iam'
447 arn_type = 'instance-profile'
448 enum_spec = ('list_instance_profiles', 'InstanceProfiles', None)
449 name = id = 'InstanceProfileName'
450 date = 'CreateDate'
451 # Denotes this resource type exists across regions
452 global_resource = True
453 arn = 'Arn'
454 cfn_type = 'AWS::IAM::InstanceProfile'
457@resources.register('iam-certificate')
458class ServerCertificate(QueryResourceManager):
460 class resource_type(TypeInfo):
461 service = 'iam'
462 arn_type = 'server-certificate'
463 enum_spec = ('list_server_certificates',
464 'ServerCertificateMetadataList',
465 None)
466 name = id = 'ServerCertificateName'
467 config_type = "AWS::IAM::ServerCertificate"
468 name = 'ServerCertificateName'
469 date = 'Expiration'
470 # Denotes this resource type exists across regions
471 global_resource = True
474@ServerCertificate.action_registry.register('delete')
475class CertificateDelete(BaseAction):
476 """Delete an IAM Certificate
478 For example, if you want to automatically delete an unused IAM certificate.
480 :example:
482 .. code-block:: yaml
484 - name: aws-iam-certificate-delete-expired
485 resource: iam-certificate
486 filters:
487 - type: value
488 key: Expiration
489 value_type: expiration
490 op: greater-than
491 value: 0
492 actions:
493 - type: delete
495 """
496 schema = type_schema('delete')
497 permissions = ('iam:DeleteServerCertificate',)
499 def process(self, resources):
500 client = local_session(self.manager.session_factory).client('iam')
501 for cert in resources:
502 self.manager.retry(
503 client.delete_server_certificate,
504 ServerCertificateName=cert['ServerCertificateName'],
505 ignore_err_codes=(
506 'NoSuchEntityException',
507 'DeleteConflictException',
508 ),
509 )
512@User.filter_registry.register('usage')
513@Role.filter_registry.register('usage')
514@Group.filter_registry.register('usage')
515@Policy.filter_registry.register('usage')
516class ServiceUsage(Filter):
517 """Filter iam resources by their api/service usage.
519 Note recent activity (last 4hrs) may not be shown, evaluation
520 is against the last 365 days of data.
522 Each service access record is evaluated against all specified
523 attributes. Attribute filters can be specified in short form k:v
524 pairs or in long form as a value type filter.
526 match-operator allows to specify how a resource is treated across
527 service access record matches. 'any' means a single matching
528 service record will return the policy resource as matching. 'all'
529 means all service access records have to match.
532 Find iam users that have not used any services in the last year
534 :example:
536 .. code-block:: yaml
538 - name: usage-unused-users
539 resource: iam-user
540 filters:
541 - type: usage
542 match-operator: all
543 LastAuthenticated: null
545 Find iam users that have used dynamodb in last 30 days
547 :example:
549 .. code-block:: yaml
551 - name: unused-users
552 resource: iam-user
553 filters:
554 - type: usage
555 ServiceNamespace: dynamodb
556 TotalAuthenticatedEntities: 1
557 LastAuthenticated:
558 type: value
559 value_type: age
560 op: less-than
561 value: 30
562 match-operator: any
564 https://aws.amazon.com/blogs/security/automate-analyzing-permissions-using-iam-access-advisor/
566 """
568 JOB_COMPLETE = 'COMPLETED'
569 SERVICE_ATTR = {
570 'ServiceName', 'ServiceNamespace', 'TotalAuthenticatedEntities',
571 'LastAuthenticated', 'LastAuthenticatedEntity'}
573 schema_alias = True
574 schema_attr = {
575 sa: {'oneOf': [
576 {'type': 'string'},
577 {'type': 'boolean'},
578 {'type': 'number'},
579 {'type': 'null'},
580 {'$ref': '#/definitions/filters/value'}]}
581 for sa in sorted(SERVICE_ATTR)}
582 schema_attr['match-operator'] = {'enum': ['all', 'any']}
583 schema_attr['poll-delay'] = {'type': 'number'}
584 schema = type_schema(
585 'usage',
586 required=('match-operator',),
587 **schema_attr)
588 permissions = ('iam:GenerateServiceLastAccessedDetails',
589 'iam:GetServiceLastAccessedDetails')
591 def process(self, resources, event=None):
592 client = local_session(self.manager.session_factory).client('iam')
594 job_resource_map = {}
595 for arn, r in zip(self.manager.get_arns(resources), resources):
596 try:
597 jid = self.manager.retry(
598 client.generate_service_last_accessed_details,
599 Arn=arn)['JobId']
600 job_resource_map[jid] = r
601 except client.exceptions.NoSuchEntityException:
602 continue
604 conf = dict(self.data)
605 conf.pop('match-operator')
606 saf = MultiAttrFilter(conf)
607 saf.multi_attrs = self.SERVICE_ATTR
609 results = []
610 match_operator = self.data.get('match-operator', 'all')
612 while job_resource_map:
613 job_results_map = {}
614 for jid, r in job_resource_map.items():
615 result = self.manager.retry(
616 client.get_service_last_accessed_details, JobId=jid)
617 if result['JobStatus'] != self.JOB_COMPLETE:
618 continue
619 job_results_map[jid] = result['ServicesLastAccessed']
621 for jid, saf_results in job_results_map.items():
622 r = job_resource_map.pop(jid)
623 saf_matches = saf.process(saf_results)
624 if match_operator == 'all' and len(saf_matches) == len(saf_results):
625 results.append(r)
626 elif match_operator != 'all' and saf_matches:
627 results.append(r)
629 time.sleep(self.data.get('poll-delay', 2))
631 return results
634@User.filter_registry.register('check-permissions')
635@Group.filter_registry.register('check-permissions')
636@Role.filter_registry.register('check-permissions')
637@Policy.filter_registry.register('check-permissions')
638class CheckPermissions(Filter):
639 """Check IAM permissions associated with a resource.
641 :example:
643 Find users that can create other users
645 .. code-block:: yaml
647 policies:
648 - name: super-users
649 resource: aws.iam-user
650 filters:
651 - type: check-permissions
652 match: allowed
653 actions:
654 - iam:CreateUser
656 :example:
658 Find users with access to all services and actions
660 .. code-block:: yaml
662 policies:
663 - name: admin-users
664 resource: aws.iam-user
665 filters:
666 - type: check-permissions
667 match: allowed
668 actions:
669 - '*:*'
671 By default permission boundaries are checked.
672 """
674 schema = type_schema(
675 'check-permissions', **{
676 'match': {'oneOf': [
677 {'enum': ['allowed', 'denied']},
678 {'$ref': '#/definitions/filters/valuekv'},
679 {'$ref': '#/definitions/filters/value'}]},
680 'boundaries': {'type': 'boolean'},
681 'match-operator': {'enum': ['and', 'or']},
682 'actions': {'type': 'array', 'items': {'type': 'string'}},
683 'required': ('actions', 'match')})
684 schema_alias = True
685 policy_annotation = 'c7n:policy'
686 eval_annotation = 'c7n:perm-matches'
688 def validate(self):
689 # This filter relies on IAM policy simulator APIs. From the docs concerning action names:
690 #
691 # "Each operation must include the service identifier, such as iam:CreateUser. This
692 # operation does not support using wildcards (*) in an action name."
693 #
694 # We can catch invalid actions during policy validation, rather than waiting to hit
695 # runtime exceptions.
696 for action in self.data['actions']:
697 if ':' not in action[1:-1]:
698 raise PolicyValidationError(
699 "invalid check-permissions action: '%s' must be in the form <service>:<action>"
700 % (action,))
701 return self
703 def get_permissions(self):
704 if self.manager.type == 'iam-policy':
705 return ('iam:SimulateCustomPolicy', 'iam:GetPolicyVersion')
706 perms = ('iam:SimulatePrincipalPolicy', 'iam:GetPolicy', 'iam:GetPolicyVersion')
707 if self.manager.type not in ('iam-user', 'iam-role',):
708 # for simulating w/ permission boundaries
709 perms += ('iam:GetRole',)
710 return perms
712 def process(self, resources, event=None):
713 client = local_session(self.manager.session_factory).client('iam')
714 actions = self.data['actions']
715 matcher = self.get_eval_matcher()
716 operator = self.data.get('match-operator', 'and') == 'and' and all or any
717 arn_resources = list(zip(self.get_iam_arns(resources), resources))
719 # To ignore permission boundaries, override with an allow-all policy
720 self.simulation_boundary_override = '''
721 {
722 "Version": "2012-10-17",
723 "Statement": [{
724 "Effect": "Allow",
725 "Action": "*",
726 "Resource": "*"
727 }]
728 }
729 ''' if not self.data.get('boundaries', True) else None
730 results = []
731 eval_cache = {}
732 for arn, r in arn_resources:
733 if arn is None:
734 continue
735 if arn in eval_cache:
736 evaluations = eval_cache[arn]
737 else:
738 evaluations = self.get_evaluations(client, arn, r, actions)
739 eval_cache[arn] = evaluations
740 if not evaluations:
741 continue
742 matches = []
743 matched = []
744 for e in evaluations:
745 match = matcher(e)
746 if match:
747 matched.append(e)
748 matches.append(match)
749 if operator(matches):
750 r[self.eval_annotation] = matched
751 results.append(r)
752 return results
754 def get_iam_arns(self, resources):
755 return self.manager.get_arns(resources)
757 def get_evaluations(self, client, arn, r, actions):
758 if self.manager.type == 'iam-policy':
759 policy = r.get(self.policy_annotation)
760 if policy is None:
761 r['c7n:policy'] = policy = client.get_policy_version(
762 PolicyArn=r['Arn'],
763 VersionId=r['DefaultVersionId']).get('PolicyVersion', {})
764 evaluations = self.manager.retry(
765 client.simulate_custom_policy,
766 PolicyInputList=[json.dumps(policy['Document'])],
767 ActionNames=actions).get('EvaluationResults', ())
768 return evaluations
770 params = dict(
771 PolicySourceArn=arn,
772 ActionNames=actions,
773 ignore_err_codes=('NoSuchEntity',))
775 # simulate_principal_policy() respects permission boundaries by default. To opt out of
776 # considering boundaries for this filter, we can provide an allow-all policy
777 # as the boundary.
778 #
779 # Note: Attempting to use an empty list for the boundary seems like a reasonable
780 # impulse too, but that seems to cause the simulator to fall back to its default
781 # of using existing boundaries.
782 if self.simulation_boundary_override:
783 params['PermissionsBoundaryPolicyInputList'] = [self.simulation_boundary_override]
785 evaluations = (self.manager.retry(
786 client.simulate_principal_policy,
787 **params) or {}).get('EvaluationResults', ())
788 return evaluations
790 def get_eval_matcher(self):
791 if isinstance(self.data['match'], str):
792 if self.data['match'] == 'denied':
793 values = ['explicitDeny', 'implicitDeny']
794 else:
795 values = ['allowed']
796 vf = ValueFilter({'type': 'value', 'key':
797 'EvalDecision', 'value': values,
798 'op': 'in'})
799 else:
800 vf = ValueFilter(self.data['match'])
801 vf.annotate = False
802 return vf
805class IamRoleUsage(Filter):
807 def get_permissions(self):
808 perms = list(itertools.chain(*[
809 self.manager.get_resource_manager(m).get_permissions()
810 for m in ['lambda', 'launch-config', 'ec2']]))
811 perms.extend(['ecs:DescribeClusters', 'ecs:DescribeServices'])
812 return perms
814 def service_role_usage(self):
815 results = set()
816 results.update(self.scan_lambda_roles())
817 results.update(self.scan_ecs_roles())
818 results.update(self.collect_profile_roles())
819 return results
821 def instance_profile_usage(self):
822 results = set()
823 results.update(self.scan_asg_roles())
824 results.update(self.scan_ec2_roles())
825 return results
827 def scan_lambda_roles(self):
828 manager = self.manager.get_resource_manager('lambda')
829 return [r['Role'] for r in manager.resources() if 'Role' in r]
831 def scan_ecs_roles(self):
832 results = []
833 client = local_session(self.manager.session_factory).client('ecs')
834 for cluster in client.describe_clusters()['clusters']:
835 services = client.list_services(
836 cluster=cluster['clusterName'])['serviceArns']
837 if services:
838 for service in client.describe_services(
839 cluster=cluster['clusterName'],
840 services=services)['services']:
841 if 'roleArn' in service:
842 results.append(service['roleArn'])
843 return results
845 def collect_profile_roles(self):
846 # Collect iam roles attached to instance profiles of EC2/ASG resources
847 profiles = set()
848 profiles.update(self.scan_asg_roles())
849 profiles.update(self.scan_ec2_roles())
851 manager = self.manager.get_resource_manager('iam-profile')
852 iprofiles = manager.resources()
853 results = []
854 for p in iprofiles:
855 if p['InstanceProfileName'] not in profiles:
856 continue
857 for role in p.get('Roles', []):
858 results.append(role['RoleName'])
859 return results
861 def scan_asg_roles(self):
862 manager = self.manager.get_resource_manager('launch-config')
863 return [r['IamInstanceProfile'] for r in manager.resources() if (
864 'IamInstanceProfile' in r)]
866 def scan_ec2_roles(self):
867 manager = self.manager.get_resource_manager('ec2')
868 results = []
869 for e in manager.resources():
870 # do not include instances that have been recently terminated
871 if e['State']['Name'] == 'terminated':
872 continue
873 profile_arn = e.get('IamInstanceProfile', {}).get('Arn', None)
874 if not profile_arn:
875 continue
876 # split arn to get the profile name
877 results.append(profile_arn.split('/')[-1])
878 return results
881###################
882# IAM Roles #
883###################
885@Role.filter_registry.register('used')
886class UsedIamRole(IamRoleUsage):
887 """Filter IAM roles that are either being used or not
889 Checks for usage on EC2, Lambda, ECS only
891 :example:
893 .. code-block:: yaml
895 policies:
896 - name: iam-role-in-use
897 resource: iam-role
898 filters:
899 - type: used
900 state: true
901 """
903 schema = type_schema(
904 'used',
905 state={'type': 'boolean'})
907 def process(self, resources, event=None):
908 roles = self.service_role_usage()
909 if self.data.get('state', True):
910 return [r for r in resources if (
911 r['Arn'] in roles or r['RoleName'] in roles)]
913 return [r for r in resources if (
914 r['Arn'] not in roles and r['RoleName'] not in roles)]
917@Role.filter_registry.register('unused')
918class UnusedIamRole(IamRoleUsage):
919 """Filter IAM roles that are either being used or not
921 This filter has been deprecated. Please use the 'used' filter
922 with the 'state' attribute to get unused iam roles
924 Checks for usage on EC2, Lambda, ECS only
926 :example:
928 .. code-block:: yaml
930 policies:
931 - name: iam-roles-not-in-use
932 resource: iam-role
933 filters:
934 - type: used
935 state: false
936 """
937 deprecations = (
938 deprecated.filter("use the 'used' filter with 'state' attribute"),
939 )
941 schema = type_schema('unused')
943 def process(self, resources, event=None):
944 return UsedIamRole({'state': False}, self.manager).process(resources)
947@Role.filter_registry.register('cross-account')
948class RoleCrossAccountAccess(CrossAccountAccessFilter):
950 policy_attribute = 'AssumeRolePolicyDocument'
951 permissions = ('iam:ListRoles',)
953 schema = type_schema(
954 'cross-account',
955 # white list accounts
956 whitelist_from=ValuesFrom.schema,
957 whitelist={'type': 'array', 'items': {'type': 'string'}})
960@Role.filter_registry.register('has-inline-policy')
961class IamRoleInlinePolicy(Filter):
962 """Filter IAM roles that have an inline-policy attached
963 True: Filter roles that have an inline-policy
964 False: Filter roles that do not have an inline-policy
966 :example:
968 .. code-block:: yaml
970 policies:
971 - name: iam-roles-with-inline-policies
972 resource: iam-role
973 filters:
974 - type: has-inline-policy
975 value: True
976 """
978 schema = type_schema('has-inline-policy', value={'type': 'boolean'})
979 permissions = ('iam:ListRolePolicies',)
981 def _inline_policies(self, client, resource):
982 policies = client.list_role_policies(
983 RoleName=resource['RoleName'])['PolicyNames']
984 resource['c7n:InlinePolicies'] = policies
985 return resource
987 def process(self, resources, event=None):
988 c = local_session(self.manager.session_factory).client('iam')
989 res = []
990 value = self.data.get('value', True)
991 for r in resources:
992 r = self._inline_policies(c, r)
993 if len(r['c7n:InlinePolicies']) > 0 and value:
994 res.append(r)
995 if len(r['c7n:InlinePolicies']) == 0 and not value:
996 res.append(r)
997 return res
1000@Role.filter_registry.register('has-specific-managed-policy')
1001class SpecificIamRoleManagedPolicy(ValueFilter):
1002 """Find IAM roles that have a specific policy attached
1004 :example:
1006 Check for roles with 'admin-policy' attached:
1008 .. code-block:: yaml
1010 policies:
1011 - name: iam-roles-have-admin
1012 resource: aws.iam-role
1013 filters:
1014 - type: has-specific-managed-policy
1015 value: admin-policy
1017 :example:
1019 Check for roles with an attached policy matching
1020 a given list:
1022 .. code-block:: yaml
1024 policies:
1025 - name: iam-roles-with-selected-policies
1026 resource: aws.iam-role
1027 filters:
1028 - type: has-specific-managed-policy
1029 op: in
1030 value:
1031 - AmazonS3FullAccess
1032 - AWSOrganizationsFullAccess
1034 :example:
1036 Check for roles with attached policy names matching a pattern:
1038 .. code-block:: yaml
1040 policies:
1041 - name: iam-roles-with-full-access-policies
1042 resource: aws.iam-role
1043 filters:
1044 - type: has-specific-managed-policy
1045 op: glob
1046 value: "*FullAccess"
1048 Check for roles with attached policy ARNs matching a pattern:
1050 .. code-block:: yaml
1052 policies:
1053 - name: iam-roles-with-aws-full-access-policies
1054 resource: aws.iam-role
1055 filters:
1056 - type: has-specific-managed-policy
1057 key: PolicyArn
1058 op: regex
1059 value: "arn:aws:iam::aws:policy/.*FullAccess"
1060 """
1062 schema = type_schema('has-specific-managed-policy', rinherit=ValueFilter.schema)
1063 permissions = ('iam:ListAttachedRolePolicies',)
1064 annotation_key = 'c7n:AttachedPolicies'
1065 matched_annotation_key = 'c7n:MatchedPolicies'
1066 schema_alias = False
1068 def __init__(self, data, manager=None):
1069 # Preserve backward compatibility
1070 if 'key' not in data:
1071 data['key'] = 'PolicyName'
1072 super(SpecificIamRoleManagedPolicy, self).__init__(data, manager)
1074 def get_managed_policies(self, client, role_set):
1075 for role in role_set:
1076 role[self.annotation_key] = [
1077 role_policy
1078 for role_policy
1079 in client.list_attached_role_policies(
1080 RoleName=role['RoleName'])['AttachedPolicies']
1081 ]
1083 def process(self, resources, event=None):
1084 client = local_session(self.manager.session_factory).client('iam')
1085 with self.executor_factory(max_workers=2) as w:
1086 augment_set = [r for r in resources if self.annotation_key not in r]
1087 self.log.debug(
1088 "Querying %d roles' attached policies" % len(augment_set))
1089 list(w.map(
1090 functools.partial(self.get_managed_policies, client),
1091 chunks(augment_set, 50)))
1093 matched = []
1094 for r in resources:
1095 matched_keys = [k for k in r[self.annotation_key] if self.match(k)]
1096 self.merge_annotation(r, self.matched_annotation_key, matched_keys)
1097 if matched_keys:
1098 matched.append(r)
1099 return matched
1102@Role.filter_registry.register('no-specific-managed-policy')
1103class NoSpecificIamRoleManagedPolicy(Filter):
1104 """Filter IAM roles that do not have a specific policy attached
1106 For example, if the user wants to check all roles without 'ip-restriction':
1108 :example:
1110 .. code-block:: yaml
1112 policies:
1113 - name: iam-roles-no-ip-restriction
1114 resource: iam-role
1115 filters:
1116 - type: no-specific-managed-policy
1117 value: ip-restriction
1118 """
1120 schema = type_schema('no-specific-managed-policy', value={'type': 'string'})
1121 permissions = ('iam:ListAttachedRolePolicies',)
1123 def _managed_policies(self, client, resource):
1124 return [r['PolicyName'] for r in client.list_attached_role_policies(
1125 RoleName=resource['RoleName'])['AttachedPolicies']]
1127 def process(self, resources, event=None):
1128 c = local_session(self.manager.session_factory).client('iam')
1129 if self.data.get('value'):
1130 return [r for r in resources if self.data.get('value') not in
1131 self._managed_policies(c, r)]
1132 return []
1135@Role.action_registry.register('set-policy')
1136class SetPolicy(BaseAction):
1137 """Set a specific IAM policy as attached or detached on a role.
1139 You will identify the policy by its arn.
1141 Returns a list of roles modified by the action.
1143 For example, if you want to automatically attach a policy to all roles which don't have it...
1145 :example:
1147 .. code-block:: yaml
1149 - name: iam-attach-role-policy
1150 resource: iam-role
1151 filters:
1152 - type: no-specific-managed-policy
1153 value: my-iam-policy
1154 actions:
1155 - type: set-policy
1156 state: detached
1157 arn: "*"
1158 - type: set-policy
1159 state: attached
1160 arn: arn:aws:iam::123456789012:policy/my-iam-policy
1162 """
1163 schema = type_schema(
1164 'set-policy',
1165 state={'enum': ['attached', 'detached']},
1166 arn={'type': 'string'},
1167 required=['state', 'arn'])
1169 permissions = ('iam:AttachRolePolicy', 'iam:DetachRolePolicy', "iam:ListAttachedRolePolicies",)
1171 def validate(self):
1172 if self.data.get('state') == 'attached' and self.data.get('arn') == "*":
1173 raise PolicyValidationError(
1174 '* operator is not supported for state: attached on %s' % (self.manager.data))
1176 def attach_policy(self, client, resource, policy_arn):
1177 client.attach_role_policy(
1178 RoleName=resource['RoleName'],
1179 PolicyArn=policy_arn
1180 )
1182 def detach_policy(self, client, resource, policy_arn):
1183 try:
1184 client.detach_role_policy(
1185 RoleName=resource['RoleName'],
1186 PolicyArn=policy_arn
1187 )
1188 except client.exceptions.NoSuchEntityException:
1189 return
1191 def list_attached_policies(self, client, resource):
1192 attached_policy = client.list_attached_role_policies(RoleName=resource['RoleName'])
1193 policy_arns = [p.get('PolicyArn') for p in attached_policy['AttachedPolicies']]
1194 return policy_arns
1196 def process(self, resources):
1197 client = local_session(self.manager.session_factory).client('iam')
1198 policy_arn = self.data['arn']
1199 if policy_arn != "*" and not policy_arn.startswith('arn'):
1200 policy_arn = 'arn:{}:iam::{}:policy/{}'.format(
1201 get_partition(self.manager.config.region),
1202 self.manager.account_id, policy_arn)
1203 state = self.data['state']
1204 for r in resources:
1205 if state == 'attached':
1206 self.attach_policy(client, r, policy_arn)
1207 elif state == 'detached' and policy_arn != "*":
1208 self.detach_policy(client, r, policy_arn)
1209 elif state == 'detached' and policy_arn == "*":
1210 try:
1211 self.detach_all_policies(client, r)
1212 except client.exceptions.NoSuchEntityException:
1213 continue
1215 def detach_all_policies(self, client, resource):
1216 policy_arns = self.list_attached_policies(client, resource)
1217 for parn in policy_arns:
1218 self.detach_policy(client, resource, parn)
1221@User.action_registry.register("set-policy")
1222class SetUserPolicy(SetPolicy):
1223 """Set a specific IAM policy as attached or detached on a user.
1225 You will identify the policy by its arn.
1227 Returns a list of roles modified by the action.
1229 For example, if you want to automatically attach a single policy while
1230 detaching all exisitng policies:
1232 :example:
1234 .. code-block:: yaml
1236 - name: iam-attach-user-policy
1237 resource: iam-user
1238 filters:
1239 - type: value
1240 key: UserName
1241 op: not-in
1242 value:
1243 - AdminUser1
1244 - AdminUser2
1245 actions:
1246 - type: set-policy
1247 state: detached
1248 arn: arn:aws:iam::aws:policy/AdministratorAccess
1250 """
1252 permissions = (
1253 "iam:AttachUserPolicy", "iam:DetachUserPolicy", "iam:ListAttachedUserPolicies",)
1255 def attach_policy(self, client, resource, policy_arn):
1256 client.attach_user_policy(
1257 UserName=resource["UserName"], PolicyArn=policy_arn)
1259 def detach_policy(self, client, resource, policy_arn):
1260 try:
1261 client.detach_user_policy(
1262 UserName=resource["UserName"], PolicyArn=policy_arn)
1263 except client.exceptions.NoSuchEntityException:
1264 return
1266 def list_attached_policies(self, client, resource):
1267 attached_policies = client.list_attached_user_policies(
1268 UserName=resource["UserName"]
1269 )
1270 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']]
1271 return policy_arns
1274@Group.action_registry.register("set-policy")
1275class SetGroupPolicy(SetPolicy):
1276 """Set a specific IAM policy as attached or detached on a group.
1278 You will identify the policy by its arn.
1280 Returns a list of roles modified by the action.
1282 For example, if you want to automatically attach a single policy while
1283 detaching all exisitng policies:
1285 :example:
1287 .. code-block:: yaml
1289 - name: iam-attach-group-policy
1290 resource: iam-group
1291 actions:
1292 - type: set-policy
1293 state: detached
1294 arn: "*"
1295 - type: set-policy
1296 state: attached
1297 arn: arn:aws:iam::{account_id}:policy/my-iam-policy
1299 """
1301 permissions = (
1302 "iam:AttachGroupPolicy", "iam:DetachGroupPolicy", "iam:ListAttachedGroupPolicies",)
1304 def attach_policy(self, client, resource, policy_arn):
1305 client.attach_group_policy(
1306 GroupName=resource["GroupName"], PolicyArn=policy_arn)
1308 def detach_policy(self, client, resource, policy_arn):
1309 try:
1310 client.detach_group_policy(
1311 GroupName=resource["GroupName"], PolicyArn=policy_arn)
1312 except client.exceptions.NoSuchEntityException:
1313 return
1315 def list_attached_policies(self, client, resource):
1316 attached_policies = client.list_attached_group_policies(
1317 GroupName=resource["GroupName"]
1318 )
1319 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']]
1320 return policy_arns
1323@Role.action_registry.register('delete')
1324class RoleDelete(BaseAction):
1325 """Delete an IAM Role.
1327 To delete IAM Role you must first delete the policies
1328 that are associated with the role. Also, you need to remove
1329 the role from all instance profiles that the role is in.
1331 https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_manage_delete.html
1333 For this case option 'force' is used. If you set it as 'true',
1334 policies that are associated with the role would be detached
1335 (inline policies would be removed) and all instance profiles
1336 the role is in would be removed as well as the role.
1338 For example, if you want to automatically delete an unused IAM role.
1340 :example:
1342 .. code-block:: yaml
1344 - name: iam-delete-unused-role
1345 resource: iam-role
1346 filters:
1347 - type: usage
1348 match-operator: all
1349 LastAuthenticated: null
1350 actions:
1351 - type: delete
1352 force: true
1354 """
1355 schema = type_schema('delete', force={'type': 'boolean'})
1356 permissions = ('iam:DeleteRole', 'iam:DeleteInstanceProfile',)
1358 def detach_inline_policies(self, client, r):
1359 policies = (self.manager.retry(
1360 client.list_role_policies, RoleName=r['RoleName'],
1361 ignore_err_codes=('NoSuchEntityException',)) or {}).get('PolicyNames', ())
1362 for p in policies:
1363 self.manager.retry(
1364 client.delete_role_policy,
1365 RoleName=r['RoleName'], PolicyName=p,
1366 ignore_err_codes=('NoSuchEntityException',))
1368 def delete_instance_profiles(self, client, r):
1369 # An instance profile can contain only one IAM role,
1370 # although a role can be included in multiple instance profiles
1371 profile_names = []
1372 profiles = self.manager.retry(
1373 client.list_instance_profiles_for_role,
1374 RoleName=r['RoleName'],
1375 ignore_err_codes=('NoSuchEntityException',))
1376 if profiles:
1377 profile_names = [p.get('InstanceProfileName') for p in profiles['InstanceProfiles']]
1378 for p in profile_names:
1379 self.manager.retry(
1380 client.remove_role_from_instance_profile,
1381 RoleName=r['RoleName'], InstanceProfileName=p,
1382 ignore_err_codes=('NoSuchEntityException',))
1383 self.manager.retry(
1384 client.delete_instance_profile,
1385 InstanceProfileName=p,
1386 ignore_err_codes=('NoSuchEntityException',))
1388 def process(self, resources):
1389 client = local_session(self.manager.session_factory).client('iam')
1390 error = None
1391 if self.data.get('force', False):
1392 policy_setter = self.manager.action_registry['set-policy'](
1393 {'state': 'detached', 'arn': '*'}, self.manager)
1394 policy_setter.process(resources)
1396 for r in resources:
1397 if self.data.get('force', False):
1398 self.detach_inline_policies(client, r)
1399 self.delete_instance_profiles(client, r)
1400 try:
1401 client.delete_role(RoleName=r['RoleName'])
1402 except client.exceptions.DeleteConflictException as e:
1403 self.log.warning(
1404 ("Role:%s cannot be deleted, set force "
1405 "to detach policy, instance profile and delete, error: %s") % (
1406 r['Arn'], str(e)))
1407 error = e
1408 except (client.exceptions.NoSuchEntityException,
1409 client.exceptions.UnmodifiableEntityException):
1410 continue
1411 if error:
1412 raise error
1415######################
1416# IAM Policies #
1417######################
1420@Policy.filter_registry.register('used')
1421class UsedIamPolicies(Filter):
1422 """Filter IAM policies that are being used
1423 (either attached to some roles or used as a permissions boundary).
1425 :example:
1427 .. code-block:: yaml
1429 policies:
1430 - name: iam-policy-used
1431 resource: iam-policy
1432 filters:
1433 - type: used
1434 """
1436 schema = type_schema('used')
1437 permissions = ('iam:ListPolicies',)
1439 def process(self, resources, event=None):
1440 return [r for r in resources if
1441 r['AttachmentCount'] > 0 or r.get('PermissionsBoundaryUsageCount', 0) > 0]
1444@Policy.filter_registry.register('unused')
1445class UnusedIamPolicies(Filter):
1446 """Filter IAM policies that are not being used
1447 (neither attached to any roles nor used as a permissions boundary).
1449 :example:
1451 .. code-block:: yaml
1453 policies:
1454 - name: iam-policy-unused
1455 resource: iam-policy
1456 filters:
1457 - type: unused
1458 """
1460 schema = type_schema('unused')
1461 permissions = ('iam:ListPolicies',)
1463 def process(self, resources, event=None):
1464 return [r for r in resources if
1465 r['AttachmentCount'] == 0 and r.get('PermissionsBoundaryUsageCount', 0) == 0]
1468@Policy.filter_registry.register('has-allow-all')
1469class AllowAllIamPolicies(Filter):
1470 """Check if IAM policy resource(s) have allow-all IAM policy statement block.
1472 This allows users to implement CIS AWS check 1.24 which states that no
1473 policy must exist with the following requirements.
1475 Policy must have 'Action' and Resource = '*' with 'Effect' = 'Allow'
1477 The policy will trigger on the following IAM policy (statement).
1478 For example:
1480 .. code-block:: json
1482 {
1483 "Version": "2012-10-17",
1484 "Statement": [{
1485 "Action": "*",
1486 "Resource": "*",
1487 "Effect": "Allow"
1488 }]
1489 }
1491 Additionally, the policy checks if the statement has no 'Condition' or
1492 'NotAction'.
1494 For example, if the user wants to check all used policies and filter on
1495 allow all:
1497 .. code-block:: yaml
1499 - name: iam-no-used-all-all-policy
1500 resource: iam-policy
1501 filters:
1502 - type: used
1503 - type: has-allow-all
1505 Note that scanning and getting all policies and all statements can take
1506 a while. Use it sparingly or combine it with filters such as 'used' as
1507 above.
1509 """
1510 schema = type_schema('has-allow-all')
1511 permissions = ('iam:ListPolicies', 'iam:ListPolicyVersions')
1513 def has_allow_all_policy(self, client, resource):
1514 statements = client.get_policy_version(
1515 PolicyArn=resource['Arn'],
1516 VersionId=resource['DefaultVersionId']
1517 )['PolicyVersion']['Document']['Statement']
1518 if isinstance(statements, dict):
1519 statements = [statements]
1521 for s in statements:
1522 if ('Condition' not in s and
1523 'Action' in s and
1524 isinstance(s['Action'], str) and
1525 s['Action'] == "*" and
1526 'Resource' in s and
1527 isinstance(s['Resource'], str) and
1528 s['Resource'] == "*" and
1529 s['Effect'] == "Allow"):
1530 return True
1531 return False
1533 def process(self, resources, event=None):
1534 c = local_session(self.manager.session_factory).client('iam')
1535 results = [r for r in resources if self.has_allow_all_policy(c, r)]
1536 self.log.info(
1537 "%d of %d iam policies have allow all.",
1538 len(results), len(resources))
1539 return results
1542@Policy.action_registry.register('delete')
1543class PolicyDelete(BaseAction):
1544 """Delete an IAM Policy.
1546 For example, if you want to automatically delete all unused IAM policies.
1548 :example:
1550 .. code-block:: yaml
1552 - name: iam-delete-unused-policies
1553 resource: iam-policy
1554 filters:
1555 - type: unused
1556 actions:
1557 - delete
1559 """
1560 schema = type_schema('delete')
1561 permissions = ('iam:DeletePolicy',)
1563 def process(self, resources):
1564 client = local_session(self.manager.session_factory).client('iam')
1566 rcount = len(resources)
1567 resources = [r for r in resources if Arn.parse(r['Arn']).account_id != 'aws']
1568 if len(resources) != rcount:
1569 self.log.warning("Implicitly filtering AWS managed policies: %d -> %d",
1570 rcount, len(resources))
1572 for r in resources:
1573 if r.get('DefaultVersionId', '') != 'v1':
1574 versions = [v['VersionId'] for v in client.list_policy_versions(
1575 PolicyArn=r['Arn']).get('Versions') if not v.get('IsDefaultVersion')]
1576 for v in versions:
1577 client.delete_policy_version(PolicyArn=r['Arn'], VersionId=v)
1578 client.delete_policy(PolicyArn=r['Arn'])
1581###############################
1582# IAM Instance Profiles #
1583###############################
1586@InstanceProfile.filter_registry.register('used')
1587class UsedInstanceProfiles(IamRoleUsage):
1588 """Filter IAM profiles that are being used.
1590 :example:
1592 .. code-block:: yaml
1594 policies:
1595 - name: iam-instance-profiles-in-use
1596 resource: iam-profile
1597 filters:
1598 - type: used
1599 """
1601 schema = type_schema('used')
1603 def process(self, resources, event=None):
1604 results = []
1605 profiles = self.instance_profile_usage()
1606 for r in resources:
1607 if r['Arn'] in profiles or r['InstanceProfileName'] in profiles:
1608 results.append(r)
1609 self.log.info(
1610 "%d of %d instance profiles currently in use." % (
1611 len(results), len(resources)))
1612 return results
1615@InstanceProfile.filter_registry.register('unused')
1616class UnusedInstanceProfiles(IamRoleUsage):
1617 """Filter IAM profiles that are not being used
1619 :example:
1621 .. code-block:: yaml
1623 policies:
1624 - name: iam-instance-profiles-not-in-use
1625 resource: iam-profile
1626 filters:
1627 - type: unused
1628 """
1630 schema = type_schema('unused')
1632 def process(self, resources, event=None):
1633 results = []
1634 profiles = self.instance_profile_usage()
1635 for r in resources:
1636 if (r['Arn'] not in profiles and r['InstanceProfileName'] not in profiles):
1637 results.append(r)
1638 self.log.info(
1639 "%d of %d instance profiles currently not in use." % (
1640 len(results), len(resources)))
1641 return results
1644@InstanceProfile.action_registry.register('set-role')
1645class InstanceProfileSetRole(BaseAction):
1646 """Upserts specified role name for IAM instance profiles.
1647 Instance profile roles are removed when empty role name is specified.
1649 :example:
1651 .. code-block:: yaml
1653 policies:
1654 - name: iam-instance-profile-set-role
1655 resource: iam-profile
1656 actions:
1657 - type: set-role
1658 role: my-test-role
1659 """
1661 schema = type_schema('set-role',
1662 role={'type': 'string'})
1663 permissions = ('iam:AddRoleToInstanceProfile', 'iam:RemoveRoleFromInstanceProfile',)
1665 def add_role(self, client, resource, role):
1666 self.manager.retry(
1667 client.add_role_to_instance_profile,
1668 InstanceProfileName=resource['InstanceProfileName'],
1669 RoleName=role
1670 )
1671 return
1673 def remove_role(self, client, resource):
1674 self.manager.retry(
1675 client.remove_role_from_instance_profile,
1676 InstanceProfileName=resource['InstanceProfileName'],
1677 RoleName=resource['Roles'][0]['RoleName']
1678 )
1679 return
1681 def process(self, resources):
1682 client = local_session(self.manager.session_factory).client('iam')
1683 role = self.data.get('role', '')
1684 for r in resources:
1685 if not role:
1686 if len(r['Roles']) == 0:
1687 continue
1688 else:
1689 self.remove_role(client, r)
1690 else:
1691 if len(r['Roles']) == 0:
1692 self.add_role(client, r, role)
1693 elif role == r['Roles'][0]['RoleName']:
1694 continue
1695 else:
1696 self.remove_role(client, r)
1697 self.add_role(client, r, role)
1700@InstanceProfile.action_registry.register("set-policy")
1701class SetInstanceProfileRolePolicy(SetPolicy):
1702 """Set a specific IAM policy as attached or detached on an instance profile role.
1704 You will identify the policy by its arn.
1706 Returns a list of roles modified by the action.
1708 For example, if you want to automatically attach a single policy while
1709 detaching all exisitng policies:
1711 :example:
1713 .. code-block:: yaml
1715 - name: iam-attach-instance-profile-role-policy
1716 resource: iam-profile
1717 filters:
1718 - not:
1719 - type: has-specific-managed-policy
1720 value: my-iam-policy
1721 actions:
1722 - type: set-policy
1723 state: attached
1724 arn: arn:aws:iam::123456789012:policy/my-iam-policy
1726 """
1728 permissions = ('iam:AttachRolePolicy', 'iam:DetachRolePolicy', "iam:ListAttachedRolePolicies",)
1730 def attach_policy(self, client, resource, policy_arn):
1731 client.attach_role_policy(
1732 RoleName=resource["Roles"][0]["RoleName"],
1733 PolicyArn=policy_arn
1734 )
1736 def detach_policy(self, client, resource, policy_arn):
1737 try:
1738 client.detach_role_policy(
1739 RoleName=resource["Roles"][0]["RoleName"],
1740 PolicyArn=policy_arn
1741 )
1742 except client.exceptions.NoSuchEntityException:
1743 return
1745 def list_attached_policies(self, client, resource):
1746 attached_policies = client.list_attached_role_policies(
1747 RoleName=resource["Roles"][0]["RoleName"]
1748 )
1749 policy_arns = [p.get('PolicyArn') for p in attached_policies['AttachedPolicies']]
1750 return policy_arns
1753###################
1754# IAM Users #
1755###################
1757class CredentialReport(Filter):
1758 """Use IAM Credential report to filter users.
1760 The IAM Credential report aggregates multiple pieces of
1761 information on iam users. This makes it highly efficient for
1762 querying multiple aspects of a user that would otherwise require
1763 per user api calls.
1765 https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html
1767 For example if we wanted to retrieve all users with mfa who have
1768 never used their password but have active access keys from the
1769 last month
1771 .. code-block:: yaml
1773 - name: iam-mfa-active-keys-no-login
1774 resource: iam-user
1775 filters:
1776 - type: credential
1777 key: mfa_active
1778 value: true
1779 - type: credential
1780 key: password_last_used
1781 value: absent
1782 - type: credential
1783 key: access_keys.last_used_date
1784 value_type: age
1785 value: 30
1786 op: less-than
1788 Credential Report Transforms
1790 We perform some default transformations from the raw
1791 credential report. Sub-objects (access_key_1, cert_2)
1792 are turned into array of dictionaries for matching
1793 purposes with their common prefixes stripped.
1794 N/A values are turned into None, TRUE/FALSE are turned
1795 into boolean values.
1797 """
1798 schema = type_schema(
1799 'credential',
1800 value_type={'$ref': '#/definitions/filters_common/value_types'},
1801 key={'type': 'string',
1802 'title': 'report key to search',
1803 'enum': [
1804 'user',
1805 'arn',
1806 'user_creation_time',
1807 'password_enabled',
1808 'password_last_used',
1809 'password_last_changed',
1810 'password_next_rotation',
1811 'mfa_active',
1812 'access_keys',
1813 'access_keys.active',
1814 'access_keys.last_used_date',
1815 'access_keys.last_used_region',
1816 'access_keys.last_used_service',
1817 'access_keys.last_rotated',
1818 'certs',
1819 'certs.active',
1820 'certs.last_rotated',
1821 ]},
1822 value={'$ref': '#/definitions/filters_common/value'},
1823 op={'$ref': '#/definitions/filters_common/comparison_operators'},
1824 report_generate={
1825 'title': 'Generate a report if none is present.',
1826 'default': True,
1827 'type': 'boolean'},
1828 report_delay={
1829 'title': 'Number of seconds to wait for report generation.',
1830 'default': 10,
1831 'type': 'number'},
1832 report_max_age={
1833 'title': 'Number of seconds to consider a report valid.',
1834 'default': 60 * 60 * 24,
1835 'type': 'number'})
1837 list_sub_objects = (
1838 ('access_key_1_', 'access_keys'),
1839 ('access_key_2_', 'access_keys'),
1840 ('cert_1_', 'certs'),
1841 ('cert_2_', 'certs'))
1843 # for access keys only
1844 matched_annotation_key = 'c7n:matched-keys'
1846 permissions = ('iam:GenerateCredentialReport',
1847 'iam:GetCredentialReport')
1849 def get_value_or_schema_default(self, k):
1850 if k in self.data:
1851 return self.data[k]
1852 return self.schema['properties'][k]['default']
1854 def get_credential_report(self):
1855 cache = self.manager._cache
1856 with cache:
1857 cache_key = {'account': self.manager.config.account_id, 'iam-credential-report': True}
1858 report = cache.get(cache_key)
1860 if report:
1861 return report
1862 data = self.fetch_credential_report()
1863 report = {}
1864 if isinstance(data, bytes):
1865 reader = csv.reader(io.StringIO(data.decode('utf-8')))
1866 else:
1867 reader = csv.reader(io.StringIO(data))
1868 headers = next(reader)
1869 for line in reader:
1870 info = dict(zip(headers, line))
1871 report[info['user']] = self.process_user_record(info)
1872 cache.save(cache_key, report)
1874 return report
1876 @classmethod
1877 def process_user_record(cls, info):
1878 """Type convert the csv record, modifies in place."""
1879 keys = list(info.keys())
1880 # Value conversion
1881 for k in keys:
1882 v = info[k]
1883 if v in ('N/A', 'no_information'):
1884 info[k] = None
1885 elif v == 'false':
1886 info[k] = False
1887 elif v == 'true':
1888 info[k] = True
1889 # Object conversion
1890 for p, t in cls.list_sub_objects:
1891 obj = dict([(k[len(p):], info.pop(k))
1892 for k in keys if k.startswith(p)])
1893 if obj.get('active', False) or obj.get('last_rotated', False):
1894 info.setdefault(t, []).append(obj)
1895 return info
1897 def fetch_credential_report(self):
1898 client = local_session(self.manager.session_factory).client('iam')
1899 try:
1900 report = client.get_credential_report()
1901 except ClientError as e:
1902 if e.response['Error']['Code'] == 'ReportNotPresent':
1903 report = None
1904 elif e.response['Error']['Code'] == 'ReportInProgress':
1905 # Someone else asked for the report before it was done. Wait
1906 # for it again.
1907 time.sleep(self.get_value_or_schema_default('report_delay'))
1908 report = client.get_credential_report()
1909 else:
1910 raise
1911 if report:
1912 threshold = datetime.datetime.now(tz=tzutc()) - timedelta(
1913 seconds=self.get_value_or_schema_default(
1914 'report_max_age'))
1915 if not report['GeneratedTime'].tzinfo:
1916 threshold = threshold.replace(tzinfo=None)
1917 if report['GeneratedTime'] < threshold:
1918 report = None
1919 if report is None:
1920 if not self.get_value_or_schema_default('report_generate'):
1921 raise ValueError("Credential Report Not Present")
1922 client.generate_credential_report()
1923 time.sleep(self.get_value_or_schema_default('report_delay'))
1924 report = client.get_credential_report()
1925 return report['Content']
1927 def process(self, resources, event=None):
1928 if '.' in self.data['key']:
1929 self.matcher_config = dict(self.data)
1930 self.matcher_config['key'] = self.data['key'].split('.', 1)[1]
1931 return []
1933 def match(self, resource, info):
1934 if info is None:
1935 return False
1936 k = self.data.get('key')
1937 if '.' not in k:
1938 vf = ValueFilter(self.data)
1939 vf.annotate = False
1940 return vf(info)
1942 # access key matching
1943 prefix, _ = k.split('.', 1)
1944 vf = ValueFilter(self.matcher_config)
1945 vf.annotate = False
1947 # annotation merging with previous respecting block operators
1948 k_matched = []
1949 for v in info.get(prefix, ()):
1950 if vf.match(v):
1951 k_matched.append(v)
1953 for k in k_matched:
1954 k['c7n:match-type'] = 'credential'
1956 self.merge_annotation(resource, self.matched_annotation_key, k_matched)
1957 return bool(k_matched)
1960@User.filter_registry.register('credential')
1961class UserCredentialReport(CredentialReport):
1963 def process(self, resources, event=None):
1964 super(UserCredentialReport, self).process(resources, event)
1965 report = self.get_credential_report()
1966 if report is None:
1967 return []
1968 results = []
1969 for r in resources:
1970 info = report.get(r['UserName'])
1971 if self.match(r, info):
1972 r['c7n:credential-report'] = info
1973 results.append(r)
1974 return results
1977@User.filter_registry.register('has-inline-policy')
1978class IamUserInlinePolicy(Filter):
1979 """
1980 Filter IAM users that have an inline-policy attached
1982 True: Filter users that have an inline-policy
1983 False: Filter users that do not have an inline-policy
1984 """
1986 schema = type_schema('has-inline-policy', value={'type': 'boolean'})
1987 permissions = ('iam:ListUserPolicies',)
1989 def _inline_policies(self, client, resource):
1990 resource['c7n:InlinePolicies'] = client.list_user_policies(
1991 UserName=resource['UserName'])['PolicyNames']
1992 return resource
1994 def process(self, resources, event=None):
1995 c = local_session(self.manager.session_factory).client('iam')
1996 value = self.data.get('value', True)
1997 res = []
1998 for r in resources:
1999 r = self._inline_policies(c, r)
2000 if len(r['c7n:InlinePolicies']) > 0 and value:
2001 res.append(r)
2002 if len(r['c7n:InlinePolicies']) == 0 and not value:
2003 res.append(r)
2004 return res
2007@User.filter_registry.register('policy')
2008class UserPolicy(ValueFilter):
2009 """Filter IAM users based on attached policy values
2011 :example:
2013 .. code-block:: yaml
2015 policies:
2016 - name: iam-users-with-admin-access
2017 resource: iam-user
2018 filters:
2019 - type: policy
2020 key: PolicyName
2021 value: AdministratorAccess
2022 include-via: true
2023 """
2025 schema = type_schema('policy', rinherit=ValueFilter.schema,
2026 **{'include-via': {'type': 'boolean'}})
2027 schema_alias = False
2028 permissions = (
2029 'iam:ListAttachedUserPolicies',
2030 'iam:ListGroupsForUser',
2031 'iam:ListAttachedGroupPolicies',
2032 )
2034 def find_in_user_set(self, user_set, search_key, arn_key, arn):
2035 for u in user_set:
2036 if search_key in u:
2037 searched = next((v for v in u[search_key] if v.get(arn_key) == arn), None)
2038 if searched is not None:
2039 return searched
2041 return None
2043 def user_groups_policies(self, client, user_set, u):
2044 u['c7n:Groups'] = client.list_groups_for_user(
2045 UserName=u['UserName'])['Groups']
2047 for ug in u['c7n:Groups']:
2048 ug_searched = self.find_in_user_set(user_set, 'c7n:Groups', 'Arn', ug['Arn'])
2049 if ug_searched and ug_searched.get('AttachedPolicies'):
2050 ug['AttachedPolicies'] = ug_searched['AttachedPolicies']
2051 else:
2052 ug['AttachedPolicies'] = client.list_attached_group_policies(
2053 GroupName=ug['GroupName'])['AttachedPolicies']
2055 for ap in ug['AttachedPolicies']:
2056 p_searched = self.find_in_user_set([u], 'c7n:Policies', 'Arn', ap['PolicyArn'])
2057 if not p_searched:
2058 p_searched = self.find_in_user_set(
2059 user_set, 'c7n:Policies', 'Arn', ap['PolicyArn']
2060 )
2061 if p_searched:
2062 u['c7n:Policies'].append(p_searched)
2063 else:
2064 u['c7n:Policies'].append(
2065 client.get_policy(PolicyArn=ap['PolicyArn'])['Policy'])
2067 return u
2069 def user_policies(self, user_set):
2070 client = local_session(self.manager.session_factory).client('iam')
2071 for u in user_set:
2072 if 'c7n:Policies' not in u:
2073 u['c7n:Policies'] = []
2074 aps = client.list_attached_user_policies(
2075 UserName=u['UserName'])['AttachedPolicies']
2076 for ap in aps:
2077 u['c7n:Policies'].append(
2078 client.get_policy(PolicyArn=ap['PolicyArn'])['Policy'])
2079 if self.data.get('include-via'):
2080 u = self.user_groups_policies(client, user_set, u)
2082 def process(self, resources, event=None):
2083 user_set = chunks(resources, size=50)
2084 with self.executor_factory(max_workers=2) as w:
2085 self.log.debug(
2086 "Querying %d users policies" % len(resources))
2087 list(w.map(self.user_policies, user_set))
2089 matched = []
2090 for r in resources:
2091 for p in r['c7n:Policies']:
2092 if self.match(p) and r not in matched:
2093 matched.append(r)
2094 return matched
2097@User.filter_registry.register('group')
2098class GroupMembership(ValueFilter):
2099 """Filter IAM users based on attached group values
2101 :example:
2103 .. code-block:: yaml
2105 policies:
2106 - name: iam-users-in-admin-group
2107 resource: iam-user
2108 filters:
2109 - type: group
2110 key: GroupName
2111 value: Admins
2112 """
2114 schema = type_schema('group', rinherit=ValueFilter.schema)
2115 schema_alias = False
2116 permissions = ('iam:ListGroupsForUser',)
2118 def get_user_groups(self, client, user_set):
2119 for u in user_set:
2120 u['c7n:Groups'] = client.list_groups_for_user(
2121 UserName=u['UserName'])['Groups']
2123 def process(self, resources, event=None):
2124 client = local_session(self.manager.session_factory).client('iam')
2125 with self.executor_factory(max_workers=2) as w:
2126 futures = []
2127 for user_set in chunks(
2128 [r for r in resources if 'c7n:Groups' not in r], size=50):
2129 futures.append(
2130 w.submit(self.get_user_groups, client, user_set))
2131 for f in as_completed(futures):
2132 pass
2134 matched = []
2135 for r in resources:
2136 for p in r.get('c7n:Groups', []):
2137 if self.match(p) and r not in matched:
2138 matched.append(r)
2139 return matched
2142@User.filter_registry.register('access-key')
2143class UserAccessKey(ValueFilter):
2144 """Filter IAM users based on access-key values
2146 By default multiple uses of this filter will match
2147 on any user key satisfying either filter. To find
2148 specific keys that match multiple access-key filters,
2149 use `match-operator: and`
2151 :example:
2153 .. code-block:: yaml
2155 policies:
2156 - name: iam-users-with-active-keys
2157 resource: iam-user
2158 filters:
2159 - type: access-key
2160 key: Status
2161 value: Active
2162 - type: access-key
2163 match-operator: and
2164 key: CreateDate
2165 value_type: age
2166 value: 90
2167 """
2169 schema = type_schema(
2170 'access-key',
2171 rinherit=ValueFilter.schema,
2172 **{'match-operator': {'enum': ['and', 'or']}})
2173 schema_alias = False
2174 permissions = ('iam:ListAccessKeys',)
2175 annotation_key = 'c7n:AccessKeys'
2176 matched_annotation_key = 'c7n:matched-keys'
2177 annotate = False
2179 def get_user_keys(self, client, user_set):
2180 for u in user_set:
2181 u[self.annotation_key] = self.manager.retry(
2182 client.list_access_keys,
2183 UserName=u['UserName'])['AccessKeyMetadata']
2185 def process(self, resources, event=None):
2186 client = local_session(self.manager.session_factory).client('iam')
2187 with self.executor_factory(max_workers=2) as w:
2188 augment_set = [r for r in resources if self.annotation_key not in r]
2189 self.log.debug(
2190 "Querying %d users' api keys" % len(augment_set))
2191 list(w.map(
2192 functools.partial(self.get_user_keys, client),
2193 chunks(augment_set, 50)))
2195 matched = []
2196 match_op = self.data.get('match-operator', 'or')
2197 for r in resources:
2198 keys = r[self.annotation_key]
2199 if self.matched_annotation_key in r and match_op == 'and':
2200 keys = r[self.matched_annotation_key]
2201 k_matched = []
2202 for k in keys:
2203 if self.match(k):
2204 k_matched.append(k)
2205 for k in k_matched:
2206 k['c7n:match-type'] = 'access'
2207 self.merge_annotation(r, self.matched_annotation_key, k_matched)
2208 if k_matched:
2209 matched.append(r)
2210 return matched
2213@User.filter_registry.register('ssh-key')
2214class UserSSHKeyFilter(ValueFilter):
2215 """Filter IAM users based on uploaded SSH public keys
2217 :example:
2219 .. code-block:: yaml
2221 policies:
2222 - name: iam-users-with-old-ssh-keys
2223 resource: iam-user
2224 filters:
2225 - type: ssh-key
2226 key: Status
2227 value: Active
2228 - type: ssh-key
2229 key: UploadDate
2230 value_type: age
2231 value: 90
2232 """
2234 schema = type_schema(
2235 'ssh-key',
2236 rinherit=ValueFilter.schema)
2237 schema_alias = False
2238 permissions = ('iam:ListSSHPublicKeys',)
2239 annotation_key = 'c7n:SSHKeys'
2240 matched_annotation_key = 'c7n:matched-ssh-keys'
2241 annotate = False
2243 def get_user_ssh_keys(self, client, user_set):
2244 for u in user_set:
2245 u[self.annotation_key] = self.manager.retry(
2246 client.list_ssh_public_keys,
2247 UserName=u['UserName'])['SSHPublicKeys']
2249 def process(self, resources, event=None):
2250 client = local_session(self.manager.session_factory).client('iam')
2251 with self.executor_factory(max_workers=2) as w:
2252 augment_set = [r for r in resources if self.annotation_key not in r]
2253 self.log.debug(
2254 "Querying %d users' SSH keys" % len(augment_set))
2255 list(w.map(
2256 functools.partial(self.get_user_ssh_keys, client),
2257 chunks(augment_set, 50)))
2259 matched = []
2260 for r in resources:
2261 matched_keys = [k for k in r[self.annotation_key] if self.match(k)]
2262 self.merge_annotation(r, self.matched_annotation_key, matched_keys)
2263 if matched_keys:
2264 matched.append(r)
2265 return matched
2268@User.filter_registry.register('login-profile')
2269class UserLoginProfile(ValueFilter):
2270 """Filter IAM users that have an associated login-profile
2272 For quicker evaluation and reduced API traffic, it is recommended to
2273 instead use the 'credential' filter with 'password_enabled': true when
2274 a delay of up to four hours for credential report syncing is acceptable.
2276 (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html)
2278 :example:
2280 .. code-block: yaml
2282 policies:
2283 - name: iam-users-with-console-access
2284 resource: iam-user
2285 filters:
2286 - type: login-profile
2287 """
2289 schema = type_schema('login-profile', rinherit=ValueFilter.schema)
2290 permissions = ('iam:GetLoginProfile',)
2291 annotation_key = 'c7n:LoginProfile'
2293 def user_login_profiles(self, user_set):
2294 client = local_session(self.manager.session_factory).client('iam')
2295 for u in user_set:
2296 u[self.annotation_key] = False
2297 try:
2298 login_profile_resp = client.get_login_profile(UserName=u['UserName'])
2299 if u['UserName'] == login_profile_resp['LoginProfile']['UserName']:
2300 u[self.annotation_key] = True
2301 except ClientError as e:
2302 if e.response['Error']['Code'] not in ('NoSuchEntity',):
2303 raise
2305 def process(self, resources, event=None):
2306 user_set = chunks(resources, size=50)
2307 with self.executor_factory(max_workers=2) as w:
2308 self.log.debug(
2309 "Querying %d users for login profile" % len(resources))
2310 list(w.map(self.user_login_profiles, user_set))
2312 matched = []
2313 for r in resources:
2314 if r[self.annotation_key]:
2315 matched.append(r)
2316 return matched
2319# Mfa-device filter for iam-users
2320@User.filter_registry.register('mfa-device')
2321class UserMfaDevice(ValueFilter):
2322 """Filter iam-users based on mfa-device status
2324 :example:
2326 .. code-block:: yaml
2328 policies:
2329 - name: mfa-enabled-users
2330 resource: iam-user
2331 filters:
2332 - type: mfa-device
2333 key: UserName
2334 value: not-null
2335 """
2337 schema = type_schema('mfa-device', rinherit=ValueFilter.schema)
2338 schema_alias = False
2339 permissions = ('iam:ListMFADevices',)
2341 def __init__(self, *args, **kw):
2342 super(UserMfaDevice, self).__init__(*args, **kw)
2343 self.data['key'] = 'MFADevices'
2345 def process(self, resources, event=None):
2347 def _user_mfa_devices(resource):
2348 client = local_session(self.manager.session_factory).client('iam')
2349 resource['MFADevices'] = client.list_mfa_devices(
2350 UserName=resource['UserName'])['MFADevices']
2352 with self.executor_factory(max_workers=2) as w:
2353 query_resources = [
2354 r for r in resources if 'MFADevices' not in r]
2355 self.log.debug(
2356 "Querying %d users' mfa devices" % len(query_resources))
2357 list(w.map(_user_mfa_devices, query_resources))
2359 matched = []
2360 for r in resources:
2361 if self.match(r):
2362 matched.append(r)
2364 return matched
2367@User.action_registry.register('post-finding')
2368class UserFinding(OtherResourcePostFinding):
2370 def format_resource(self, r):
2371 if any(filter(lambda x: isinstance(x, UserAccessKey), self.manager.iter_filters())):
2372 details = {
2373 "UserName": "arn:aws:iam:{}:user/{}".format(
2374 self.manager.config.account_id, r["c7n:AccessKeys"][0]["UserName"]
2375 ),
2376 "Status": r["c7n:AccessKeys"][0]["Status"],
2377 "CreatedAt": r["c7n:AccessKeys"][0]["CreateDate"].isoformat(),
2378 }
2379 accesskey = {
2380 "Type": "AwsIamAccessKey",
2381 "Id": r["c7n:AccessKeys"][0]["AccessKeyId"],
2382 "Region": self.manager.config.region,
2383 "Details": {"AwsIamAccessKey": filter_empty(details)},
2384 }
2385 return filter_empty(accesskey)
2386 else:
2387 return super(UserFinding, self).format_resource(r)
2390@User.action_registry.register('delete')
2391class UserDelete(BaseAction):
2392 """Delete a user or properties of a user.
2394 For example if you want to have a whitelist of valid (machine-)users
2395 and want to ensure that no users have been clicked without documentation.
2397 You can use both the 'credential' or the 'username'
2398 filter. 'credential' will have an SLA of 4h,
2399 (http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html),
2400 but the added benefit of performing less API calls, whereas
2401 'username' will make more API calls, but have a SLA of your cache.
2403 :example:
2405 .. code-block:: yaml
2407 # using a 'credential' filter'
2408 - name: iam-only-whitelisted-users-credential
2409 resource: iam-user
2410 filters:
2411 - type: credential
2412 key: user
2413 op: not-in
2414 value:
2415 - valid-user-1
2416 - valid-user-2
2417 actions:
2418 - delete
2420 # using a 'username' filter with 'UserName'
2421 - name: iam-only-whitelisted-users-username
2422 resource: iam-user
2423 filters:
2424 - type: value
2425 key: UserName
2426 op: not-in
2427 value:
2428 - valid-user-1
2429 - valid-user-2
2430 actions:
2431 - delete
2433 # using a 'username' filter with 'Arn'
2434 - name: iam-only-whitelisted-users-arn
2435 resource: iam-user
2436 filters:
2437 - type: value
2438 key: Arn
2439 op: not-in
2440 value:
2441 - arn:aws:iam:123456789012:user/valid-user-1
2442 - arn:aws:iam:123456789012:user/valid-user-2
2443 actions:
2444 - delete
2446 Additionally, you can specify the options to delete properties of an iam-user,
2447 including console-access, access-keys, attached-user-policies,
2448 inline-user-policies, mfa-devices, groups,
2449 ssh-keys, signing-certificates, and service-specific-credentials.
2451 Note: using options will _not_ delete the user itself, only the items specified
2452 by ``options`` that are attached to the respective iam-user. To delete a user
2453 completely, use the ``delete`` action without specifying ``options``.
2455 :example:
2457 .. code-block:: yaml
2459 - name: delete-console-access-unless-valid
2460 comment: |
2461 finds iam-users with console access and deletes console access unless
2462 the username is included in whitelist
2463 resource: iam-user
2464 filters:
2465 - type: value
2466 key: UserName
2467 op: not-in
2468 value:
2469 - valid-user-1
2470 - valid-user-2
2471 - type: credential
2472 key: password_enabled
2473 value: true
2474 actions:
2475 - type: delete
2476 options:
2477 - console-access
2479 - name: delete-misc-access-for-iam-user
2480 comment: |
2481 deletes multiple options from test_user
2482 resource: iam-user
2483 filters:
2484 - UserName: test_user
2485 actions:
2486 - type: delete
2487 options:
2488 - mfa-devices
2489 - access-keys
2490 - ssh-keys
2491 """
2493 ORDERED_OPTIONS = OrderedDict([
2494 ('console-access', 'delete_console_access'),
2495 ('access-keys', 'delete_access_keys'),
2496 ('attached-user-policies', 'delete_attached_user_policies'),
2497 ('inline-user-policies', 'delete_inline_user_policies'),
2498 ('mfa-devices', 'delete_hw_mfa_devices'),
2499 ('groups', 'delete_groups'),
2500 ('ssh-keys', 'delete_ssh_keys'),
2501 ('signing-certificates', 'delete_signing_certificates'),
2502 ('service-specific-credentials', 'delete_service_specific_credentials'),
2503 ])
2504 COMPOUND_OPTIONS = {
2505 'user-policies': ['attached-user-policies', 'inline-user-policies'],
2506 }
2508 schema = type_schema(
2509 'delete',
2510 options={
2511 'type': 'array',
2512 'items': {
2513 'type': 'string',
2514 'enum': list(ORDERED_OPTIONS.keys()) + list(COMPOUND_OPTIONS.keys()),
2515 }
2516 })
2518 permissions = (
2519 'iam:ListAttachedUserPolicies',
2520 'iam:ListAccessKeys',
2521 'iam:ListGroupsForUser',
2522 'iam:ListMFADevices',
2523 'iam:ListServiceSpecificCredentials',
2524 'iam:ListSigningCertificates',
2525 'iam:ListSSHPublicKeys',
2526 'iam:DeactivateMFADevice',
2527 'iam:DeleteAccessKey',
2528 'iam:DeleteLoginProfile',
2529 'iam:DeleteSigningCertificate',
2530 'iam:DeleteSSHPublicKey',
2531 'iam:DeleteUser',
2532 'iam:DeleteUserPolicy',
2533 'iam:DetachUserPolicy',
2534 'iam:RemoveUserFromGroup')
2536 @staticmethod
2537 def delete_console_access(client, r):
2538 try:
2539 client.delete_login_profile(
2540 UserName=r['UserName'])
2541 except ClientError as e:
2542 if e.response['Error']['Code'] not in ('NoSuchEntity',):
2543 raise
2545 @staticmethod
2546 def delete_access_keys(client, r):
2547 response = client.list_access_keys(UserName=r['UserName'])
2548 for access_key in response['AccessKeyMetadata']:
2549 client.delete_access_key(UserName=r['UserName'],
2550 AccessKeyId=access_key['AccessKeyId'])
2552 @staticmethod
2553 def delete_attached_user_policies(client, r):
2554 response = client.list_attached_user_policies(UserName=r['UserName'])
2555 for user_policy in response['AttachedPolicies']:
2556 client.detach_user_policy(
2557 UserName=r['UserName'], PolicyArn=user_policy['PolicyArn'])
2559 @staticmethod
2560 def delete_inline_user_policies(client, r):
2561 response = client.list_user_policies(UserName=r['UserName'])
2562 for user_policy_name in response['PolicyNames']:
2563 client.delete_user_policy(
2564 UserName=r['UserName'], PolicyName=user_policy_name)
2566 @staticmethod
2567 def delete_hw_mfa_devices(client, r):
2568 response = client.list_mfa_devices(UserName=r['UserName'])
2569 for mfa_device in response['MFADevices']:
2570 client.deactivate_mfa_device(
2571 UserName=r['UserName'], SerialNumber=mfa_device['SerialNumber'])
2573 @staticmethod
2574 def delete_groups(client, r):
2575 response = client.list_groups_for_user(UserName=r['UserName'])
2576 for user_group in response['Groups']:
2577 client.remove_user_from_group(
2578 UserName=r['UserName'], GroupName=user_group['GroupName'])
2580 @staticmethod
2581 def delete_ssh_keys(client, r):
2582 response = client.list_ssh_public_keys(UserName=r['UserName'])
2583 for key in response.get('SSHPublicKeys', ()):
2584 client.delete_ssh_public_key(
2585 UserName=r['UserName'], SSHPublicKeyId=key['SSHPublicKeyId'])
2587 @staticmethod
2588 def delete_signing_certificates(client, r):
2589 response = client.list_signing_certificates(UserName=r['UserName'])
2590 for cert in response.get('Certificates', ()):
2591 client.delete_signing_certificate(
2592 UserName=r['UserName'], CertificateId=cert['CertificateId'])
2594 @staticmethod
2595 def delete_service_specific_credentials(client, r):
2596 # Service specific user credentials (codecommit)
2597 response = client.list_service_specific_credentials(UserName=r['UserName'])
2598 for screds in response.get('ServiceSpecificCredentials', ()):
2599 client.delete_service_specific_credential(
2600 UserName=r['UserName'],
2601 ServiceSpecificCredentialId=screds['ServiceSpecificCredentialId'])
2603 @staticmethod
2604 def delete_user(client, r):
2605 client.delete_user(UserName=r['UserName'])
2607 def process(self, resources):
2608 client = local_session(self.manager.session_factory).client('iam')
2609 self.log.debug('Deleting user %s options: %s' %
2610 (len(resources), self.data.get('options', 'all')))
2611 for r in resources:
2612 self.process_user(client, r)
2614 def process_user(self, client, r):
2615 user_options = self.data.get('options', list(self.ORDERED_OPTIONS.keys()))
2616 # resolve compound options
2617 for cmd in self.COMPOUND_OPTIONS:
2618 if cmd in user_options:
2619 user_options += self.COMPOUND_OPTIONS[cmd]
2620 # process options in ordered fashion
2621 for cmd in self.ORDERED_OPTIONS:
2622 if cmd in user_options:
2623 op = getattr(self, self.ORDERED_OPTIONS[cmd])
2624 op(client, r)
2625 if not self.data.get('options'):
2626 self.delete_user(client, r)
2629@User.action_registry.register('remove-keys')
2630class UserRemoveAccessKey(BaseAction):
2631 """Delete or disable user's access keys.
2633 For example if we wanted to disable keys 90 days after creation and
2634 delete them 180 days after creation:
2636 :example:
2638 .. code-block:: yaml
2640 - name: iam-mfa-active-key-no-login
2641 resource: iam-user
2642 actions:
2643 - type: remove-keys
2644 disable: true
2645 age: 90
2646 - type: remove-keys
2647 age: 180
2648 """
2650 schema = type_schema(
2651 'remove-keys',
2652 matched={'type': 'boolean'},
2653 age={'type': 'number'},
2654 disable={'type': 'boolean'})
2655 permissions = ('iam:ListAccessKeys', 'iam:UpdateAccessKey',
2656 'iam:DeleteAccessKey')
2658 def validate(self):
2659 if self.data.get('matched') and self.data.get('age'):
2660 raise PolicyValidationError(
2661 "policy:%s cant mix matched and age parameters")
2662 ftypes = {f.type for f in self.manager.iter_filters()}
2663 if 'credential' in ftypes and 'access-key' in ftypes:
2664 raise PolicyValidationError(
2665 "policy:%s cant mix credential and access-key filters w/ delete action")
2666 return self
2668 def process(self, resources):
2669 client = local_session(self.manager.session_factory).client('iam')
2671 age = self.data.get('age')
2672 disable = self.data.get('disable')
2673 matched = self.data.get('matched')
2675 if age:
2676 threshold_date = datetime.datetime.now(tz=tzutc()) - timedelta(age)
2678 for r in resources:
2679 if 'c7n:AccessKeys' not in r:
2680 r['c7n:AccessKeys'] = client.list_access_keys(
2681 UserName=r['UserName'])['AccessKeyMetadata']
2683 keys = r['c7n:AccessKeys']
2684 if matched:
2685 m_keys = resolve_credential_keys(
2686 r.get(CredentialReport.matched_annotation_key),
2687 keys)
2688 # It is possible for a _user_ to match multiple credential filters
2689 # without having any single key match them all.
2690 if not m_keys:
2691 continue
2692 keys = m_keys
2694 for k in keys:
2695 if age:
2696 if not k['CreateDate'] < threshold_date:
2697 continue
2698 if disable:
2699 client.update_access_key(
2700 UserName=r['UserName'],
2701 AccessKeyId=k['AccessKeyId'],
2702 Status='Inactive')
2703 else:
2704 client.delete_access_key(
2705 UserName=r['UserName'],
2706 AccessKeyId=k['AccessKeyId'])
2709@User.action_registry.register('delete-ssh-keys')
2710class UserDeleteSSHKey(BaseAction):
2711 """Delete or disable a user's SSH keys.
2713 For example to delete keys after 90 days:
2715 :example:
2717 .. code-block:: yaml
2719 - name: iam-user-delete-ssh-keys
2720 resource: iam-user
2721 actions:
2722 - type: delete-ssh-keys
2723 """
2725 schema = type_schema(
2726 'delete-ssh-keys',
2727 matched={'type': 'boolean'},
2728 disable={'type': 'boolean'})
2729 annotation_key = 'c7n:SSHKeys'
2730 permissions = ('iam:ListSSHPublicKeys', 'iam:UpdateSSHPublicKey',
2731 'iam:DeleteSSHPublicKey')
2733 def process(self, resources):
2734 client = local_session(self.manager.session_factory).client('iam')
2736 for r in resources:
2737 if self.annotation_key not in r:
2738 r[self.annotation_key] = client.list_ssh_public_keys(
2739 UserName=r['UserName'])['SSHPublicKeys']
2741 keys = (r.get(UserSSHKeyFilter.matched_annotation_key, [])
2742 if self.data.get('matched') else r[self.annotation_key])
2744 for k in keys:
2745 if self.data.get('disable'):
2746 client.update_ssh_public_key(
2747 UserName=r['UserName'],
2748 SSHPublicKeyId=k['SSHPublicKeyId'],
2749 Status='Inactive')
2750 else:
2751 client.delete_ssh_public_key(
2752 UserName=r['UserName'],
2753 SSHPublicKeyId=k['SSHPublicKeyId'])
2756def resolve_credential_keys(m_keys, keys):
2757 res = []
2758 for k in m_keys:
2759 if k['c7n:match-type'] == 'credential':
2760 c_date = parse_date(k['last_rotated'])
2761 for ak in keys:
2762 if c_date == ak['CreateDate']:
2763 ak = dict(ak)
2764 ak['c7n:match-type'] = 'access'
2765 if ak not in res:
2766 res.append(ak)
2767 elif k not in res:
2768 res.append(k)
2769 return res
2772#################
2773# IAM Groups #
2774#################
2777@Group.filter_registry.register('has-specific-managed-policy')
2778class SpecificIamGroupManagedPolicy(Filter):
2779 """Filter IAM groups that have a specific policy attached
2781 For example, if the user wants to check all groups with 'admin-policy':
2783 :example:
2785 .. code-block:: yaml
2787 policies:
2788 - name: iam-groups-have-admin
2789 resource: iam-group
2790 filters:
2791 - type: has-specific-managed-policy
2792 value: admin-policy
2793 """
2795 schema = type_schema('has-specific-managed-policy', value={'type': 'string'})
2796 permissions = ('iam:ListAttachedGroupPolicies',)
2798 def _managed_policies(self, client, resource):
2799 return [r['PolicyName'] for r in client.list_attached_group_policies(
2800 GroupName=resource['GroupName'])['AttachedPolicies']]
2802 def process(self, resources, event=None):
2803 c = local_session(self.manager.session_factory).client('iam')
2804 if self.data.get('value'):
2805 results = []
2806 for r in resources:
2807 r["ManagedPolicies"] = self._managed_policies(c, r)
2808 if self.data.get('value') in r["ManagedPolicies"]:
2809 results.append(r)
2810 return results
2811 return []
2814@Group.filter_registry.register('has-users')
2815class IamGroupUsers(Filter):
2816 """Filter IAM groups that have users attached based on True/False value:
2817 True: Filter all IAM groups with users assigned to it
2818 False: Filter all IAM groups without any users assigned to it
2820 :example:
2822 .. code-block:: yaml
2824 - name: empty-iam-group
2825 resource: iam-group
2826 filters:
2827 - type: has-users
2828 value: False
2829 """
2830 schema = type_schema('has-users', value={'type': 'boolean'})
2831 permissions = ('iam:GetGroup',)
2833 def _user_count(self, client, resource):
2834 return len(client.get_group(GroupName=resource['GroupName'])['Users'])
2836 def process(self, resources, events=None):
2837 c = local_session(self.manager.session_factory).client('iam')
2838 if self.data.get('value', True):
2839 return [r for r in resources if self._user_count(c, r) > 0]
2840 return [r for r in resources if self._user_count(c, r) == 0]
2843@Group.filter_registry.register('has-inline-policy')
2844class IamGroupInlinePolicy(Filter):
2845 """Filter IAM groups that have an inline-policy based on boolean value:
2846 True: Filter all groups that have an inline-policy attached
2847 False: Filter all groups that do not have an inline-policy attached
2849 :example:
2851 .. code-block:: yaml
2853 - name: iam-groups-with-inline-policy
2854 resource: iam-group
2855 filters:
2856 - type: has-inline-policy
2857 value: True
2858 """
2859 schema = type_schema('has-inline-policy', value={'type': 'boolean'})
2860 permissions = ('iam:ListGroupPolicies',)
2862 def _inline_policies(self, client, resource):
2863 resource['c7n:InlinePolicies'] = client.list_group_policies(
2864 GroupName=resource['GroupName'])['PolicyNames']
2865 return resource
2867 def process(self, resources, events=None):
2868 c = local_session(self.manager.session_factory).client('iam')
2869 value = self.data.get('value', True)
2870 res = []
2871 for r in resources:
2872 r = self._inline_policies(c, r)
2873 if len(r['c7n:InlinePolicies']) > 0 and value:
2874 res.append(r)
2875 if len(r['c7n:InlinePolicies']) == 0 and not value:
2876 res.append(r)
2877 return res
2880@Group.action_registry.register('delete-inline-policies')
2881class GroupInlinePolicyDelete(BaseAction):
2882 """Delete inline policies embedded in an IAM group.
2884 :example:
2886 .. code-block:: yaml
2888 - name: iam-delete-group-policies
2889 resource: aws.iam-group
2890 filters:
2891 - type: value
2892 key: GroupName
2893 value: test
2894 actions:
2895 - type: delete-inline-policies
2896 """
2897 schema = type_schema('delete-inline-policies')
2898 permissions = ('iam:ListGroupPolicies', 'iam:DeleteGroupPolicy',)
2900 def process(self, resources):
2901 client = local_session(self.manager.session_factory).client('iam')
2902 for r in resources:
2903 self.process_group(client, r)
2905 def process_group(self, client, r):
2906 if 'c7n:InlinePolicies' not in r:
2907 r['c7n:InlinePolicies'] = client.list_group_policies(
2908 GroupName=r['GroupName'])['PolicyNames']
2909 for policy in r.get('c7n:InlinePolicies', []):
2910 try:
2911 self.manager.retry(client.delete_group_policy,
2912 GroupName=r['GroupName'], PolicyName=policy)
2913 except client.exceptions.NoSuchEntityException:
2914 continue
2917@Group.action_registry.register('delete')
2918class UserGroupDelete(BaseAction):
2919 """Delete an IAM User Group.
2921 For example, if you want to delete a group named 'test'.
2923 :example:
2925 .. code-block:: yaml
2927 - name: iam-delete-user-group
2928 resource: aws.iam-group
2929 filters:
2930 - type: value
2931 key: GroupName
2932 value: test
2933 actions:
2934 - type: delete
2935 force: True
2936 """
2937 schema = type_schema('delete', force={'type': 'boolean'})
2938 permissions = ('iam:DeleteGroup', 'iam:RemoveUserFromGroup')
2940 def process(self, resources):
2941 client = local_session(self.manager.session_factory).client('iam')
2942 for r in resources:
2943 self.process_group(client, r)
2945 def process_group(self, client, r):
2946 error = None
2947 force = self.data.get('force', False)
2948 if force:
2949 users = client.get_group(GroupName=r['GroupName']).get('Users', [])
2950 for user in users:
2951 client.remove_user_from_group(
2952 UserName=user['UserName'], GroupName=r['GroupName'])
2954 try:
2955 client.delete_group(GroupName=r['GroupName'])
2956 except client.exceptions.DeleteConflictException as e:
2957 self.log.warning(
2958 ("Group:%s cannot be deleted, "
2959 "set force to remove all users from group")
2960 % r['Arn'])
2961 error = e
2962 except (client.exceptions.NoSuchEntityException,
2963 client.exceptions.UnmodifiableEntityException):
2964 pass
2965 if error:
2966 raise error
2969class SamlProviderDescribe(DescribeSource):
2971 def augment(self, resources):
2972 super().augment(resources)
2973 for r in resources:
2974 md = r.get('SAMLMetadataDocument')
2975 if not md:
2976 continue
2977 root = sso_metadata(md)
2978 r['IDPSSODescriptor'] = root['IDPSSODescriptor']
2979 return resources
2981 def get_permissions(self):
2982 return ('iam:GetSAMLProvider', 'iam:ListSAMLProviders')
2985def sso_metadata(md):
2986 root = ElementTree.fromstringlist(md)
2987 d = {}
2988 _sso_recurse(root, d)
2989 return d
2992def _sso_recurse(node, d):
2993 d.update(node.attrib)
2994 for c in node:
2995 k = c.tag.split('}', 1)[-1]
2996 cd = {}
2997 if k in d:
2998 if not isinstance(d[k], list):
2999 d[k] = [d[k]]
3000 d[k].append(cd)
3001 else:
3002 d[k] = cd
3003 _sso_recurse(c, cd)
3004 if node.text and node.text.strip():
3005 d['Value'] = node.text.strip()
3008@resources.register('iam-saml-provider')
3009class SamlProvider(QueryResourceManager):
3010 """SAML SSO Provider
3012 we parse and expose attributes of the SAML Metadata XML Document
3013 as resources attribute for use with custodian's standard value filter.
3014 """
3016 class resource_type(TypeInfo):
3018 service = 'iam'
3019 name = id = 'Arn'
3020 enum_spec = ('list_saml_providers', 'SAMLProviderList', None)
3021 detail_spec = ('get_saml_provider', 'SAMLProviderArn', 'Arn', None)
3022 arn = 'Arn'
3023 arn_type = 'saml-provider'
3024 config_type = "AWS::IAM::SAMLProvider"
3025 global_resource = True
3027 source_mapping = {'describe': SamlProviderDescribe}
3030class OpenIdDescribe(DescribeSource):
3032 def get_permissions(self):
3033 return ('iam:GetOpenIDConnectProvider', 'iam:ListOpenIDConnectProviders')
3036@resources.register('iam-oidc-provider')
3037class OpenIdProvider(QueryResourceManager):
3039 class resource_type(TypeInfo):
3041 service = 'iam'
3042 name = id = 'Arn'
3043 enum_spec = ('list_open_id_connect_providers', 'OpenIDConnectProviderList', None)
3044 detail_spec = ('get_open_id_connect_provider', 'OpenIDConnectProviderArn', 'Arn', None)
3045 config_type = cfn_type = "AWS::IAM::OIDCProvider"
3046 arn = 'Arn'
3047 arn_type = 'oidc-provider'
3048 global_resource = True
3050 source_mapping = {'describe': OpenIdDescribe}
3053@OpenIdProvider.action_registry.register('delete')
3054class OpenIdProviderDelete(BaseAction):
3055 """Delete an OpenID Connect IAM Identity Provider
3057 For example, if you want to automatically delete an OIDC IdP for example.com
3059 :example:
3061 .. code-block:: yaml
3063 - name: aws-iam-oidc-provider-delete
3064 resource: iam-oidc-provider
3065 filters:
3066 - type: value
3067 key: Url
3068 value: example.com
3069 actions:
3070 - type: delete
3072 """
3073 schema = type_schema('delete')
3074 permissions = ('iam:DeleteOpenIDConnectProvider',)
3076 def process(self, resources):
3077 client = local_session(self.manager.session_factory).client('iam')
3078 for provider in resources:
3079 self.manager.retry(
3080 client.delete_open_id_connect_provider,
3081 OpenIDConnectProviderArn=provider['Arn'],
3082 ignore_err_codes=(
3083 'NoSuchEntityException',
3084 'DeleteConflictException',
3085 ),
3086 )
3089@SamlProvider.action_registry.register('delete')
3090class SamlProviderDelete(BaseAction):
3091 """Delete a SAML IAM Identity Provider
3093 For example, if you want to automatically delete an SAML IdP for unknown-idp
3095 :example:
3097 .. code-block:: yaml
3099 - name: aws-iam-saml-provider-delete
3100 resource: iam-saml-provider
3101 filters:
3102 - type: value
3103 key: Name
3104 value: unknown-idp
3105 actions:
3106 - type: delete
3108 """
3109 schema = type_schema('delete')
3110 permissions = ('iam:DeleteSAMLProvider',)
3112 def process(self, resources):
3113 client = local_session(self.manager.session_factory).client('iam')
3114 for provider in resources:
3115 self.manager.retry(
3116 client.delete_saml_provider,
3117 SAMLProviderArn=provider['Arn'],
3118 ignore_err_codes=(
3119 'NoSuchEntityException',
3120 ),
3121 )
3124@InstanceProfile.filter_registry.register('has-specific-managed-policy')
3125class SpecificIamProfileManagedPolicy(ValueFilter):
3126 """Filter an IAM instance profile that contains an IAM role that has a specific managed IAM
3127 policy. If an IAM instance profile does not contain an IAM role, then it will be treated
3128 as not having the policy.
3130 :example:
3132 Check for instance profile roles with 'admin-policy' attached:
3134 .. code-block:: yaml
3136 policies:
3137 - name: iam-profiles-have-admin
3138 resource: aws.iam-profile
3139 filters:
3140 - type: has-specific-managed-policy
3141 value: admin-policy
3143 :example:
3145 Check for instance profile roles with an attached policy matching
3146 a given list:
3148 .. code-block:: yaml
3150 policies:
3151 - name: iam-profiles-with-selected-policies
3152 resource: aws.iam-profile
3153 filters:
3154 - type: has-specific-managed-policy
3155 value:
3156 - AmazonS3FullAccess
3157 - AWSOrganizationsFullAccess
3159 :example:
3161 Check for instance profile roles with attached policy names matching
3162 a pattern:
3164 .. code-block:: yaml
3166 policies:
3167 - name: iam-profiles-with-full-access-policies
3168 resource: aws.iam-profile
3169 filters:
3170 - type: has-specific-managed-policy
3171 op: glob
3172 value: "*FullAccess"
3174 Check for instance profile roles with attached policy ARNs matching
3175 a pattern:
3177 .. code-block:: yaml
3179 policies:
3180 - name: iam-profiles-with-aws-full-access-policies
3181 resource: aws.iam-profile
3182 filters:
3183 - type: has-specific-managed-policy
3184 key: PolicyArn
3185 op: regex
3186 value: "arn:aws:iam::aws:policy/.*FullAccess"
3187 """
3189 schema = type_schema('has-specific-managed-policy', rinherit=ValueFilter.schema)
3190 permissions = ('iam:ListAttachedRolePolicies',)
3191 annotation_key = 'c7n:AttachedPolicies'
3192 matched_annotation_key = 'c7n:MatchedPolicies'
3193 schema_alias = False
3195 def __init__(self, data, manager=None):
3196 # Preserve backward compatibility
3197 if 'key' not in data:
3198 data['key'] = 'PolicyName'
3199 super(SpecificIamProfileManagedPolicy, self).__init__(data, manager)
3201 # This code assumes a single IAM role in the profile and the annotation
3202 # is added to the profile resource, not the role
3203 def get_managed_policies(self, client, prof_set):
3204 for prof in prof_set:
3205 prof[self.annotation_key] = []
3206 for role in prof.get('Roles', []):
3207 prof[self.annotation_key] = [
3208 role_policy
3209 for role_policy
3210 in client.list_attached_role_policies(
3211 RoleName=role['RoleName'])['AttachedPolicies']
3212 ]
3214 def process(self, resources, event=None):
3215 client = local_session(self.manager.session_factory).client('iam')
3216 with self.executor_factory(max_workers=2) as w:
3217 augment_set = [r for r in resources if self.annotation_key not in r]
3218 self.log.debug(
3219 "Querying %d roles' attached policies" % len(augment_set))
3220 list(w.map(
3221 functools.partial(self.get_managed_policies, client),
3222 chunks(augment_set, 50)))
3224 matched = []
3225 for r in resources:
3226 matched_keys = [k for k in r[self.annotation_key] if self.match(k)]
3227 self.merge_annotation(r, self.matched_annotation_key, matched_keys)
3228 if matched_keys:
3229 matched.append(r)
3230 return matched
3233#########################
3234# IAM Access Keys #
3235#########################
3238@resources.register('iam-access-key')
3239class AccessKey(ChildResourceManager):
3241 class resource_type(TypeInfo):
3242 service = 'iam'
3243 # Access keys don't have ARNs - they use AccessKeyId as identifier
3244 # Using 'access-key' as arn_type for consistency but ARN construction will not be used
3245 arn_type = 'access-key'
3246 id = name = 'AccessKeyId'
3247 date = 'CreateDate'
3248 # Denotes this resource type exists across regions
3249 global_resource = True
3250 enum_spec = ('list_access_keys', 'AccessKeys', None)
3251 parent_spec = ('iam-user', 'UserName', None)
3252 # No detail spec needed as list_access_keys returns full metadata
3253 cfn_type = config_type = "AWS::IAM::AccessKey"
3254 # config_id = 'AccessKeyId'
3256 source_mapping = {
3257 'config': ConfigSource
3258 }