Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/shield.py: 46%
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 botocore.exceptions import ClientError
4from botocore.paginate import Paginator
6from c7n.actions import BaseAction
7from c7n.filters import Filter
8from c7n.manager import resources
9from c7n.query import (QueryResourceManager, RetryPageIterator, TypeInfo,
10 DescribeSource, ConfigSource)
11from c7n.tags import RemoveTag, Tag, TagDelayedAction, TagActionFilter
12from c7n.utils import local_session, type_schema, get_retry
15class DescribeShieldProtection(DescribeSource):
16 def augment(self, resources):
17 client = local_session(self.manager.session_factory).client('shield')
19 def _augment(r):
20 tags = self.manager.retry(client.list_tags_for_resource,
21 ResourceARN=r['ProtectionArn'])['Tags']
22 r['Tags'] = tags
23 return r
24 resources = super().augment(resources)
25 return list(map(_augment, resources))
28@resources.register('shield-protection')
29class ShieldProtection(QueryResourceManager):
30 class resource_type(TypeInfo):
31 service = 'shield'
32 enum_spec = ('list_protections', 'Protections', None)
33 id = 'Id'
34 name = 'Name'
35 arn = 'ProtectionArn'
36 config_type = 'AWS::Shield::Protection'
37 global_resource = True
39 source_mapping = {
40 'describe': DescribeShieldProtection,
41 'config': ConfigSource
42 }
45@resources.register('shield-attack')
46class ShieldAttack(QueryResourceManager):
48 class resource_type(TypeInfo):
49 service = 'shield'
50 enum_spec = ('list_attacks', 'Attacks', None)
51 detail_spec = (
52 'describe_attack', 'AttackId', 'AttackId', 'Attack')
53 name = id = 'AttackId'
54 date = 'StartTime'
55 filter_name = 'ResourceArns'
56 filter_type = 'list'
57 arn = False
58 global_resource = True
61def get_protections_paginator(client):
62 return Paginator(
63 client.list_protections,
64 {'input_token': 'NextToken', 'output_token': 'NextToken', 'result_key': 'Protections'},
65 client.meta.service_model.operation_model('ListProtections'))
68def get_type_protections(client, arn_type):
69 pager = get_protections_paginator(client)
70 pager.PAGE_ITERATOR_CLS = RetryPageIterator
71 try:
72 protections = pager.paginate().build_full_result().get('Protections', [])
73 except client.exceptions.ResourceNotFoundException:
74 # shield is not enabled in the account, so all resources are not protected
75 return []
76 return [p for p in protections if arn_type in p['ResourceArn']]
79ShieldRetry = get_retry(('ThrottlingException',))
82class ProtectedResource:
83 """Base class with helper methods for dealing with
84 ARNs of resources protected by Shield
85 """
87 def get_arns(self, resources):
88 return self.manager.get_arns(resources)
90 @property
91 def arn_type(self):
92 return self.manager.get_model().arn_type
95class IsShieldProtected(Filter, ProtectedResource):
97 permissions = ('shield:ListProtections',)
98 schema = type_schema('shield-enabled', state={'type': 'boolean'})
100 def process(self, resources, event=None):
101 client = local_session(self.manager.session_factory).client(
102 'shield', region_name='us-east-1')
104 protections = get_type_protections(client, self.arn_type)
105 protected_resources = {p['ResourceArn'] for p in protections}
107 state = self.data.get('state', False)
108 results = []
110 for arn, r in zip(self.get_arns(resources), resources):
111 r['c7n:ShieldProtected'] = shielded = arn in protected_resources
112 if shielded and state:
113 results.append(r)
114 elif not shielded and not state:
115 results.append(r)
117 return results
120class SetShieldProtection(BaseAction, ProtectedResource):
121 """Enable shield protection on applicable resource.
123 setting `sync` parameter will also clear out stale shield protections
124 for resources that no longer exist.
125 """
127 permissions = ('shield:CreateProtection', 'shield:ListProtections',)
128 schema = type_schema(
129 'set-shield',
130 state={'type': 'boolean'}, sync={'type': 'boolean'})
132 def process(self, resources):
133 client = local_session(self.manager.session_factory).client(
134 'shield', region_name='us-east-1')
135 model = self.manager.get_model()
136 protections = get_type_protections(client, self.arn_type)
137 protected_resources = {p['ResourceArn']: p for p in protections}
138 state = self.data.get('state', True)
140 if self.data.get('sync', False):
141 self.clear_stale(client, protections)
143 for arn, r in zip(self.get_arns(resources), resources):
144 if state and arn in protected_resources:
145 continue
146 if state is False and arn in protected_resources:
147 ShieldRetry(
148 client.delete_protection,
149 ProtectionId=protected_resources[arn]['Id'])
150 continue
151 try:
152 ShieldRetry(
153 client.create_protection,
154 Name=r[model.name], ResourceArn=arn)
155 except ClientError as e:
156 if e.response['Error']['Code'] == 'ResourceAlreadyExistsException':
157 continue
158 if e.response['Error']['Code'] == 'InvalidParameterException':
159 # CloudFront distributions with pricing plans cannot have Shield Advanced
160 # enabled. Skip these resources gracefully.
161 # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/flat-rate-pricing-plan.html
162 if 'CloudFront Pricing Plan' in str(e):
163 self.log.warning(
164 "Skipping Shield protection for %s: distribution has a "
165 "CloudFront pricing plan subscription which does not support "
166 "Shield Advanced", arn)
167 continue
168 raise
170 def clear_stale(self, client, protections):
171 # Get all resources unfiltered
172 resources = self.manager.get_resource_manager(
173 self.manager.type).resources()
174 resource_arns = set(self.get_arns(resources))
176 pmap = {}
177 # Only process stale resources in region for non global resources.
178 global_resource = getattr(self.manager.resource_type, 'global_resource', False)
179 for p in protections:
180 if not global_resource and self.manager.region not in p['ResourceArn']:
181 continue
182 pmap[p['ResourceArn']] = p
184 # Find any protections for resources that don't exist
185 stale = set(pmap).difference(resource_arns)
186 self.log.info("clearing %d stale protections", len(stale))
187 for s in stale:
188 ShieldRetry(
189 client.delete_protection, ProtectionId=pmap[s]['Id'])
192class ProtectedEIP:
193 """Contains helper methods for dealing with Elastic IP within Shield API calls.
194 The Elastic IP resource type as described in IAM is "elastic-ip":
195 https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonec2.html#amazonec2-elastic-ip
197 But Shield requires the resource type to be "eip-allocation":
198 https://docs.aws.amazon.com/waf/latest/DDOSAPIReference/API_CreateProtection.html
199 """
201 def get_arns(self, resources):
202 arns = [
203 arn.replace(':elastic-ip', ':eip-allocation')
204 if ':elastic-ip' in arn else arn
205 for arn in
206 self.manager.get_arns(resources)
207 ]
208 return arns
210 @property
211 def arn_type(self):
212 return 'eip-allocation'
215class IsEIPShieldProtected(ProtectedEIP, IsShieldProtected):
216 pass
219class SetEIPShieldProtection(ProtectedEIP, SetShieldProtection):
220 pass
223@ShieldProtection.action_registry.register('tag')
224class TagResource(Tag):
225 """Action to tag a Shield resources
226 """
227 permissions = ('shield:TagResource',)
229 def process_resource_set(self, client, resource_set, tags):
230 mid = self.manager.resource_type.arn
231 for r in resource_set:
232 try:
233 client.tag_resource(ResourceARN=r[mid], Tags=tags)
234 except client.exceptions.ResourceNotFoundException:
235 continue
238@ShieldProtection.action_registry.register('remove-tag')
239class RemoveTag(RemoveTag):
240 """Action to remove tags from a Shield resource
241 """
242 permissions = ('shield:UntagResource',)
244 def process_resource_set(self, client, resource_set, tag_keys):
245 mid = self.manager.resource_type.arn
246 for r in resource_set:
247 try:
248 client.untag_resource(ResourceARN=r[mid], TagKeys=tag_keys)
249 except client.exceptions.ResourceNotFoundException:
250 continue
253@ShieldProtection.filter_registry.register('marked-for-op', TagActionFilter)
254@ShieldProtection.action_registry.register('mark-for-op')
255class MarkShieldProtectionForOp(TagDelayedAction):
256 """Mark Shield Protection for deferred action
258 :example:
260 .. code-block:: yaml
262 policies:
263 - name: shield-protection-invalid-tag-mark
264 resource: shield-protection
265 filters:
266 - "tag:InvalidTag": present
267 actions:
268 - type: mark-for-op
269 op: delete
270 days: 1
271 """