Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/shield.py: 47%
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 raise
160 def clear_stale(self, client, protections):
161 # Get all resources unfiltered
162 resources = self.manager.get_resource_manager(
163 self.manager.type).resources()
164 resource_arns = set(self.get_arns(resources))
166 pmap = {}
167 # Only process stale resources in region for non global resources.
168 global_resource = getattr(self.manager.resource_type, 'global_resource', False)
169 for p in protections:
170 if not global_resource and self.manager.region not in p['ResourceArn']:
171 continue
172 pmap[p['ResourceArn']] = p
174 # Find any protections for resources that don't exist
175 stale = set(pmap).difference(resource_arns)
176 self.log.info("clearing %d stale protections", len(stale))
177 for s in stale:
178 ShieldRetry(
179 client.delete_protection, ProtectionId=pmap[s]['Id'])
182class ProtectedEIP:
183 """Contains helper methods for dealing with Elastic IP within Shield API calls.
184 The Elastic IP resource type as described in IAM is "elastic-ip":
185 https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonec2.html#amazonec2-elastic-ip
187 But Shield requires the resource type to be "eip-allocation":
188 https://docs.aws.amazon.com/waf/latest/DDOSAPIReference/API_CreateProtection.html
189 """
191 def get_arns(self, resources):
192 arns = [
193 arn.replace(':elastic-ip', ':eip-allocation')
194 if ':elastic-ip' in arn else arn
195 for arn in
196 self.manager.get_arns(resources)
197 ]
198 return arns
200 @property
201 def arn_type(self):
202 return 'eip-allocation'
205class IsEIPShieldProtected(ProtectedEIP, IsShieldProtected):
206 pass
209class SetEIPShieldProtection(ProtectedEIP, SetShieldProtection):
210 pass
213@ShieldProtection.action_registry.register('tag')
214class TagResource(Tag):
215 """Action to tag a Shield resources
216 """
217 permissions = ('shield:TagResource',)
219 def process_resource_set(self, client, resource_set, tags):
220 mid = self.manager.resource_type.arn
221 for r in resource_set:
222 try:
223 client.tag_resource(ResourceARN=r[mid], Tags=tags)
224 except client.exceptions.ResourceNotFoundException:
225 continue
228@ShieldProtection.action_registry.register('remove-tag')
229class RemoveTag(RemoveTag):
230 """Action to remove tags from a Shield resource
231 """
232 permissions = ('shield:UntagResource',)
234 def process_resource_set(self, client, resource_set, tag_keys):
235 mid = self.manager.resource_type.arn
236 for r in resource_set:
237 try:
238 client.untag_resource(ResourceARN=r[mid], TagKeys=tag_keys)
239 except client.exceptions.ResourceNotFoundException:
240 continue
243@ShieldProtection.filter_registry.register('marked-for-op', TagActionFilter)
244@ShieldProtection.action_registry.register('mark-for-op')
245class MarkShieldProtectionForOp(TagDelayedAction):
246 """Mark Shield Protection for deferred action
248 :example:
250 .. code-block:: yaml
252 policies:
253 - name: shield-protection-invalid-tag-mark
254 resource: shield-protection
255 filters:
256 - "tag:InvalidTag": present
257 actions:
258 - type: mark-for-op
259 op: delete
260 days: 1
261 """