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

144 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3from botocore.exceptions import ClientError 

4from botocore.paginate import Paginator 

5 

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 

13 

14 

15class DescribeShieldProtection(DescribeSource): 

16 def augment(self, resources): 

17 client = local_session(self.manager.session_factory).client('shield') 

18 

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)) 

26 

27 

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 

38 

39 source_mapping = { 

40 'describe': DescribeShieldProtection, 

41 'config': ConfigSource 

42 } 

43 

44 

45@resources.register('shield-attack') 

46class ShieldAttack(QueryResourceManager): 

47 

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 

59 

60 

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')) 

66 

67 

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']] 

77 

78 

79ShieldRetry = get_retry(('ThrottlingException',)) 

80 

81 

82class ProtectedResource: 

83 """Base class with helper methods for dealing with 

84 ARNs of resources protected by Shield 

85 """ 

86 

87 def get_arns(self, resources): 

88 return self.manager.get_arns(resources) 

89 

90 @property 

91 def arn_type(self): 

92 return self.manager.get_model().arn_type 

93 

94 

95class IsShieldProtected(Filter, ProtectedResource): 

96 

97 permissions = ('shield:ListProtections',) 

98 schema = type_schema('shield-enabled', state={'type': 'boolean'}) 

99 

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

101 client = local_session(self.manager.session_factory).client( 

102 'shield', region_name='us-east-1') 

103 

104 protections = get_type_protections(client, self.arn_type) 

105 protected_resources = {p['ResourceArn'] for p in protections} 

106 

107 state = self.data.get('state', False) 

108 results = [] 

109 

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) 

116 

117 return results 

118 

119 

120class SetShieldProtection(BaseAction, ProtectedResource): 

121 """Enable shield protection on applicable resource. 

122 

123 setting `sync` parameter will also clear out stale shield protections 

124 for resources that no longer exist. 

125 """ 

126 

127 permissions = ('shield:CreateProtection', 'shield:ListProtections',) 

128 schema = type_schema( 

129 'set-shield', 

130 state={'type': 'boolean'}, sync={'type': 'boolean'}) 

131 

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) 

139 

140 if self.data.get('sync', False): 

141 self.clear_stale(client, protections) 

142 

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 

159 

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)) 

165 

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 

173 

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']) 

180 

181 

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 

186 

187 But Shield requires the resource type to be "eip-allocation": 

188 https://docs.aws.amazon.com/waf/latest/DDOSAPIReference/API_CreateProtection.html 

189 """ 

190 

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 

199 

200 @property 

201 def arn_type(self): 

202 return 'eip-allocation' 

203 

204 

205class IsEIPShieldProtected(ProtectedEIP, IsShieldProtected): 

206 pass 

207 

208 

209class SetEIPShieldProtection(ProtectedEIP, SetShieldProtection): 

210 pass 

211 

212 

213@ShieldProtection.action_registry.register('tag') 

214class TagResource(Tag): 

215 """Action to tag a Shield resources 

216 """ 

217 permissions = ('shield:TagResource',) 

218 

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 

226 

227 

228@ShieldProtection.action_registry.register('remove-tag') 

229class RemoveTag(RemoveTag): 

230 """Action to remove tags from a Shield resource 

231 """ 

232 permissions = ('shield:UntagResource',) 

233 

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 

241 

242 

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 

247 

248 :example: 

249 

250 .. code-block:: yaml 

251 

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 """