Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/c7n/resources/vpc.py: 44%

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

1520 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3import itertools 

4import zlib 

5import re 

6from c7n.actions import BaseAction, ModifyVpcSecurityGroupsAction 

7from c7n.deprecated import DeprecatedField 

8from c7n.exceptions import PolicyValidationError, ClientError 

9from c7n.filters import Filter, ValueFilter, MetricsFilter, ListItemFilter 

10import c7n.filters.vpc as net_filters 

11from c7n.filters.iamaccess import CrossAccountAccessFilter 

12from c7n.filters.related import RelatedResourceFilter, RelatedResourceByIdFilter 

13from c7n.filters.revisions import Diff 

14from c7n import query, resolver 

15from c7n.manager import resources 

16from c7n.resources.securityhub import OtherResourcePostFinding, PostFinding 

17from c7n.utils import ( 

18 chunks, 

19 get_eni_resource_type, 

20 get_retry, 

21 jmespath_compile, 

22 jmespath_search, 

23 local_session, 

24 merge_dict, 

25 parse_cidr, 

26 type_schema, 

27) 

28from c7n.resources.aws import shape_validate 

29from c7n.resources.shield import IsEIPShieldProtected, SetEIPShieldProtection 

30from c7n.filters.policystatement import HasStatementFilter 

31 

32 

33@resources.register('vpc') 

34class Vpc(query.QueryResourceManager): 

35 

36 class resource_type(query.TypeInfo): 

37 service = 'ec2' 

38 arn_type = 'vpc' 

39 enum_spec = ('describe_vpcs', 'Vpcs', None) 

40 name = id = 'VpcId' 

41 filter_name = 'VpcIds' 

42 filter_type = 'list' 

43 cfn_type = config_type = 'AWS::EC2::VPC' 

44 id_prefix = "vpc-" 

45 

46 

47@Vpc.filter_registry.register('metrics') 

48class VpcMetrics(MetricsFilter): 

49 

50 def get_dimensions(self, resource): 

51 return [{"Name": "Per-VPC Metrics", 

52 "Value": resource["VpcId"]}] 

53 

54 

55@Vpc.action_registry.register('modify') 

56class ModifyVpc(BaseAction): 

57 """Modify vpc settings 

58 """ 

59 

60 schema = type_schema( 

61 'modify', 

62 **{'dnshostnames': {'type': 'boolean'}, 

63 'dnssupport': {'type': 'boolean'}, 

64 'addressusage': {'type': 'boolean'}} 

65 ) 

66 

67 key_params = ( 

68 ('dnshostnames', 'EnableDnsHostnames'), 

69 ('dnssupport', 'EnableDnsSupport'), 

70 ('addressusage', 'EnableNetworkAddressUsageMetrics') 

71 ) 

72 

73 permissions = ('ec2:ModifyVpcAttribute',) 

74 

75 def process(self, resources): 

76 client = local_session(self.manager.session_factory).client('ec2') 

77 

78 for policy_key, param_name in self.key_params: 

79 if policy_key not in self.data: 

80 continue 

81 params = {param_name: {'Value': self.data[policy_key]}} 

82 # can only modify one attribute per request 

83 for r in resources: 

84 params['VpcId'] = r['VpcId'] 

85 client.modify_vpc_attribute(**params) 

86 

87 

88@Vpc.action_registry.register('delete-empty') 

89class DeleteVpc(BaseAction): 

90 """Delete an empty VPC 

91 

92 For example, if you want to delete an empty VPC 

93 

94 :example: 

95 

96 .. code-block:: yaml 

97 

98 - name: aws-ec2-vpc-delete 

99 resource: vpc 

100 actions: 

101 - type: delete-empty 

102 

103 """ 

104 schema = type_schema('delete-empty',) 

105 permissions = ('ec2:DeleteVpc',) 

106 

107 def process(self, resources): 

108 client = local_session(self.manager.session_factory).client('ec2') 

109 

110 for vpc in resources: 

111 self.manager.retry( 

112 client.delete_vpc, 

113 VpcId=vpc['VpcId'], 

114 ignore_err_codes=( 

115 'NoSuchEntityException', 

116 'DeleteConflictException', 

117 ), 

118 ) 

119 

120 

121class DescribeFlow(query.DescribeSource): 

122 

123 def get_resources(self, ids, cache=True): 

124 params = {'Filters': [{'Name': 'flow-log-id', 'Values': ids}]} 

125 return self.query.filter(self.resource_manager, **params) 

126 

127 

128@resources.register('flow-log') 

129class FlowLog(query.QueryResourceManager): 

130 

131 class resource_type(query.TypeInfo): 

132 

133 service = 'ec2' 

134 arn_type = 'vpc-flow-log' 

135 enum_spec = ('describe_flow_logs', 'FlowLogs', None) 

136 name = id = 'FlowLogId' 

137 cfn_type = config_type = 'AWS::EC2::FlowLog' 

138 id_prefix = 'fl-' 

139 

140 source_mapping = { 

141 'describe': DescribeFlow, 

142 'config': query.ConfigSource 

143 } 

144 

145 

146@Vpc.filter_registry.register('flow-logs') 

147class FlowLogv2Filter(ListItemFilter): 

148 """Are flow logs enabled on the resource. 

149 

150 This filter reuses `list-item` filter for arbitrary filtering 

151 on the flow log attibutes, it also maintains compatiblity 

152 with the legacy flow-log filter. 

153 

154 ie to find all vpcs with flows logs disabled we can do this 

155 

156 :example: 

157 

158 .. code-block:: yaml 

159 

160 policies: 

161 - name: flow-logs-enabled 

162 resource: vpc 

163 filters: 

164 - flow-logs 

165 

166 or to find all vpcs with flow logs but that don't match a 

167 particular configuration. 

168 

169 :example: 

170 

171 .. code-block:: yaml 

172 

173 policies: 

174 - name: flow-mis-configured 

175 resource: vpc 

176 filters: 

177 - not: 

178 - type: flow-logs 

179 attrs: 

180 - TrafficType: ALL 

181 - FlowLogStatus: ACTIVE 

182 - LogGroupName: vpc-logs 

183 """ 

184 

185 legacy_schema = { 

186 'enabled': {'type': 'boolean', 'default': False}, 

187 'op': {'enum': ['equal', 'not-equal'], 'default': 'equal'}, 

188 'set-op': {'enum': ['or', 'and'], 'default': 'or'}, 

189 'status': {'enum': ['active']}, 

190 'deliver-status': {'enum': ['success', 'failure']}, 

191 'destination': {'type': 'string'}, 

192 'destination-type': {'enum': ['s3', 'cloud-watch-logs']}, 

193 'traffic-type': {'enum': ['accept', 'reject', 'all']}, 

194 'log-format': {'type': 'string'}, 

195 'log-group': {'type': 'string'} 

196 } 

197 

198 schema = type_schema( 

199 'flow-logs', 

200 attrs={'$ref': '#/definitions/filters_common/list_item_attrs'}, 

201 count={'type': 'number'}, 

202 count_op={'$ref': '#/definitions/filters_common/comparison_operators'}, 

203 **legacy_schema 

204 ) 

205 schema_alias = True 

206 annotate_items = True 

207 permissions = ('ec2:DescribeFlowLogs',) 

208 

209 compat_conversion = { 

210 'status': { 

211 'key': 'FlowLogStatus', 

212 'values': {'active': 'ACTIVE'}, 

213 }, 

214 'deliver-status': { 

215 'key': 'DeliverLogsStatus', 

216 'values': {'success': 'SUCCESS', 

217 'failure': 'FAILED'} 

218 }, 

219 'destination': { 

220 'key': 'LogDestination', 

221 }, 

222 'destination-type': { 

223 'key': 'LogDestinationType', 

224 # values ? 

225 }, 

226 'traffic-type': { 

227 'key': 'TrafficType', 

228 'values': {'all': 'ALL', 

229 'reject': 'REJECT', 

230 'accept': 'ACCEPT'}, 

231 }, 

232 'log-format': { 

233 'key': 'LogFormat', 

234 }, 

235 'log-group': { 

236 'key': 'LogGroupName' 

237 } 

238 } 

239 

240 flow_log_map = None 

241 

242 def get_deprecations(self): 

243 filter_name = self.data["type"] 

244 return [ 

245 DeprecatedField(f"{filter_name}.{k}", "use list-item style attrs and set operators") 

246 for k in set(self.legacy_schema).intersection(self.data) 

247 ] 

248 

249 def validate(self): 

250 keys = set(self.data) 

251 if 'attrs' in keys and keys.intersection(self.compat_conversion): 

252 raise PolicyValidationError( 

253 "flow-log filter doesn't allow combining legacy keys with list-item attrs") 

254 return super().validate() 

255 

256 def convert(self): 

257 self.source_data = {} 

258 # no mixing of legacy and list-item style 

259 if 'attrs' in self.data: 

260 return 

261 data = {} 

262 if self.data.get('enabled', False): 

263 data['count_op'] = 'gte' 

264 data['count'] = 1 

265 else: 

266 data['count'] = 0 

267 attrs = [] 

268 for k in self.compat_conversion: 

269 if k not in self.data: 

270 continue 

271 afilter = {} 

272 cinfo = self.compat_conversion[k] 

273 ak = cinfo['key'] 

274 av = self.data[k] 

275 if 'values' in cinfo: 

276 av = cinfo['values'][av] 

277 if 'op' in self.data and self.data['op'] == 'not-equal': 

278 av = {'value': av, 'op': 'not-equal'} 

279 afilter[ak] = av 

280 attrs.append(afilter) 

281 if attrs: 

282 data['attrs'] = attrs 

283 data['type'] = self.type 

284 self.source_data = self.data 

285 self.data = data 

286 

287 def get_item_values(self, resource): 

288 flogs = self.flow_log_map.get(resource[self.manager.resource_type.id], ()) 

289 # compatibility with v1 filter, we also add list-item annotation 

290 # for matched flow logs 

291 resource['c7n:flow-logs'] = flogs 

292 

293 # set operators are a little odd, but for list-item do require 

294 # some runtime modification to ensure compatiblity. 

295 if self.source_data.get('set-op', 'or') == 'and': 

296 self.data['count'] = len(flogs) 

297 return flogs 

298 

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

300 self.convert() 

301 self.flow_log_map = {} 

302 for r in self.manager.get_resource_manager('flow-log').resources(): 

303 self.flow_log_map.setdefault(r['ResourceId'], []).append(r) 

304 return super().process(resources, event) 

305 

306 

307@Vpc.filter_registry.register('security-group') 

308class VpcSecurityGroupFilter(RelatedResourceFilter): 

309 """Filter VPCs based on Security Group attributes 

310 

311 :example: 

312 

313 .. code-block:: yaml 

314 

315 policies: 

316 - name: vpc-by-sg 

317 resource: vpc 

318 filters: 

319 - type: security-group 

320 key: tag:Color 

321 value: Gray 

322 """ 

323 schema = type_schema( 

324 'security-group', rinherit=ValueFilter.schema, 

325 **{'match-resource': {'type': 'boolean'}, 

326 'operator': {'enum': ['and', 'or']}}) 

327 RelatedResource = "c7n.resources.vpc.SecurityGroup" 

328 RelatedIdsExpression = '[SecurityGroups][].GroupId' 

329 AnnotationKey = "matched-vpcs" 

330 

331 def get_related_ids(self, resources): 

332 vpc_ids = [vpc['VpcId'] for vpc in resources] 

333 vpc_group_ids = { 

334 g['GroupId'] for g in 

335 self.manager.get_resource_manager('security-group').resources() 

336 if g.get('VpcId', '') in vpc_ids 

337 } 

338 return vpc_group_ids 

339 

340 

341@Vpc.filter_registry.register('subnet') 

342class VpcSubnetFilter(RelatedResourceFilter): 

343 """Filter VPCs based on Subnet attributes 

344 

345 :example: 

346 

347 .. code-block:: yaml 

348 

349 policies: 

350 - name: vpc-by-subnet 

351 resource: vpc 

352 filters: 

353 - type: subnet 

354 key: tag:Color 

355 value: Gray 

356 """ 

357 schema = type_schema( 

358 'subnet', rinherit=ValueFilter.schema, 

359 **{'match-resource': {'type': 'boolean'}, 

360 'operator': {'enum': ['and', 'or']}}) 

361 RelatedResource = "c7n.resources.vpc.Subnet" 

362 RelatedIdsExpression = '[Subnets][].SubnetId' 

363 AnnotationKey = "MatchedVpcsSubnets" 

364 

365 def get_related_ids(self, resources): 

366 vpc_ids = [vpc['VpcId'] for vpc in resources] 

367 vpc_subnet_ids = { 

368 g['SubnetId'] for g in 

369 self.manager.get_resource_manager('subnet').resources() 

370 if g.get('VpcId', '') in vpc_ids 

371 } 

372 return vpc_subnet_ids 

373 

374 

375@Vpc.filter_registry.register('nat-gateway') 

376class VpcNatGatewayFilter(RelatedResourceFilter): 

377 """Filter VPCs based on NAT Gateway attributes 

378 

379 :example: 

380 

381 .. code-block:: yaml 

382 

383 policies: 

384 - name: vpc-by-nat 

385 resource: vpc 

386 filters: 

387 - type: nat-gateway 

388 key: tag:Color 

389 value: Gray 

390 """ 

391 schema = type_schema( 

392 'nat-gateway', rinherit=ValueFilter.schema, 

393 **{'match-resource': {'type': 'boolean'}, 

394 'operator': {'enum': ['and', 'or']}}) 

395 RelatedResource = "c7n.resources.vpc.NATGateway" 

396 RelatedIdsExpression = '[NatGateways][].NatGatewayId' 

397 AnnotationKey = "MatchedVpcsNatGateways" 

398 

399 def get_related_ids(self, resources): 

400 vpc_ids = [vpc['VpcId'] for vpc in resources] 

401 vpc_natgw_ids = { 

402 g['NatGatewayId'] for g in 

403 self.manager.get_resource_manager('nat-gateway').resources() 

404 if g.get('VpcId', '') in vpc_ids 

405 } 

406 return vpc_natgw_ids 

407 

408 

409@Vpc.filter_registry.register('internet-gateway') 

410class VpcInternetGatewayFilter(RelatedResourceFilter): 

411 """Filter VPCs based on Internet Gateway attributes 

412 

413 :example: 

414 

415 .. code-block:: yaml 

416 

417 policies: 

418 - name: vpc-by-igw 

419 resource: vpc 

420 filters: 

421 - type: internet-gateway 

422 key: tag:Color 

423 value: Gray 

424 """ 

425 schema = type_schema( 

426 'internet-gateway', rinherit=ValueFilter.schema, 

427 **{'match-resource': {'type': 'boolean'}, 

428 'operator': {'enum': ['and', 'or']}}) 

429 RelatedResource = "c7n.resources.vpc.InternetGateway" 

430 RelatedIdsExpression = '[InternetGateways][].InternetGatewayId' 

431 AnnotationKey = "MatchedVpcsIgws" 

432 

433 def get_related_ids(self, resources): 

434 vpc_ids = [vpc['VpcId'] for vpc in resources] 

435 vpc_igw_ids = set() 

436 for igw in self.manager.get_resource_manager('internet-gateway').resources(): 

437 for attachment in igw['Attachments']: 

438 if attachment.get('VpcId', '') in vpc_ids: 

439 vpc_igw_ids.add(igw['InternetGatewayId']) 

440 return vpc_igw_ids 

441 

442 

443@Vpc.filter_registry.register('vpc-attributes') 

444class AttributesFilter(Filter): 

445 """Filters VPCs based on their DNS attributes 

446 

447 :example: 

448 

449 .. code-block:: yaml 

450 

451 policies: 

452 - name: dns-hostname-enabled 

453 resource: vpc 

454 filters: 

455 - type: vpc-attributes 

456 dnshostnames: True 

457 """ 

458 schema = type_schema( 

459 'vpc-attributes', 

460 dnshostnames={'type': 'boolean'}, 

461 addressusage={'type': 'boolean'}, 

462 dnssupport={'type': 'boolean'}) 

463 

464 permissions = ('ec2:DescribeVpcAttribute',) 

465 

466 key_params = ( 

467 ('dnshostnames', 'enableDnsHostnames'), 

468 ('dnssupport', 'enableDnsSupport'), 

469 ('addressusage', 'enableNetworkAddressUsageMetrics') 

470 ) 

471 annotation_key = 'c7n:attributes' 

472 

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

474 results = [] 

475 client = local_session(self.manager.session_factory).client('ec2') 

476 

477 for r in resources: 

478 found = True 

479 for policy_key, vpc_attr in self.key_params: 

480 if policy_key not in self.data: 

481 continue 

482 policy_value = self.data[policy_key] 

483 response_attr = "%s%s" % (vpc_attr[0].upper(), vpc_attr[1:]) 

484 value = client.describe_vpc_attribute( 

485 VpcId=r['VpcId'], 

486 Attribute=vpc_attr 

487 ) 

488 value = value[response_attr]['Value'] 

489 r.setdefault(self.annotation_key, {})[policy_key] = value 

490 if policy_value != value: 

491 found = False 

492 break 

493 if found: 

494 results.append(r) 

495 return results 

496 

497 

498@Vpc.filter_registry.register('dhcp-options') 

499class DhcpOptionsFilter(Filter): 

500 """Filter VPCs based on their dhcp options 

501 

502 :example: 

503 

504 .. code-block:: yaml 

505 

506 policies: 

507 - name: vpcs-in-domain 

508 resource: vpc 

509 filters: 

510 - type: dhcp-options 

511 domain-name: ec2.internal 

512 

513 if an option value is specified as a list, then all elements must be present. 

514 if an option value is specified as a string, then that string must be present. 

515 

516 vpcs not matching a given option value can be found via specifying 

517 a `present: false` parameter. 

518 

519 """ 

520 

521 option_keys = ('domain-name', 'domain-name-servers', 'ntp-servers') 

522 schema = type_schema('dhcp-options', **{ 

523 k: {'oneOf': [ 

524 {'type': 'array', 'items': {'type': 'string'}}, 

525 {'type': 'string'}]} 

526 for k in option_keys}) 

527 schema['properties']['present'] = {'type': 'boolean'} 

528 permissions = ('ec2:DescribeDhcpOptions',) 

529 

530 def validate(self): 

531 if not any([self.data.get(k) for k in self.option_keys]): 

532 raise PolicyValidationError("one of %s required" % (self.option_keys,)) 

533 return self 

534 

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

536 client = local_session(self.manager.session_factory).client('ec2') 

537 option_ids = [r['DhcpOptionsId'] for r in resources] 

538 options_map = {} 

539 results = [] 

540 for options in client.describe_dhcp_options( 

541 Filters=[{ 

542 'Name': 'dhcp-options-id', 

543 'Values': option_ids}]).get('DhcpOptions', ()): 

544 options_map[options['DhcpOptionsId']] = { 

545 o['Key']: [v['Value'] for v in o['Values']] 

546 for o in options['DhcpConfigurations']} 

547 

548 for vpc in resources: 

549 if self.process_vpc(vpc, options_map[vpc['DhcpOptionsId']]): 

550 results.append(vpc) 

551 return results 

552 

553 def process_vpc(self, vpc, dhcp): 

554 vpc['c7n:DhcpConfiguration'] = dhcp 

555 found = True 

556 for k in self.option_keys: 

557 if k not in self.data: 

558 continue 

559 is_list = isinstance(self.data[k], list) 

560 if k not in dhcp: 

561 found = False 

562 elif not is_list and self.data[k] not in dhcp[k]: 

563 found = False 

564 elif is_list and sorted(self.data[k]) != sorted(dhcp[k]): 

565 found = False 

566 if not self.data.get('present', True): 

567 found = not found 

568 return found 

569 

570 

571@Vpc.action_registry.register('post-finding') 

572class VpcPostFinding(PostFinding): 

573 

574 resource_type = "AwsEc2Vpc" 

575 

576 def format_resource(self, r): 

577 envelope, payload = self.format_envelope(r) 

578 # more inane sechub formatting deltas 

579 detail = { 

580 'DhcpOptionsId': r.get('DhcpOptionsId'), 

581 'State': r['State']} 

582 

583 for assoc in r.get('CidrBlockAssociationSet', ()): 

584 detail.setdefault('CidrBlockAssociationSet', []).append(dict( 

585 AssociationId=assoc['AssociationId'], 

586 CidrBlock=assoc['CidrBlock'], 

587 CidrBlockState=assoc['CidrBlockState']['State'])) 

588 

589 for assoc in r.get('Ipv6CidrBlockAssociationSet', ()): 

590 detail.setdefault('Ipv6CidrBlockAssociationSet', []).append(dict( 

591 AssociationId=assoc['AssociationId'], 

592 Ipv6CidrBlock=assoc['Ipv6CidrBlock'], 

593 CidrBlockState=assoc['Ipv6CidrBlockState']['State'])) 

594 payload.update(self.filter_empty(detail)) 

595 return envelope 

596 

597 

598class DescribeSubnets(query.DescribeSource): 

599 

600 def get_resources(self, resource_ids): 

601 while resource_ids: 

602 try: 

603 return super().get_resources(resource_ids) 

604 except ClientError as e: 

605 if e.response['Error']['Code'] != 'InvalidSubnetID.NotFound': 

606 raise 

607 sid = extract_subnet_id(e) 

608 if sid: 

609 resource_ids.remove(sid) 

610 else: 

611 return [] 

612 

613 

614RE_ERROR_SUBNET_ID = re.compile("'(?P<subnet_id>subnet-.*?)'") 

615 

616 

617def extract_subnet_id(state_error): 

618 "Extract an subnet id from an error" 

619 subnet_id = None 

620 match = RE_ERROR_SUBNET_ID.search(str(state_error)) 

621 if match: 

622 subnet_id = match.groupdict().get('subnet_id') 

623 return subnet_id 

624 

625 

626@resources.register('subnet') 

627class Subnet(query.QueryResourceManager): 

628 

629 class resource_type(query.TypeInfo): 

630 service = 'ec2' 

631 arn_type = 'subnet' 

632 enum_spec = ('describe_subnets', 'Subnets', None) 

633 name = id = 'SubnetId' 

634 filter_name = 'SubnetIds' 

635 filter_type = 'list' 

636 cfn_type = config_type = 'AWS::EC2::Subnet' 

637 id_prefix = "subnet-" 

638 

639 source_mapping = { 

640 'describe': DescribeSubnets, 

641 'config': query.ConfigSource} 

642 

643 

644Subnet.filter_registry.register('flow-logs', FlowLogv2Filter) 

645 

646 

647@Subnet.filter_registry.register('vpc') 

648class SubnetVpcFilter(net_filters.VpcFilter): 

649 

650 RelatedIdsExpression = "VpcId" 

651 

652 

653@Subnet.filter_registry.register('ip-address-usage') 

654class SubnetIpAddressUsageFilter(ValueFilter): 

655 """Filter subnets based on available IP addresses. 

656 

657 :example: 

658 

659 Show subnets with no addresses in use. 

660 

661 .. code-block:: yaml 

662 

663 policies: 

664 - name: empty-subnets 

665 resource: aws.subnet 

666 filters: 

667 - type: ip-address-usage 

668 key: NumberUsed 

669 value: 0 

670 

671 :example: 

672 

673 Show subnets where 90% or more addresses are in use. 

674 

675 .. code-block:: yaml 

676 

677 policies: 

678 - name: almost-full-subnets 

679 resource: aws.subnet 

680 filters: 

681 - type: ip-address-usage 

682 key: PercentUsed 

683 op: greater-than 

684 value: 90 

685 

686 This filter allows ``key`` to be: 

687 

688 * ``MaxAvailable``: the number of addresses available based on a subnet's CIDR block size 

689 (minus the 5 addresses 

690 `reserved by AWS <https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html>`_) 

691 * ``NumberUsed``: ``MaxAvailable`` minus the subnet's ``AvailableIpAddressCount`` value 

692 * ``PercentUsed``: ``NumberUsed`` divided by ``MaxAvailable`` 

693 """ 

694 annotation_key = 'c7n:IpAddressUsage' 

695 aws_reserved_addresses = 5 

696 schema_alias = False 

697 schema = type_schema( 

698 'ip-address-usage', 

699 key={'enum': ['MaxAvailable', 'NumberUsed', 'PercentUsed']}, 

700 rinherit=ValueFilter.schema, 

701 ) 

702 

703 def augment(self, resource): 

704 cidr_block = parse_cidr(resource['CidrBlock']) 

705 max_addresses = cidr_block.num_addresses - self.aws_reserved_addresses 

706 resource[self.annotation_key] = dict( 

707 MaxAvailable=max_addresses, 

708 NumberUsed=max_addresses - resource['AvailableIpAddressCount'], 

709 PercentUsed=round( 

710 (max_addresses - resource['AvailableIpAddressCount']) / max_addresses * 100.0, 

711 2 

712 ), 

713 ) 

714 

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

716 results = [] 

717 for r in resources: 

718 if self.annotation_key not in r: 

719 self.augment(r) 

720 if self.match(r[self.annotation_key]): 

721 results.append(r) 

722 return results 

723 

724 

725class ConfigSG(query.ConfigSource): 

726 

727 def load_resource(self, item): 

728 r = super(ConfigSG, self).load_resource(item) 

729 for rset in ('IpPermissions', 'IpPermissionsEgress'): 

730 for p in r.get(rset, ()): 

731 if p.get('FromPort', '') is None: 

732 p.pop('FromPort') 

733 if p.get('ToPort', '') is None: 

734 p.pop('ToPort') 

735 if 'Ipv6Ranges' not in p: 

736 p[u'Ipv6Ranges'] = [] 

737 for i in p.get('UserIdGroupPairs', ()): 

738 for k, v in list(i.items()): 

739 if v is None: 

740 i.pop(k) 

741 # legacy config form, still version 1.2 

742 for attribute, element_key in (('IpRanges', u'CidrIp'),): 

743 if attribute not in p: 

744 continue 

745 p[attribute] = [{element_key: v} for v in p[attribute]] 

746 if 'Ipv4Ranges' in p: 

747 p['IpRanges'] = p.pop('Ipv4Ranges') 

748 return r 

749 

750 

751@resources.register('security-group') 

752class SecurityGroup(query.QueryResourceManager): 

753 

754 class resource_type(query.TypeInfo): 

755 service = 'ec2' 

756 arn_type = 'security-group' 

757 enum_spec = ('describe_security_groups', 'SecurityGroups', None) 

758 id = 'GroupId' 

759 name = 'GroupName' 

760 filter_name = "GroupIds" 

761 filter_type = 'list' 

762 cfn_type = config_type = "AWS::EC2::SecurityGroup" 

763 id_prefix = "sg-" 

764 

765 source_mapping = { 

766 'config': ConfigSG, 

767 'describe': query.DescribeSource 

768 } 

769 

770 

771@SecurityGroup.filter_registry.register('diff') 

772class SecurityGroupDiffFilter(Diff): 

773 

774 def diff(self, source, target): 

775 differ = SecurityGroupDiff() 

776 return differ.diff(source, target) 

777 

778 

779class SecurityGroupDiff: 

780 """Diff two versions of a security group 

781 

782 Immutable: GroupId, GroupName, Description, VpcId, OwnerId 

783 Mutable: Tags, Rules 

784 """ 

785 

786 def diff(self, source, target): 

787 delta = {} 

788 tag_delta = self.get_tag_delta(source, target) 

789 if tag_delta: 

790 delta['tags'] = tag_delta 

791 ingress_delta = self.get_rule_delta('IpPermissions', source, target) 

792 if ingress_delta: 

793 delta['ingress'] = ingress_delta 

794 egress_delta = self.get_rule_delta( 

795 'IpPermissionsEgress', source, target) 

796 if egress_delta: 

797 delta['egress'] = egress_delta 

798 if delta: 

799 return delta 

800 

801 def get_tag_delta(self, source, target): 

802 source_tags = {t['Key']: t['Value'] for t in source.get('Tags', ())} 

803 target_tags = {t['Key']: t['Value'] for t in target.get('Tags', ())} 

804 target_keys = set(target_tags.keys()) 

805 source_keys = set(source_tags.keys()) 

806 removed = source_keys.difference(target_keys) 

807 added = target_keys.difference(source_keys) 

808 changed = set() 

809 for k in target_keys.intersection(source_keys): 

810 if source_tags[k] != target_tags[k]: 

811 changed.add(k) 

812 return {k: v for k, v in { 

813 'added': {k: target_tags[k] for k in added}, 

814 'removed': {k: source_tags[k] for k in removed}, 

815 'updated': {k: target_tags[k] for k in changed}}.items() if v} 

816 

817 def get_rule_delta(self, key, source, target): 

818 source_rules = { 

819 self.compute_rule_hash(r): r for r in source.get(key, ())} 

820 target_rules = { 

821 self.compute_rule_hash(r): r for r in target.get(key, ())} 

822 source_keys = set(source_rules.keys()) 

823 target_keys = set(target_rules.keys()) 

824 removed = source_keys.difference(target_keys) 

825 added = target_keys.difference(source_keys) 

826 return {k: v for k, v in 

827 {'removed': [source_rules[rid] for rid in sorted(removed)], 

828 'added': [target_rules[rid] for rid in sorted(added)]}.items() if v} 

829 

830 RULE_ATTRS = ( 

831 ('PrefixListIds', 'PrefixListId'), 

832 ('UserIdGroupPairs', 'GroupId'), 

833 ('IpRanges', 'CidrIp'), 

834 ('Ipv6Ranges', 'CidrIpv6') 

835 ) 

836 

837 def compute_rule_hash(self, rule): 

838 buf = "%d-%d-%s-" % ( 

839 rule.get('FromPort', 0) or 0, 

840 rule.get('ToPort', 0) or 0, 

841 rule.get('IpProtocol', '-1') or '-1' 

842 ) 

843 for a, ke in self.RULE_ATTRS: 

844 if a not in rule: 

845 continue 

846 ev = [e[ke] for e in rule[a]] 

847 ev.sort() 

848 for e in ev: 

849 buf += "%s-" % e 

850 # mask to generate the same numeric value across all Python versions 

851 return zlib.crc32(buf.encode('ascii')) & 0xffffffff 

852 

853 

854@SecurityGroup.action_registry.register('patch') 

855class SecurityGroupApplyPatch(BaseAction): 

856 """Modify a resource via application of a reverse delta. 

857 """ 

858 schema = type_schema('patch') 

859 

860 permissions = ('ec2:AuthorizeSecurityGroupIngress', 

861 'ec2:AuthorizeSecurityGroupEgress', 

862 'ec2:RevokeSecurityGroupIngress', 

863 'ec2:RevokeSecurityGroupEgress', 

864 'ec2:CreateTags', 

865 'ec2:DeleteTags') 

866 

867 def validate(self): 

868 diff_filters = [n for n in self.manager.iter_filters() if isinstance( 

869 n, SecurityGroupDiffFilter)] 

870 if not len(diff_filters): 

871 raise PolicyValidationError( 

872 "resource patching requires diff filter") 

873 return self 

874 

875 def process(self, resources): 

876 client = local_session(self.manager.session_factory).client('ec2') 

877 differ = SecurityGroupDiff() 

878 patcher = SecurityGroupPatch() 

879 for r in resources: 

880 # reverse the patch by computing fresh, the forward 

881 # patch is for notifications 

882 d = differ.diff(r, r['c7n:previous-revision']['resource']) 

883 patcher.apply_delta(client, r, d) 

884 

885 

886class SecurityGroupPatch: 

887 

888 RULE_TYPE_MAP = { 

889 'egress': ('IpPermissionsEgress', 

890 'revoke_security_group_egress', 

891 'authorize_security_group_egress'), 

892 'ingress': ('IpPermissions', 

893 'revoke_security_group_ingress', 

894 'authorize_security_group_ingress')} 

895 

896 retry = staticmethod(get_retry(( 

897 'RequestLimitExceeded', 'Client.RequestLimitExceeded'))) 

898 

899 def apply_delta(self, client, target, change_set): 

900 if 'tags' in change_set: 

901 self.process_tags(client, target, change_set['tags']) 

902 if 'ingress' in change_set: 

903 self.process_rules( 

904 client, 'ingress', target, change_set['ingress']) 

905 if 'egress' in change_set: 

906 self.process_rules( 

907 client, 'egress', target, change_set['egress']) 

908 

909 def process_tags(self, client, group, tag_delta): 

910 if 'removed' in tag_delta: 

911 self.retry(client.delete_tags, 

912 Resources=[group['GroupId']], 

913 Tags=[{'Key': k} 

914 for k in tag_delta['removed']]) 

915 tags = [] 

916 if 'added' in tag_delta: 

917 tags.extend( 

918 [{'Key': k, 'Value': v} 

919 for k, v in tag_delta['added'].items()]) 

920 if 'updated' in tag_delta: 

921 tags.extend( 

922 [{'Key': k, 'Value': v} 

923 for k, v in tag_delta['updated'].items()]) 

924 if tags: 

925 self.retry( 

926 client.create_tags, Resources=[group['GroupId']], Tags=tags) 

927 

928 def process_rules(self, client, rule_type, group, delta): 

929 _, revoke_op, auth_op = self.RULE_TYPE_MAP[rule_type] 

930 revoke, authorize = getattr( 

931 client, revoke_op), getattr(client, auth_op) 

932 

933 # Process removes 

934 if 'removed' in delta: 

935 self.retry(revoke, GroupId=group['GroupId'], 

936 IpPermissions=[r for r in delta['removed']]) 

937 

938 # Process adds 

939 if 'added' in delta: 

940 self.retry(authorize, GroupId=group['GroupId'], 

941 IpPermissions=[r for r in delta['added']]) 

942 

943 

944class SGUsage(Filter): 

945 

946 nics = () 

947 

948 def get_permissions(self): 

949 return list(itertools.chain( 

950 *[self.manager.get_resource_manager(m).get_permissions() 

951 for m in 

952 ['lambda', 'eni', 'launch-config', 'security-group', 'event-rule-target', 

953 'aws.batch-compute']])) 

954 

955 def filter_peered_refs(self, resources): 

956 if not resources: 

957 return resources 

958 # Check that groups are not referenced across accounts 

959 client = local_session(self.manager.session_factory).client('ec2') 

960 peered_ids = set() 

961 for resource_set in chunks(resources, 200): 

962 for sg_ref in client.describe_security_group_references( 

963 GroupId=[r['GroupId'] for r in resource_set] 

964 )['SecurityGroupReferenceSet']: 

965 peered_ids.add(sg_ref['GroupId']) 

966 self.log.debug( 

967 "%d of %d groups w/ peered refs", len(peered_ids), len(resources)) 

968 return [r for r in resources if r['GroupId'] not in peered_ids] 

969 

970 def get_scanners(self): 

971 return ( 

972 ("nics", self.get_eni_sgs), 

973 ("sg-perm-refs", self.get_sg_refs), 

974 ('lambdas', self.get_lambda_sgs), 

975 ("launch-configs", self.get_launch_config_sgs), 

976 ("ecs-cwe", self.get_ecs_cwe_sgs), 

977 ("codebuild", self.get_codebuild_sgs), 

978 ("batch", self.get_batch_sgs), 

979 ) 

980 

981 def scan_groups(self): 

982 used = set() 

983 for kind, scanner in self.get_scanners(): 

984 sg_ids = scanner() 

985 new_refs = sg_ids.difference(used) 

986 used = used.union(sg_ids) 

987 self.log.debug( 

988 "%s using %d sgs, new refs %s total %s", 

989 kind, len(sg_ids), len(new_refs), len(used)) 

990 

991 return used 

992 

993 def get_launch_config_sgs(self): 

994 # Note assuming we also have launch config garbage collection 

995 # enabled. 

996 sg_ids = set() 

997 for cfg in self.manager.get_resource_manager('launch-config').resources(): 

998 for g in cfg['SecurityGroups']: 

999 sg_ids.add(g) 

1000 for g in cfg['ClassicLinkVPCSecurityGroups']: 

1001 sg_ids.add(g) 

1002 return sg_ids 

1003 

1004 def get_lambda_sgs(self): 

1005 sg_ids = set() 

1006 for func in self.manager.get_resource_manager('lambda').resources(augment=False): 

1007 if 'VpcConfig' not in func: 

1008 continue 

1009 for g in func['VpcConfig']['SecurityGroupIds']: 

1010 sg_ids.add(g) 

1011 return sg_ids 

1012 

1013 def get_eni_sgs(self): 

1014 sg_ids = set() 

1015 self.nics = self.manager.get_resource_manager('eni').resources() 

1016 for nic in self.nics: 

1017 for g in nic['Groups']: 

1018 sg_ids.add(g['GroupId']) 

1019 return sg_ids 

1020 

1021 def get_codebuild_sgs(self): 

1022 sg_ids = set() 

1023 for cb in self.manager.get_resource_manager('codebuild').resources(): 

1024 sg_ids |= set(cb.get('vpcConfig', {}).get('securityGroupIds', [])) 

1025 return sg_ids 

1026 

1027 def get_sg_refs(self): 

1028 sg_ids = set() 

1029 for sg in self.manager.get_resource_manager('security-group').resources(): 

1030 for perm_type in ('IpPermissions', 'IpPermissionsEgress'): 

1031 for p in sg.get(perm_type, []): 

1032 for g in p.get('UserIdGroupPairs', ()): 

1033 # self references aren't usage. 

1034 if g['GroupId'] != sg['GroupId']: 

1035 sg_ids.add(g['GroupId']) 

1036 return sg_ids 

1037 

1038 def get_ecs_cwe_sgs(self): 

1039 sg_ids = set() 

1040 expr = jmespath_compile( 

1041 'EcsParameters.NetworkConfiguration.awsvpcConfiguration.SecurityGroups[]') 

1042 for rule in self.manager.get_resource_manager( 

1043 'event-rule-target').resources(augment=False): 

1044 ids = expr.search(rule) 

1045 if ids: 

1046 sg_ids.update(ids) 

1047 return sg_ids 

1048 

1049 def get_batch_sgs(self): 

1050 expr = jmespath_compile('[].computeResources.securityGroupIds[]') 

1051 resources = self.manager.get_resource_manager('aws.batch-compute').resources(augment=False) 

1052 return set(expr.search(resources) or []) 

1053 

1054 

1055@SecurityGroup.filter_registry.register('unused') 

1056class UnusedSecurityGroup(SGUsage): 

1057 """Filter to just vpc security groups that are not used. 

1058 

1059 We scan all extant enis in the vpc to get a baseline set of groups 

1060 in use. Then augment with those referenced by launch configs, and 

1061 lambdas as they may not have extant resources in the vpc at a 

1062 given moment. We also find any security group with references from 

1063 other security group either within the vpc or across peered 

1064 connections. Also checks cloud watch event targeting ecs. 

1065 

1066 Checks - enis, lambda, launch-configs, sg rule refs, and ecs cwe 

1067 targets. 

1068 

1069 Note this filter does not support classic security groups atm. 

1070 

1071 :example: 

1072 

1073 .. code-block:: yaml 

1074 

1075 policies: 

1076 - name: security-groups-unused 

1077 resource: security-group 

1078 filters: 

1079 - unused 

1080 

1081 """ 

1082 schema = type_schema('unused') 

1083 

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

1085 used = self.scan_groups() 

1086 unused = [ 

1087 r for r in resources 

1088 if r['GroupId'] not in used and 'VpcId' in r] 

1089 return unused and self.filter_peered_refs(unused) or [] 

1090 

1091 

1092@SecurityGroup.filter_registry.register('used') 

1093class UsedSecurityGroup(SGUsage): 

1094 """Filter to security groups that are used. 

1095 This operates as a complement to the unused filter for multi-step 

1096 workflows. 

1097 

1098 :example: 

1099 

1100 .. code-block:: yaml 

1101 

1102 policies: 

1103 - name: security-groups-in-use 

1104 resource: security-group 

1105 filters: 

1106 - used 

1107 

1108 policies: 

1109 - name: security-groups-used-by-rds 

1110 resource: security-group 

1111 filters: 

1112 - used 

1113 - type: value 

1114 key: c7n:InstanceOwnerIds 

1115 op: intersect 

1116 value: 

1117 - amazon-rds 

1118 

1119 policies: 

1120 - name: security-groups-used-by-natgw 

1121 resource: security-group 

1122 filters: 

1123 - used 

1124 - type: value 

1125 key: c7n:InterfaceTypes 

1126 op: intersect 

1127 value: 

1128 - nat_gateway 

1129 

1130 policies: 

1131 - name: security-groups-used-by-alb 

1132 resource: security-group 

1133 filters: 

1134 - used 

1135 - type: value 

1136 key: c7n:InterfaceResourceTypes 

1137 op: intersect 

1138 value: 

1139 - elb-app 

1140 """ 

1141 schema = type_schema('used') 

1142 

1143 instance_owner_id_key = 'c7n:InstanceOwnerIds' 

1144 interface_type_key = 'c7n:InterfaceTypes' 

1145 interface_resource_type_key = 'c7n:InterfaceResourceTypes' 

1146 

1147 def _get_eni_attributes(self): 

1148 group_enis = {} 

1149 for nic in self.nics: 

1150 instance_owner_id, interface_resource_type = '', '' 

1151 if nic['Status'] == 'in-use': 

1152 if nic.get('Attachment') and 'InstanceOwnerId' in nic['Attachment']: 

1153 instance_owner_id = nic['Attachment']['InstanceOwnerId'] 

1154 interface_resource_type = get_eni_resource_type(nic) 

1155 interface_type = nic.get('InterfaceType') 

1156 for g in nic['Groups']: 

1157 group_enis.setdefault(g['GroupId'], []).append({ 

1158 'InstanceOwnerId': instance_owner_id, 

1159 'InterfaceType': interface_type, 

1160 'InterfaceResourceType': interface_resource_type 

1161 }) 

1162 return group_enis 

1163 

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

1165 used = self.scan_groups() 

1166 unused = [ 

1167 r for r in resources 

1168 if r['GroupId'] not in used and 'VpcId' in r] 

1169 unused = {g['GroupId'] for g in self.filter_peered_refs(unused)} 

1170 group_enis = self._get_eni_attributes() 

1171 for r in resources: 

1172 enis = group_enis.get(r['GroupId'], ()) 

1173 r[self.instance_owner_id_key] = list({ 

1174 i['InstanceOwnerId'] for i in enis if i['InstanceOwnerId']}) 

1175 r[self.interface_type_key] = list({ 

1176 i['InterfaceType'] for i in enis if i['InterfaceType']}) 

1177 r[self.interface_resource_type_key] = list({ 

1178 i['InterfaceResourceType'] for i in enis if i['InterfaceResourceType']}) 

1179 return [r for r in resources if r['GroupId'] not in unused] 

1180 

1181 

1182@SecurityGroup.filter_registry.register('stale') 

1183class Stale(Filter): 

1184 """Filter to find security groups that contain stale references 

1185 to other groups that are either no longer present or traverse 

1186 a broken vpc peering connection. Note this applies to VPC 

1187 Security groups only and will implicitly filter security groups. 

1188 

1189 AWS Docs: 

1190 https://docs.aws.amazon.com/vpc/latest/peering/vpc-peering-security-groups.html 

1191 

1192 :example: 

1193 

1194 .. code-block:: yaml 

1195 

1196 policies: 

1197 - name: stale-security-groups 

1198 resource: security-group 

1199 filters: 

1200 - stale 

1201 """ 

1202 schema = type_schema('stale') 

1203 permissions = ('ec2:DescribeStaleSecurityGroups',) 

1204 

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

1206 client = local_session(self.manager.session_factory).client('ec2') 

1207 vpc_ids = {r['VpcId'] for r in resources if 'VpcId' in r} 

1208 group_map = {r['GroupId']: r for r in resources} 

1209 results = [] 

1210 self.log.debug("Querying %d vpc for stale refs", len(vpc_ids)) 

1211 stale_count = 0 

1212 for vpc_id in vpc_ids: 

1213 stale_groups = client.describe_stale_security_groups( 

1214 VpcId=vpc_id).get('StaleSecurityGroupSet', ()) 

1215 

1216 stale_count += len(stale_groups) 

1217 for s in stale_groups: 

1218 if s['GroupId'] in group_map: 

1219 r = group_map[s['GroupId']] 

1220 if 'StaleIpPermissions' in s: 

1221 r['MatchedIpPermissions'] = s['StaleIpPermissions'] 

1222 if 'StaleIpPermissionsEgress' in s: 

1223 r['MatchedIpPermissionsEgress'] = s[ 

1224 'StaleIpPermissionsEgress'] 

1225 results.append(r) 

1226 self.log.debug("Found %d stale security groups", stale_count) 

1227 return results 

1228 

1229 

1230@SecurityGroup.filter_registry.register('default-vpc') 

1231class SGDefaultVpc(net_filters.DefaultVpcBase): 

1232 """Filter that returns any security group that exists within the default vpc 

1233 

1234 :example: 

1235 

1236 .. code-block:: yaml 

1237 

1238 policies: 

1239 - name: security-group-default-vpc 

1240 resource: security-group 

1241 filters: 

1242 - default-vpc 

1243 """ 

1244 

1245 schema = type_schema('default-vpc') 

1246 

1247 def __call__(self, resource, event=None): 

1248 if 'VpcId' not in resource: 

1249 return False 

1250 return self.match(resource['VpcId']) 

1251 

1252 

1253class SGPermission(Filter): 

1254 """Filter for verifying security group ingress and egress permissions 

1255 

1256 All attributes of a security group permission are available as 

1257 value filters. 

1258 

1259 If multiple attributes are specified the permission must satisfy 

1260 all of them. Note that within an attribute match against a list value 

1261 of a permission we default to or. 

1262 

1263 If a group has any permissions that match all conditions, then it 

1264 matches the filter. 

1265 

1266 Permissions that match on the group are annotated onto the group and 

1267 can subsequently be used by the remove-permission action. 

1268 

1269 We have specialized handling for matching `Ports` in ingress/egress 

1270 permission From/To range. The following example matches on ingress 

1271 rules which allow for a range that includes all of the given ports. 

1272 

1273 .. code-block:: yaml 

1274 

1275 - type: ingress 

1276 Ports: [22, 443, 80] 

1277 

1278 As well for verifying that a rule only allows for a specific set of ports 

1279 as in the following example. The delta between this and the previous 

1280 example is that if the permission allows for any ports not specified here, 

1281 then the rule will match. ie. OnlyPorts is a negative assertion match, 

1282 it matches when a permission includes ports outside of the specified set. 

1283 

1284 .. code-block:: yaml 

1285 

1286 - type: ingress 

1287 OnlyPorts: [22] 

1288 

1289 For simplifying ipranges handling which is specified as a list on a rule 

1290 we provide a `Cidr` key which can be used as a value type filter evaluated 

1291 against each of the rules. If any iprange cidr match then the permission 

1292 matches. 

1293 

1294 .. code-block:: yaml 

1295 

1296 - type: ingress 

1297 IpProtocol: -1 

1298 FromPort: 445 

1299 

1300 We also have specialized handling for matching self-references in 

1301 ingress/egress permissions. The following example matches on ingress 

1302 rules which allow traffic its own same security group. 

1303 

1304 .. code-block:: yaml 

1305 

1306 - type: ingress 

1307 SelfReference: True 

1308 

1309 As well for assertions that a ingress/egress permission only matches 

1310 a given set of ports, *note* OnlyPorts is an inverse match. 

1311 

1312 .. code-block:: yaml 

1313 

1314 - type: egress 

1315 OnlyPorts: [22, 443, 80] 

1316 

1317 - type: egress 

1318 Cidr: 

1319 value_type: cidr 

1320 op: in 

1321 value: x.y.z 

1322 

1323 `value_type: cidr` can also filter if cidr is a subset of cidr 

1324 value range. In this example we are allowing any smaller cidrs within 

1325 allowed_cidrs.csv. 

1326 

1327 .. code-block:: yaml 

1328 

1329 - type: ingress 

1330 Cidr: 

1331 value_type: cidr 

1332 op: not-in 

1333 value_from: 

1334 url: s3://a-policy-data-us-west-2/allowed_cidrs.csv 

1335 format: csv 

1336 

1337 or value can be specified as a list. 

1338 

1339 .. code-block:: yaml 

1340 

1341 - type: ingress 

1342 Cidr: 

1343 value_type: cidr 

1344 op: not-in 

1345 value: ["10.0.0.0/8", "192.168.0.0/16"] 

1346 

1347 `Cidr` can match ipv4 rules and `CidrV6` can match ipv6 rules. In 

1348 this example we are blocking global inbound connections to SSH or 

1349 RDP. 

1350 

1351 .. code-block:: yaml 

1352 

1353 - or: 

1354 - type: ingress 

1355 Ports: [22, 3389] 

1356 Cidr: 

1357 value: "0.0.0.0/0" 

1358 - type: ingress 

1359 Ports: [22, 3389] 

1360 CidrV6: 

1361 value: "::/0" 

1362 

1363 `SGReferences` can be used to filter out SG references in rules. 

1364 In this example we want to block ingress rules that reference a SG 

1365 that is tagged with `Access: Public`. 

1366 

1367 .. code-block:: yaml 

1368 

1369 - type: ingress 

1370 SGReferences: 

1371 key: "tag:Access" 

1372 value: "Public" 

1373 op: equal 

1374 

1375 We can also filter SG references based on the VPC that they are 

1376 within. In this example we want to ensure that our outbound rules 

1377 that reference SGs are only referencing security groups within a 

1378 specified VPC. 

1379 

1380 .. code-block:: yaml 

1381 

1382 - type: egress 

1383 SGReferences: 

1384 key: 'VpcId' 

1385 value: 'vpc-11a1a1aa' 

1386 op: equal 

1387 

1388 Likewise, we can also filter SG references by their description. 

1389 For example, we can prevent egress rules from referencing any 

1390 SGs that have a description of "default - DO NOT USE". 

1391 

1392 .. code-block:: yaml 

1393 

1394 - type: egress 

1395 SGReferences: 

1396 key: 'Description' 

1397 value: 'default - DO NOT USE' 

1398 op: equal 

1399 

1400 By default, this filter matches a security group rule if 

1401 _all_ of its keys match. Using `match-operator: or` causes a match 

1402 if _any_ key matches. This can help consolidate some simple 

1403 cases that would otherwise require multiple filters. To find 

1404 security groups that allow all inbound traffic over IPv4 or IPv6, 

1405 for example, we can use two filters inside an `or` block: 

1406 

1407 .. code-block:: yaml 

1408 

1409 - or: 

1410 - type: ingress 

1411 Cidr: "0.0.0.0/0" 

1412 - type: ingress 

1413 CidrV6: "::/0" 

1414 

1415 or combine them into a single filter: 

1416 

1417 .. code-block:: yaml 

1418 

1419 - type: ingress 

1420 match-operator: or 

1421 Cidr: "0.0.0.0/0" 

1422 CidrV6: "::/0" 

1423 

1424 Note that evaluating _combinations_ of factors (e.g. traffic over 

1425 port 22 from 0.0.0.0/0) still requires separate filters. 

1426 """ 

1427 

1428 perm_attrs = { 

1429 'IpProtocol', 'FromPort', 'ToPort', 'UserIdGroupPairs', 

1430 'IpRanges', 'PrefixListIds'} 

1431 filter_attrs = { 

1432 'Cidr', 'CidrV6', 'Ports', 'OnlyPorts', 

1433 'SelfReference', 'Description', 'SGReferences'} 

1434 attrs = perm_attrs.union(filter_attrs) 

1435 attrs.add('match-operator') 

1436 attrs.add('match-operator') 

1437 

1438 def validate(self): 

1439 delta = set(self.data.keys()).difference(self.attrs) 

1440 delta.remove('type') 

1441 if delta: 

1442 raise PolicyValidationError("Unknown keys %s on %s" % ( 

1443 ", ".join(delta), self.manager.data)) 

1444 return self 

1445 

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

1447 self.vfilters = [] 

1448 fattrs = list(sorted(self.perm_attrs.intersection(self.data.keys()))) 

1449 self.ports = 'Ports' in self.data and self.data['Ports'] or () 

1450 self.only_ports = ( 

1451 'OnlyPorts' in self.data and self.data['OnlyPorts'] or ()) 

1452 for f in fattrs: 

1453 fv = self.data.get(f) 

1454 if isinstance(fv, dict): 

1455 fv['key'] = f 

1456 else: 

1457 fv = {f: fv} 

1458 vf = ValueFilter(fv, self.manager) 

1459 vf.annotate = False 

1460 self.vfilters.append(vf) 

1461 return super(SGPermission, self).process(resources, event) 

1462 

1463 def process_ports(self, perm): 

1464 found = None 

1465 if 'FromPort' in perm and 'ToPort' in perm: 

1466 for port in self.ports: 

1467 if port >= perm['FromPort'] and port <= perm['ToPort']: 

1468 found = True 

1469 break 

1470 found = False 

1471 only_found = False 

1472 for port in self.only_ports: 

1473 if port == perm['FromPort'] and port == perm['ToPort']: 

1474 only_found = True 

1475 if self.only_ports and not only_found: 

1476 found = found is None or found and True or False 

1477 if self.only_ports and only_found: 

1478 found = False 

1479 return found 

1480 

1481 def _process_cidr(self, cidr_key, cidr_type, range_type, perm): 

1482 

1483 found = None 

1484 ip_perms = perm.get(range_type, []) 

1485 if not ip_perms: 

1486 return False 

1487 

1488 match_range = self.data[cidr_key] 

1489 

1490 if isinstance(match_range, dict): 

1491 match_range['key'] = cidr_type 

1492 else: 

1493 match_range = {cidr_type: match_range} 

1494 

1495 vf = ValueFilter(match_range, self.manager) 

1496 vf.annotate = False 

1497 

1498 for ip_range in ip_perms: 

1499 found = vf(ip_range) 

1500 if found: 

1501 break 

1502 else: 

1503 found = False 

1504 return found 

1505 

1506 def process_cidrs(self, perm): 

1507 found_v6 = found_v4 = None 

1508 if 'CidrV6' in self.data: 

1509 found_v6 = self._process_cidr('CidrV6', 'CidrIpv6', 'Ipv6Ranges', perm) 

1510 if 'Cidr' in self.data: 

1511 found_v4 = self._process_cidr('Cidr', 'CidrIp', 'IpRanges', perm) 

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

1513 cidr_match = [k for k in (found_v6, found_v4) if k is not None] 

1514 if not cidr_match: 

1515 return None 

1516 return match_op(cidr_match) 

1517 

1518 def process_description(self, perm): 

1519 if 'Description' not in self.data: 

1520 return None 

1521 

1522 d = dict(self.data['Description']) 

1523 d['key'] = 'Description' 

1524 

1525 vf = ValueFilter(d, self.manager) 

1526 vf.annotate = False 

1527 

1528 for k in ('Ipv6Ranges', 'IpRanges', 'UserIdGroupPairs', 'PrefixListIds'): 

1529 if k not in perm or not perm[k]: 

1530 continue 

1531 return vf(perm[k][0]) 

1532 return False 

1533 

1534 def process_self_reference(self, perm, sg_id): 

1535 found = None 

1536 ref_match = self.data.get('SelfReference') 

1537 if ref_match is not None: 

1538 found = False 

1539 if 'UserIdGroupPairs' in perm and 'SelfReference' in self.data: 

1540 self_reference = sg_id in [p['GroupId'] 

1541 for p in perm['UserIdGroupPairs']] 

1542 if ref_match is False and not self_reference: 

1543 found = True 

1544 if ref_match is True and self_reference: 

1545 found = True 

1546 return found 

1547 

1548 def process_sg_references(self, perm, owner_id): 

1549 sg_refs = self.data.get('SGReferences') 

1550 if not sg_refs: 

1551 return None 

1552 

1553 sg_perm = perm.get('UserIdGroupPairs', []) 

1554 if not sg_perm: 

1555 return False 

1556 

1557 sg_group_ids = [p['GroupId'] for p in sg_perm if p.get('UserId', '') == owner_id] 

1558 sg_resources = self.manager.get_resources(sg_group_ids) 

1559 vf = ValueFilter(sg_refs, self.manager) 

1560 vf.annotate = False 

1561 

1562 for sg in sg_resources: 

1563 if vf(sg): 

1564 return True 

1565 return False 

1566 

1567 def expand_permissions(self, permissions): 

1568 """Expand each list of cidr, prefix list, user id group pair 

1569 by port/protocol as an individual rule. 

1570 

1571 The console ux automatically expands them out as addition/removal is 

1572 per this expansion, the describe calls automatically group them. 

1573 """ 

1574 for p in permissions: 

1575 np = dict(p) 

1576 values = {} 

1577 for k in (u'IpRanges', 

1578 u'Ipv6Ranges', 

1579 u'PrefixListIds', 

1580 u'UserIdGroupPairs'): 

1581 values[k] = np.pop(k, ()) 

1582 np[k] = [] 

1583 for k, v in values.items(): 

1584 if not v: 

1585 continue 

1586 for e in v: 

1587 ep = dict(np) 

1588 ep[k] = [e] 

1589 yield ep 

1590 

1591 def __call__(self, resource): 

1592 matched = [] 

1593 sg_id = resource['GroupId'] 

1594 owner_id = resource['OwnerId'] 

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

1596 for perm in self.expand_permissions(resource[self.ip_permissions_key]): 

1597 perm_matches = {} 

1598 for idx, f in enumerate(self.vfilters): 

1599 perm_matches[idx] = bool(f(perm)) 

1600 perm_matches['description'] = self.process_description(perm) 

1601 perm_matches['ports'] = self.process_ports(perm) 

1602 perm_matches['cidrs'] = self.process_cidrs(perm) 

1603 perm_matches['self-refs'] = self.process_self_reference(perm, sg_id) 

1604 perm_matches['sg-refs'] = self.process_sg_references(perm, owner_id) 

1605 perm_match_values = list(filter( 

1606 lambda x: x is not None, perm_matches.values())) 

1607 

1608 # account for one python behavior any([]) == False, all([]) == True 

1609 if match_op == all and not perm_match_values: 

1610 continue 

1611 

1612 match = match_op(perm_match_values) 

1613 if match: 

1614 matched.append(perm) 

1615 

1616 if matched: 

1617 matched_annotation = resource.setdefault('Matched%s' % self.ip_permissions_key, []) 

1618 # If the same rule matches multiple filters, only add it to the match annotation 

1619 # once. Note: Because we're looking for unique dicts and those aren't hashable, 

1620 # we can't conveniently use set() to de-duplicate rules. 

1621 matched_annotation.extend(m for m in matched if m not in matched_annotation) 

1622 return True 

1623 

1624 

1625SGPermissionSchema = { 

1626 'match-operator': {'type': 'string', 'enum': ['or', 'and']}, 

1627 'Ports': {'type': 'array', 'items': {'type': 'integer'}}, 

1628 'SelfReference': {'type': 'boolean'}, 

1629 'OnlyPorts': {'type': 'array', 'items': {'type': 'integer'}}, 

1630 'IpProtocol': { 

1631 'oneOf': [ 

1632 {'enum': ["-1", -1, 'tcp', 'udp', 'icmp', 'icmpv6']}, 

1633 {'$ref': '#/definitions/filters/value'} 

1634 ] 

1635 }, 

1636 'FromPort': {'oneOf': [ 

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

1638 {'type': 'integer'}]}, 

1639 'ToPort': {'oneOf': [ 

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

1641 {'type': 'integer'}]}, 

1642 'UserIdGroupPairs': {}, 

1643 'IpRanges': {}, 

1644 'PrefixListIds': {}, 

1645 'Description': {}, 

1646 'Cidr': {}, 

1647 'CidrV6': {}, 

1648 'SGReferences': {} 

1649} 

1650 

1651 

1652@SecurityGroup.filter_registry.register('ingress') 

1653class IPPermission(SGPermission): 

1654 

1655 ip_permissions_key = "IpPermissions" 

1656 schema = { 

1657 'type': 'object', 

1658 'additionalProperties': False, 

1659 'properties': {'type': {'enum': ['ingress']}}, 

1660 'required': ['type']} 

1661 schema['properties'].update(SGPermissionSchema) 

1662 

1663 

1664@SecurityGroup.filter_registry.register('egress') 

1665class IPPermissionEgress(SGPermission): 

1666 

1667 ip_permissions_key = "IpPermissionsEgress" 

1668 schema = { 

1669 'type': 'object', 

1670 'additionalProperties': False, 

1671 'properties': {'type': {'enum': ['egress']}}, 

1672 'required': ['type']} 

1673 schema['properties'].update(SGPermissionSchema) 

1674 

1675 

1676@SecurityGroup.action_registry.register('delete') 

1677class Delete(BaseAction): 

1678 """Action to delete security group(s) 

1679 

1680 It is recommended to apply a filter to the delete policy to avoid the 

1681 deletion of all security groups returned. 

1682 

1683 :example: 

1684 

1685 .. code-block:: yaml 

1686 

1687 policies: 

1688 - name: security-groups-unused-delete 

1689 resource: security-group 

1690 filters: 

1691 - type: unused 

1692 actions: 

1693 - delete 

1694 """ 

1695 

1696 schema = type_schema('delete') 

1697 permissions = ('ec2:DeleteSecurityGroup',) 

1698 

1699 def process(self, resources): 

1700 client = local_session(self.manager.session_factory).client('ec2') 

1701 for r in resources: 

1702 client.delete_security_group(GroupId=r['GroupId']) 

1703 

1704 

1705@SecurityGroup.action_registry.register('remove-permissions') 

1706class RemovePermissions(BaseAction): 

1707 """Action to remove ingress/egress rule(s) from a security group 

1708 

1709 :example: 

1710 

1711 .. code-block:: yaml 

1712 

1713 policies: 

1714 - name: security-group-revoke-8080 

1715 resource: security-group 

1716 filters: 

1717 - type: ingress 

1718 IpProtocol: tcp 

1719 Ports: [8080] 

1720 actions: 

1721 - type: remove-permissions 

1722 ingress: matched 

1723 

1724 """ 

1725 schema = type_schema( 

1726 'remove-permissions', 

1727 ingress={'type': 'string', 'enum': ['matched', 'all']}, 

1728 egress={'type': 'string', 'enum': ['matched', 'all']}) 

1729 

1730 permissions = ('ec2:RevokeSecurityGroupIngress', 

1731 'ec2:RevokeSecurityGroupEgress') 

1732 

1733 def process(self, resources): 

1734 i_perms = self.data.get('ingress', 'matched') 

1735 e_perms = self.data.get('egress', 'matched') 

1736 

1737 client = local_session(self.manager.session_factory).client('ec2') 

1738 for r in resources: 

1739 for label, perms in [('ingress', i_perms), ('egress', e_perms)]: 

1740 if perms == 'matched': 

1741 key = 'MatchedIpPermissions%s' % ( 

1742 label == 'egress' and 'Egress' or '') 

1743 groups = r.get(key, ()) 

1744 elif perms == 'all': 

1745 key = 'IpPermissions%s' % ( 

1746 label == 'egress' and 'Egress' or '') 

1747 groups = r.get(key, ()) 

1748 elif isinstance(perms, list): 

1749 groups = perms 

1750 else: 

1751 continue 

1752 if not groups: 

1753 continue 

1754 method = getattr(client, 'revoke_security_group_%s' % label) 

1755 method(GroupId=r['GroupId'], IpPermissions=groups) 

1756 

1757 

1758@SecurityGroup.action_registry.register('set-permissions') 

1759class SetPermissions(BaseAction): 

1760 """Action to add/remove ingress/egress rule(s) to a security group 

1761 

1762 :example: 

1763 

1764 .. code-block:: yaml 

1765 

1766 policies: 

1767 - name: ops-access-via 

1768 resource: aws.security-group 

1769 filters: 

1770 - type: ingress 

1771 IpProtocol: "-1" 

1772 Ports: [22, 3389] 

1773 Cidr: "0.0.0.0/0" 

1774 actions: 

1775 - type: set-permissions 

1776 # remove the permission matched by a previous ingress filter. 

1777 remove-ingress: matched 

1778 # remove permissions by specifying them fully, ie remove default outbound 

1779 # access. 

1780 remove-egress: 

1781 - IpProtocol: "-1" 

1782 Cidr: "0.0.0.0/0" 

1783 

1784 # add a list of permissions to the group. 

1785 add-ingress: 

1786 # full syntax/parameters to authorize can be used. 

1787 - IpPermissions: 

1788 - IpProtocol: TCP 

1789 FromPort: 22 

1790 ToPort: 22 

1791 IpRanges: 

1792 - Description: Ops SSH Access 

1793 CidrIp: "1.1.1.1/32" 

1794 - Description: Security SSH Access 

1795 CidrIp: "2.2.2.2/32" 

1796 # add a list of egress permissions to a security group 

1797 add-egress: 

1798 - IpProtocol: "TCP" 

1799 FromPort: 5044 

1800 ToPort: 5044 

1801 CidrIp: "192.168.1.2/32" 

1802 

1803 """ 

1804 schema = type_schema( 

1805 'set-permissions', 

1806 **{'add-ingress': {'type': 'array', 'items': {'type': 'object', 'minProperties': 1}}, 

1807 'remove-ingress': {'oneOf': [ 

1808 {'enum': ['all', 'matched']}, 

1809 {'type': 'array', 'items': {'type': 'object', 'minProperties': 2}}]}, 

1810 'add-egress': {'type': 'array', 'items': {'type': 'object', 'minProperties': 1}}, 

1811 'remove-egress': {'oneOf': [ 

1812 {'enum': ['all', 'matched']}, 

1813 {'type': 'array', 'items': {'type': 'object', 'minProperties': 2}}]}} 

1814 ) 

1815 permissions = ( 

1816 'ec2:AuthorizeSecurityGroupEgress', 

1817 'ec2:AuthorizeSecurityGroupIngress',) 

1818 

1819 ingress_shape = "AuthorizeSecurityGroupIngressRequest" 

1820 egress_shape = "AuthorizeSecurityGroupEgressRequest" 

1821 

1822 def validate(self): 

1823 request_template = {'GroupId': 'sg-06bc5ce18a2e5d57a'} 

1824 for perm_type, shape in ( 

1825 ('egress', self.egress_shape), ('ingress', self.ingress_shape)): 

1826 for perm in self.data.get('add-%s' % type, ()): 

1827 params = dict(request_template) 

1828 params.update(perm) 

1829 shape_validate(params, shape, 'ec2') 

1830 

1831 def get_permissions(self): 

1832 perms = () 

1833 if 'add-ingress' in self.data: 

1834 perms += ('ec2:AuthorizeSecurityGroupIngress',) 

1835 if 'add-egress' in self.data: 

1836 perms += ('ec2:AuthorizeSecurityGroupEgress',) 

1837 if 'remove-ingress' in self.data or 'remove-egress' in self.data: 

1838 perms += RemovePermissions.permissions 

1839 if not perms: 

1840 perms = self.permissions + RemovePermissions.permissions 

1841 return perms 

1842 

1843 def process(self, resources): 

1844 client = local_session(self.manager.session_factory).client('ec2') 

1845 for r in resources: 

1846 for method, permissions in ( 

1847 (client.authorize_security_group_egress, self.data.get('add-egress', ())), 

1848 (client.authorize_security_group_ingress, self.data.get('add-ingress', ()))): 

1849 for p in permissions: 

1850 p = dict(p) 

1851 p['GroupId'] = r['GroupId'] 

1852 try: 

1853 method(**p) 

1854 except ClientError as e: 

1855 if e.response['Error']['Code'] != 'InvalidPermission.Duplicate': 

1856 raise 

1857 

1858 remover = RemovePermissions( 

1859 {'ingress': self.data.get('remove-ingress', ()), 

1860 'egress': self.data.get('remove-egress', ())}, self.manager) 

1861 remover.process(resources) 

1862 

1863 

1864@SecurityGroup.action_registry.register('post-finding') 

1865class SecurityGroupPostFinding(OtherResourcePostFinding): 

1866 

1867 def format_resource(self, r): 

1868 fr = super(SecurityGroupPostFinding, self).format_resource(r) 

1869 fr['Type'] = 'AwsEc2SecurityGroup' 

1870 return fr 

1871 

1872 

1873class DescribeENI(query.DescribeSource): 

1874 

1875 def augment(self, resources): 

1876 for r in resources: 

1877 r['Tags'] = r.pop('TagSet', []) 

1878 return resources 

1879 

1880 

1881@resources.register('eni') 

1882class NetworkInterface(query.QueryResourceManager): 

1883 

1884 class resource_type(query.TypeInfo): 

1885 service = 'ec2' 

1886 arn_type = 'network-interface' 

1887 enum_spec = ('describe_network_interfaces', 'NetworkInterfaces', None) 

1888 name = id = 'NetworkInterfaceId' 

1889 filter_name = 'NetworkInterfaceIds' 

1890 filter_type = 'list' 

1891 cfn_type = config_type = "AWS::EC2::NetworkInterface" 

1892 id_prefix = "eni-" 

1893 

1894 source_mapping = { 

1895 'describe': DescribeENI, 

1896 'config': query.ConfigSource 

1897 } 

1898 

1899 

1900NetworkInterface.filter_registry.register('flow-logs', FlowLogv2Filter) 

1901NetworkInterface.filter_registry.register( 

1902 'network-location', net_filters.NetworkLocation) 

1903 

1904 

1905@NetworkInterface.filter_registry.register('subnet') 

1906class InterfaceSubnetFilter(net_filters.SubnetFilter): 

1907 """Network interface subnet filter 

1908 

1909 :example: 

1910 

1911 .. code-block:: yaml 

1912 

1913 policies: 

1914 - name: network-interface-in-subnet 

1915 resource: eni 

1916 filters: 

1917 - type: subnet 

1918 key: CidrBlock 

1919 value: 10.0.2.0/24 

1920 """ 

1921 

1922 RelatedIdsExpression = "SubnetId" 

1923 

1924 

1925@NetworkInterface.filter_registry.register('security-group') 

1926class InterfaceSecurityGroupFilter(net_filters.SecurityGroupFilter): 

1927 """Network interface security group filter 

1928 

1929 :example: 

1930 

1931 .. code-block:: yaml 

1932 

1933 policies: 

1934 - name: network-interface-ssh 

1935 resource: eni 

1936 filters: 

1937 - type: security-group 

1938 match-resource: true 

1939 key: FromPort 

1940 value: 22 

1941 """ 

1942 

1943 RelatedIdsExpression = "Groups[].GroupId" 

1944 

1945 

1946@NetworkInterface.filter_registry.register('vpc') 

1947class InterfaceVpcFilter(net_filters.VpcFilter): 

1948 

1949 RelatedIdsExpression = "VpcId" 

1950 

1951 

1952@NetworkInterface.action_registry.register('modify-security-groups') 

1953class InterfaceModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

1954 """Remove security groups from an interface. 

1955 

1956 Can target either physical groups as a list of group ids or 

1957 symbolic groups like 'matched' or 'all'. 'matched' uses 

1958 the annotations of the 'group' interface filter. 

1959 

1960 Note an interface always gets at least one security group, so 

1961 we also allow specification of an isolation/quarantine group 

1962 that can be specified if there would otherwise be no groups. 

1963 

1964 

1965 :example: 

1966 

1967 .. code-block:: yaml 

1968 

1969 policies: 

1970 - name: network-interface-remove-group 

1971 resource: eni 

1972 filters: 

1973 - type: security-group 

1974 match-resource: true 

1975 key: FromPort 

1976 value: 22 

1977 actions: 

1978 - type: modify-security-groups 

1979 isolation-group: sg-01ab23c4 

1980 add: [] 

1981 """ 

1982 permissions = ('ec2:ModifyNetworkInterfaceAttribute',) 

1983 

1984 def process(self, resources): 

1985 client = local_session(self.manager.session_factory).client('ec2') 

1986 groups = super( 

1987 InterfaceModifyVpcSecurityGroups, self).get_groups(resources) 

1988 for idx, r in enumerate(resources): 

1989 client.modify_network_interface_attribute( 

1990 NetworkInterfaceId=r['NetworkInterfaceId'], 

1991 Groups=groups[idx]) 

1992 

1993 

1994@NetworkInterface.action_registry.register('delete') 

1995class DeleteNetworkInterface(BaseAction): 

1996 """Delete a network interface. 

1997 

1998 :example: 

1999 

2000 .. code-block:: yaml 

2001 

2002 policies: 

2003 - name: mark-orphaned-enis 

2004 comment: Flag abandoned Lambda VPC ENIs for deletion 

2005 resource: eni 

2006 filters: 

2007 - Status: available 

2008 - type: value 

2009 op: glob 

2010 key: Description 

2011 value: "AWS Lambda VPC ENI*" 

2012 - "tag:custodian_status": absent 

2013 actions: 

2014 - type: mark-for-op 

2015 tag: custodian_status 

2016 msg: "Orphaned Lambda VPC ENI: {op}@{action_date}" 

2017 op: delete 

2018 days: 1 

2019 

2020 - name: delete-marked-enis 

2021 comment: Delete flagged ENIs that have not been cleaned up naturally 

2022 resource: eni 

2023 filters: 

2024 - type: marked-for-op 

2025 tag: custodian_status 

2026 op: delete 

2027 actions: 

2028 - type: delete 

2029 """ 

2030 permissions = ('ec2:DeleteNetworkInterface',) 

2031 schema = type_schema('delete') 

2032 

2033 def process(self, resources): 

2034 client = local_session(self.manager.session_factory).client('ec2') 

2035 for r in resources: 

2036 try: 

2037 self.manager.retry( 

2038 client.delete_network_interface, 

2039 NetworkInterfaceId=r['NetworkInterfaceId']) 

2040 except ClientError as err: 

2041 if not err.response['Error']['Code'] == 'InvalidNetworkInterfaceID.NotFound': 

2042 raise 

2043 

2044 

2045@NetworkInterface.action_registry.register('detach') 

2046class DetachNetworkInterface(BaseAction): 

2047 """Detach a network interface from an EC2 instance. 

2048 

2049 :example: 

2050 

2051 .. code-block:: yaml 

2052 

2053 policies: 

2054 - name: detach-enis 

2055 comment: Detach ENIs attached to EC2 with public IP addresses 

2056 resource: eni 

2057 filters: 

2058 - type: value 

2059 key: Attachment.InstanceId 

2060 value: present 

2061 - type: value 

2062 key: Association.PublicIp 

2063 value: present 

2064 actions: 

2065 - type: detach 

2066 """ 

2067 permissions = ('ec2:DetachNetworkInterface',) 

2068 schema = type_schema('detach') 

2069 

2070 def process(self, resources): 

2071 client = local_session(self.manager.session_factory).client('ec2') 

2072 att_resources = [ar for ar in resources if ('Attachment' in ar 

2073 and ar['Attachment'].get('InstanceId') 

2074 and ar['Attachment'].get('DeviceIndex') != 0)] 

2075 if att_resources and (len(att_resources) < len(resources)): 

2076 self.log.warning( 

2077 "Filtered {} of {} non-primary network interfaces attatched to EC2".format( 

2078 len(att_resources), len(resources)) 

2079 ) 

2080 elif not att_resources: 

2081 self.log.warning("No non-primary EC2 interfaces indentified - revise c7n filters") 

2082 for r in att_resources: 

2083 client.detach_network_interface(AttachmentId=r['Attachment']['AttachmentId']) 

2084 

2085 

2086@resources.register('route-table') 

2087class RouteTable(query.QueryResourceManager): 

2088 

2089 class resource_type(query.TypeInfo): 

2090 service = 'ec2' 

2091 arn_type = 'route-table' 

2092 enum_spec = ('describe_route_tables', 'RouteTables', None) 

2093 name = id = 'RouteTableId' 

2094 filter_name = 'RouteTableIds' 

2095 filter_type = 'list' 

2096 id_prefix = "rtb-" 

2097 cfn_type = config_type = "AWS::EC2::RouteTable" 

2098 

2099 

2100@RouteTable.filter_registry.register('vpc') 

2101class RouteTableVpcFilter(net_filters.VpcFilter): 

2102 

2103 RelatedIdsExpression = "VpcId" 

2104 

2105 

2106@RouteTable.filter_registry.register('subnet') 

2107class SubnetRoute(net_filters.SubnetFilter): 

2108 """Filter a route table by its associated subnet attributes.""" 

2109 

2110 RelatedIdsExpression = "Associations[].SubnetId" 

2111 

2112 RelatedMapping = None 

2113 

2114 def get_related_ids(self, resources): 

2115 if self.RelatedIdMapping is None: 

2116 return super(SubnetRoute, self).get_related_ids(resources) 

2117 return list(itertools.chain(*[self.RelatedIdMapping[r['RouteTableId']] for r in resources])) 

2118 

2119 def get_related(self, resources): 

2120 rt_subnet_map = {} 

2121 main_tables = {} 

2122 

2123 manager = self.get_resource_manager() 

2124 for r in resources: 

2125 rt_subnet_map[r['RouteTableId']] = [] 

2126 for a in r.get('Associations', ()): 

2127 if 'SubnetId' in a: 

2128 rt_subnet_map[r['RouteTableId']].append(a['SubnetId']) 

2129 elif a.get('Main'): 

2130 main_tables[r['VpcId']] = r['RouteTableId'] 

2131 explicit_subnet_ids = set(itertools.chain(*rt_subnet_map.values())) 

2132 subnets = manager.resources() 

2133 for s in subnets: 

2134 if s['SubnetId'] in explicit_subnet_ids: 

2135 continue 

2136 if s['VpcId'] not in main_tables: 

2137 continue 

2138 rt_subnet_map.setdefault(main_tables[s['VpcId']], []).append(s['SubnetId']) 

2139 related_subnets = set(itertools.chain(*rt_subnet_map.values())) 

2140 self.RelatedIdMapping = rt_subnet_map 

2141 return {s['SubnetId']: s for s in subnets if s['SubnetId'] in related_subnets} 

2142 

2143 

2144@RouteTable.filter_registry.register('route') 

2145class Route(ValueFilter): 

2146 """Filter a route table by its routes' attributes.""" 

2147 

2148 schema = type_schema('route', rinherit=ValueFilter.schema) 

2149 schema_alias = False 

2150 

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

2152 results = [] 

2153 for r in resources: 

2154 matched = [] 

2155 for route in r['Routes']: 

2156 if self.match(route): 

2157 matched.append(route) 

2158 if matched: 

2159 r.setdefault('c7n:matched-routes', []).extend(matched) 

2160 results.append(r) 

2161 return results 

2162 

2163 

2164@resources.register('transit-gateway') 

2165class TransitGateway(query.QueryResourceManager): 

2166 

2167 class resource_type(query.TypeInfo): 

2168 service = 'ec2' 

2169 enum_spec = ('describe_transit_gateways', 'TransitGateways', None) 

2170 name = id = 'TransitGatewayId' 

2171 arn = "TransitGatewayArn" 

2172 id_prefix = "tgw-" 

2173 filter_name = 'TransitGatewayIds' 

2174 filter_type = 'list' 

2175 config_type = cfn_type = 'AWS::EC2::TransitGateway' 

2176 

2177 

2178TransitGateway.filter_registry.register('flow-logs', FlowLogv2Filter) 

2179 

2180 

2181class TransitGatewayAttachmentQuery(query.ChildResourceQuery): 

2182 

2183 def get_parent_parameters(self, params, parent_id, parent_key): 

2184 merged_params = dict(params) 

2185 merged_params.setdefault('Filters', []).append( 

2186 {'Name': parent_key, 'Values': [parent_id]}) 

2187 return merged_params 

2188 

2189 

2190@query.sources.register('transit-attachment') 

2191class TransitAttachmentSource(query.ChildDescribeSource): 

2192 

2193 resource_query_factory = TransitGatewayAttachmentQuery 

2194 

2195 

2196@resources.register('transit-attachment') 

2197class TransitGatewayAttachment(query.ChildResourceManager): 

2198 

2199 child_source = 'transit-attachment' 

2200 

2201 class resource_type(query.TypeInfo): 

2202 service = 'ec2' 

2203 enum_spec = ('describe_transit_gateway_attachments', 'TransitGatewayAttachments', None) 

2204 parent_spec = ('transit-gateway', 'transit-gateway-id', None) 

2205 id_prefix = 'tgw-attach-' 

2206 name = id = 'TransitGatewayAttachmentId' 

2207 metrics_namespace = 'AWS/TransitGateway' 

2208 arn = False 

2209 cfn_type = 'AWS::EC2::TransitGatewayAttachment' 

2210 supports_trailevents = True 

2211 

2212 

2213@TransitGatewayAttachment.filter_registry.register('metrics') 

2214class TransitGatewayAttachmentMetricsFilter(MetricsFilter): 

2215 

2216 def get_dimensions(self, resource): 

2217 return [ 

2218 {'Name': 'TransitGateway', 'Value': resource['TransitGatewayId']}, 

2219 {'Name': 'TransitGatewayAttachment', 'Value': resource['TransitGatewayAttachmentId']} 

2220 ] 

2221 

2222 

2223@resources.register('peering-connection') 

2224class PeeringConnection(query.QueryResourceManager): 

2225 

2226 class resource_type(query.TypeInfo): 

2227 service = 'ec2' 

2228 arn_type = 'vpc-peering-connection' 

2229 enum_spec = ('describe_vpc_peering_connections', 

2230 'VpcPeeringConnections', None) 

2231 name = id = 'VpcPeeringConnectionId' 

2232 filter_name = 'VpcPeeringConnectionIds' 

2233 filter_type = 'list' 

2234 id_prefix = "pcx-" 

2235 cfn_type = config_type = "AWS::EC2::VPCPeeringConnection" 

2236 

2237 

2238@PeeringConnection.filter_registry.register('cross-account') 

2239class CrossAccountPeer(CrossAccountAccessFilter): 

2240 

2241 schema = type_schema( 

2242 'cross-account', 

2243 # white list accounts 

2244 whitelist_from=resolver.ValuesFrom.schema, 

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

2246 

2247 permissions = ('ec2:DescribeVpcPeeringConnections',) 

2248 

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

2250 results = [] 

2251 accounts = self.get_accounts() 

2252 owners = map(jmespath_compile, ( 

2253 'AccepterVpcInfo.OwnerId', 'RequesterVpcInfo.OwnerId')) 

2254 

2255 for r in resources: 

2256 for o_expr in owners: 

2257 account_id = o_expr.search(r) 

2258 if account_id and account_id not in accounts: 

2259 r.setdefault( 

2260 'c7n:CrossAccountViolations', []).append(account_id) 

2261 results.append(r) 

2262 return results 

2263 

2264 

2265@PeeringConnection.filter_registry.register('missing-route') 

2266class MissingRoute(Filter): 

2267 """Return peers which are missing a route in route tables. 

2268 

2269 If the peering connection is between two vpcs in the same account, 

2270 the connection is returned unless it is in present route tables in 

2271 each vpc. 

2272 

2273 If the peering connection is between accounts, then the local vpc's 

2274 route table is checked. 

2275 """ 

2276 

2277 schema = type_schema('missing-route') 

2278 permissions = ('ec2:DescribeRouteTables',) 

2279 

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

2281 tables = self.manager.get_resource_manager( 

2282 'route-table').resources() 

2283 routed_vpcs = {} 

2284 mid = 'VpcPeeringConnectionId' 

2285 for t in tables: 

2286 for r in t.get('Routes', ()): 

2287 if mid in r: 

2288 routed_vpcs.setdefault(r[mid], []).append(t['VpcId']) 

2289 results = [] 

2290 for r in resources: 

2291 if r[mid] not in routed_vpcs: 

2292 results.append(r) 

2293 continue 

2294 for k in ('AccepterVpcInfo', 'RequesterVpcInfo'): 

2295 if r[k]['OwnerId'] != self.manager.config.account_id: 

2296 continue 

2297 if r[k].get('Region') and r['k']['Region'] != self.manager.config.region: 

2298 continue 

2299 if r[k]['VpcId'] not in routed_vpcs[r['VpcPeeringConnectionId']]: 

2300 results.append(r) 

2301 break 

2302 return results 

2303 

2304 

2305@resources.register('network-acl') 

2306class NetworkAcl(query.QueryResourceManager): 

2307 

2308 class resource_type(query.TypeInfo): 

2309 service = 'ec2' 

2310 arn_type = 'network-acl' 

2311 enum_spec = ('describe_network_acls', 'NetworkAcls', None) 

2312 name = id = 'NetworkAclId' 

2313 filter_name = 'NetworkAclIds' 

2314 filter_type = 'list' 

2315 cfn_type = config_type = "AWS::EC2::NetworkAcl" 

2316 id_prefix = "acl-" 

2317 

2318 

2319@NetworkAcl.filter_registry.register('subnet') 

2320class AclSubnetFilter(net_filters.SubnetFilter): 

2321 """Filter network acls by the attributes of their attached subnets. 

2322 

2323 :example: 

2324 

2325 .. code-block:: yaml 

2326 

2327 policies: 

2328 - name: subnet-acl 

2329 resource: network-acl 

2330 filters: 

2331 - type: subnet 

2332 key: "tag:Location" 

2333 value: Public 

2334 """ 

2335 

2336 RelatedIdsExpression = "Associations[].SubnetId" 

2337 

2338 

2339@NetworkAcl.filter_registry.register('s3-cidr') 

2340class AclAwsS3Cidrs(Filter): 

2341 """Filter network acls by those that allow access to s3 cidrs. 

2342 

2343 Defaults to filtering those nacls that do not allow s3 communication. 

2344 

2345 :example: 

2346 

2347 Find all nacls that do not allow communication with s3. 

2348 

2349 .. code-block:: yaml 

2350 

2351 policies: 

2352 - name: s3-not-allowed-nacl 

2353 resource: network-acl 

2354 filters: 

2355 - s3-cidr 

2356 """ 

2357 # TODO allow for port specification as range 

2358 schema = type_schema( 

2359 's3-cidr', 

2360 egress={'type': 'boolean', 'default': True}, 

2361 ingress={'type': 'boolean', 'default': True}, 

2362 present={'type': 'boolean', 'default': False}) 

2363 

2364 permissions = ('ec2:DescribePrefixLists',) 

2365 

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

2367 ec2 = local_session(self.manager.session_factory).client('ec2') 

2368 cidrs = jmespath_search( 

2369 "PrefixLists[].Cidrs[]", ec2.describe_prefix_lists()) 

2370 cidrs = [parse_cidr(cidr) for cidr in cidrs] 

2371 results = [] 

2372 

2373 check_egress = self.data.get('egress', True) 

2374 check_ingress = self.data.get('ingress', True) 

2375 present = self.data.get('present', False) 

2376 

2377 for r in resources: 

2378 matched = {cidr: None for cidr in cidrs} 

2379 for entry in r['Entries']: 

2380 if entry['Egress'] and not check_egress: 

2381 continue 

2382 if not entry['Egress'] and not check_ingress: 

2383 continue 

2384 entry_cidr = parse_cidr(entry['CidrBlock']) 

2385 for c in matched: 

2386 if c in entry_cidr and matched[c] is None: 

2387 matched[c] = ( 

2388 entry['RuleAction'] == 'allow' and True or False) 

2389 if present and all(matched.values()): 

2390 results.append(r) 

2391 elif not present and not all(matched.values()): 

2392 results.append(r) 

2393 return results 

2394 

2395 

2396class DescribeElasticIp(query.DescribeSource): 

2397 

2398 def augment(self, resources): 

2399 return [r for r in resources if self.manager.resource_type.id in r] 

2400 

2401 

2402@resources.register('elastic-ip', aliases=('network-addr',)) 

2403class NetworkAddress(query.QueryResourceManager): 

2404 

2405 class resource_type(query.TypeInfo): 

2406 service = 'ec2' 

2407 arn_type = 'elastic-ip' 

2408 enum_spec = ('describe_addresses', 'Addresses', None) 

2409 name = 'PublicIp' 

2410 id = 'AllocationId' 

2411 id_prefix = 'eipalloc-' 

2412 filter_name = 'AllocationIds' 

2413 filter_type = 'list' 

2414 config_type = cfn_type = "AWS::EC2::EIP" 

2415 

2416 source_mapping = { 

2417 'describe': DescribeElasticIp, 

2418 'config': query.ConfigSource 

2419 } 

2420 

2421 

2422NetworkAddress.filter_registry.register('shield-enabled', IsEIPShieldProtected) 

2423NetworkAddress.action_registry.register('set-shield', SetEIPShieldProtection) 

2424 

2425 

2426@NetworkAddress.action_registry.register('release') 

2427class AddressRelease(BaseAction): 

2428 """Action to release elastic IP address(es) 

2429 

2430 Use the force option to cause any attached elastic IPs to 

2431 also be released. Otherwise, only unattached elastic IPs 

2432 will be released. 

2433 

2434 :example: 

2435 

2436 .. code-block:: yaml 

2437 

2438 policies: 

2439 - name: release-network-addr 

2440 resource: network-addr 

2441 filters: 

2442 - AllocationId: ... 

2443 actions: 

2444 - type: release 

2445 force: True 

2446 """ 

2447 

2448 schema = type_schema('release', force={'type': 'boolean'}) 

2449 permissions = ('ec2:ReleaseAddress', 'ec2:DisassociateAddress',) 

2450 

2451 def process_attached(self, client, associated_addrs): 

2452 for aa in list(associated_addrs): 

2453 try: 

2454 client.disassociate_address(AssociationId=aa['AssociationId']) 

2455 except ClientError as e: 

2456 # If its already been diassociated ignore, else raise. 

2457 if not (e.response['Error']['Code'] == 'InvalidAssocationID.NotFound' and 

2458 aa['AssocationId'] in e.response['Error']['Message']): 

2459 raise e 

2460 associated_addrs.remove(aa) 

2461 return associated_addrs 

2462 

2463 def process(self, network_addrs): 

2464 client = local_session(self.manager.session_factory).client('ec2') 

2465 force = self.data.get('force') 

2466 assoc_addrs = [addr for addr in network_addrs if 'AssociationId' in addr] 

2467 unassoc_addrs = [addr for addr in network_addrs if 'AssociationId' not in addr] 

2468 

2469 if len(assoc_addrs) and not force: 

2470 self.log.warning( 

2471 "Filtered %d attached eips of %d eips. Use 'force: true' to release them.", 

2472 len(assoc_addrs), len(network_addrs)) 

2473 elif len(assoc_addrs) and force: 

2474 unassoc_addrs = itertools.chain( 

2475 unassoc_addrs, self.process_attached(client, assoc_addrs)) 

2476 

2477 for r in unassoc_addrs: 

2478 try: 

2479 client.release_address(AllocationId=r['AllocationId']) 

2480 except ClientError as e: 

2481 # If its already been released, ignore, else raise. 

2482 if e.response['Error']['Code'] == 'InvalidAddress.PtrSet': 

2483 self.log.warning( 

2484 "EIP %s cannot be released because it has a PTR record set.", 

2485 r['AllocationId']) 

2486 if e.response['Error']['Code'] == 'InvalidAddress.Locked': 

2487 self.log.warning( 

2488 "EIP %s cannot be released because it is locked to your account.", 

2489 r['AllocationId']) 

2490 if e.response['Error']['Code'] != 'InvalidAllocationID.NotFound': 

2491 raise 

2492 

2493 

2494@NetworkAddress.action_registry.register('disassociate') 

2495class DisassociateAddress(BaseAction): 

2496 """Disassociate elastic IP addresses from resources without releasing them. 

2497 

2498 :example: 

2499 

2500 .. code-block:: yaml 

2501 

2502 policies: 

2503 - name: disassociate-network-addr 

2504 resource: network-addr 

2505 filters: 

2506 - AllocationId: ... 

2507 actions: 

2508 - type: disassociate 

2509 """ 

2510 

2511 schema = type_schema('disassociate') 

2512 permissions = ('ec2:DisassociateAddress',) 

2513 

2514 def process(self, network_addrs): 

2515 client = local_session(self.manager.session_factory).client('ec2') 

2516 assoc_addrs = [addr for addr in network_addrs if 'AssociationId' in addr] 

2517 

2518 for aa in assoc_addrs: 

2519 try: 

2520 client.disassociate_address(AssociationId=aa['AssociationId']) 

2521 except ClientError as e: 

2522 # If its already been diassociated ignore, else raise. 

2523 if not (e.response['Error']['Code'] == 'InvalidAssocationID.NotFound' and 

2524 aa['AssocationId'] in e.response['Error']['Message']): 

2525 raise e 

2526 

2527 

2528@resources.register('customer-gateway') 

2529class CustomerGateway(query.QueryResourceManager): 

2530 

2531 class resource_type(query.TypeInfo): 

2532 service = 'ec2' 

2533 arn_type = 'customer-gateway' 

2534 enum_spec = ('describe_customer_gateways', 'CustomerGateways', None) 

2535 id = 'CustomerGatewayId' 

2536 filter_name = 'CustomerGatewayIds' 

2537 filter_type = 'list' 

2538 name = 'CustomerGatewayId' 

2539 id_prefix = "cgw-" 

2540 cfn_type = config_type = 'AWS::EC2::CustomerGateway' 

2541 

2542 

2543@resources.register('internet-gateway') 

2544class InternetGateway(query.QueryResourceManager): 

2545 

2546 class resource_type(query.TypeInfo): 

2547 service = 'ec2' 

2548 arn_type = 'internet-gateway' 

2549 enum_spec = ('describe_internet_gateways', 'InternetGateways', None) 

2550 name = id = 'InternetGatewayId' 

2551 filter_name = 'InternetGatewayIds' 

2552 filter_type = 'list' 

2553 cfn_type = config_type = "AWS::EC2::InternetGateway" 

2554 id_prefix = "igw-" 

2555 

2556 

2557@InternetGateway.action_registry.register('delete') 

2558class DeleteInternetGateway(BaseAction): 

2559 

2560 """Action to delete Internet Gateway 

2561 

2562 :example: 

2563 

2564 .. code-block:: yaml 

2565 

2566 policies: 

2567 - name: delete-internet-gateway 

2568 resource: internet-gateway 

2569 actions: 

2570 - type: delete 

2571 """ 

2572 

2573 schema = type_schema('delete') 

2574 permissions = ('ec2:DeleteInternetGateway',) 

2575 

2576 def process(self, resources): 

2577 

2578 client = local_session(self.manager.session_factory).client('ec2') 

2579 for r in resources: 

2580 try: 

2581 client.delete_internet_gateway(InternetGatewayId=r['InternetGatewayId']) 

2582 except ClientError as err: 

2583 if err.response['Error']['Code'] == 'DependencyViolation': 

2584 self.log.warning( 

2585 "%s error hit deleting internetgateway: %s", 

2586 err.response['Error']['Code'], 

2587 err.response['Error']['Message'], 

2588 ) 

2589 elif err.response['Error']['Code'] == 'InvalidInternetGatewayId.NotFound': 

2590 pass 

2591 else: 

2592 raise 

2593 

2594 

2595@resources.register('nat-gateway') 

2596class NATGateway(query.QueryResourceManager): 

2597 

2598 class resource_type(query.TypeInfo): 

2599 service = 'ec2' 

2600 arn_type = 'natgateway' 

2601 enum_spec = ('describe_nat_gateways', 'NatGateways', None) 

2602 name = id = 'NatGatewayId' 

2603 filter_name = 'NatGatewayIds' 

2604 filter_type = 'list' 

2605 date = 'CreateTime' 

2606 dimension = 'NatGatewayId' 

2607 metrics_namespace = 'AWS/NATGateway' 

2608 id_prefix = "nat-" 

2609 cfn_type = config_type = 'AWS::EC2::NatGateway' 

2610 

2611 

2612@NATGateway.action_registry.register('delete') 

2613class DeleteNATGateway(BaseAction): 

2614 

2615 schema = type_schema('delete') 

2616 permissions = ('ec2:DeleteNatGateway',) 

2617 

2618 def process(self, resources): 

2619 client = local_session(self.manager.session_factory).client('ec2') 

2620 for r in resources: 

2621 client.delete_nat_gateway(NatGatewayId=r['NatGatewayId']) 

2622 

2623 

2624@resources.register('vpn-connection') 

2625class VPNConnection(query.QueryResourceManager): 

2626 

2627 class resource_type(query.TypeInfo): 

2628 service = 'ec2' 

2629 arn_type = 'vpn-connection' 

2630 enum_spec = ('describe_vpn_connections', 'VpnConnections', None) 

2631 name = id = 'VpnConnectionId' 

2632 filter_name = 'VpnConnectionIds' 

2633 filter_type = 'list' 

2634 cfn_type = config_type = 'AWS::EC2::VPNConnection' 

2635 id_prefix = "vpn-" 

2636 

2637 

2638@resources.register('vpn-gateway') 

2639class VPNGateway(query.QueryResourceManager): 

2640 

2641 class resource_type(query.TypeInfo): 

2642 service = 'ec2' 

2643 arn_type = 'vpn-gateway' 

2644 enum_spec = ('describe_vpn_gateways', 'VpnGateways', None) 

2645 name = id = 'VpnGatewayId' 

2646 filter_name = 'VpnGatewayIds' 

2647 filter_type = 'list' 

2648 cfn_type = config_type = 'AWS::EC2::VPNGateway' 

2649 id_prefix = "vgw-" 

2650 

2651 

2652@resources.register('vpc-endpoint') 

2653class VpcEndpoint(query.QueryResourceManager): 

2654 

2655 class resource_type(query.TypeInfo): 

2656 service = 'ec2' 

2657 arn_type = 'vpc-endpoint' 

2658 enum_spec = ('describe_vpc_endpoints', 'VpcEndpoints', None) 

2659 name = id = 'VpcEndpointId' 

2660 metrics_namespace = "AWS/PrivateLinkEndpoints" 

2661 date = 'CreationTimestamp' 

2662 filter_name = 'VpcEndpointIds' 

2663 filter_type = 'list' 

2664 id_prefix = "vpce-" 

2665 universal_taggable = object() 

2666 cfn_type = config_type = "AWS::EC2::VPCEndpoint" 

2667 

2668 

2669@VpcEndpoint.filter_registry.register('metrics') 

2670class VpcEndpointMetricsFilter(MetricsFilter): 

2671 

2672 def get_dimensions(self, resource): 

2673 return [ 

2674 {'Name': 'Endpoint Type', 'Value': resource['VpcEndpointType']}, 

2675 {'Name': 'Service Name', 'Value': resource['ServiceName']}, 

2676 {'Name': 'VPC Endpoint Id', 'Value': resource['VpcEndpointId']}, 

2677 {'Name': 'VPC Id', 'Value': resource['VpcId']}, 

2678 ] 

2679 

2680 

2681@VpcEndpoint.filter_registry.register('has-statement') 

2682class EndpointPolicyStatementFilter(HasStatementFilter): 

2683 """Find resources with matching endpoint policy statements. 

2684 

2685 :example: 

2686 

2687 .. code-block:: yaml 

2688 

2689 policies: 

2690 - name: vpc-endpoint-policy 

2691 resource: aws.vpc-endpoint 

2692 filters: 

2693 - type: has-statement 

2694 statements: 

2695 - Action: "*" 

2696 Effect: "Allow" 

2697 """ 

2698 

2699 policy_attribute = 'PolicyDocument' 

2700 permissions = ('ec2:DescribeVpcEndpoints',) 

2701 

2702 def get_std_format_args(self, endpoint): 

2703 return { 

2704 'endpoint_id': endpoint['VpcEndpointId'], 

2705 'account_id': self.manager.config.account_id, 

2706 'region': self.manager.config.region 

2707 } 

2708 

2709 

2710@VpcEndpoint.filter_registry.register('cross-account') 

2711class EndpointCrossAccountFilter(CrossAccountAccessFilter): 

2712 

2713 policy_attribute = 'PolicyDocument' 

2714 annotation_key = 'c7n:CrossAccountViolations' 

2715 permissions = ('ec2:DescribeVpcEndpoints',) 

2716 

2717 

2718@VpcEndpoint.filter_registry.register('security-group') 

2719class EndpointSecurityGroupFilter(net_filters.SecurityGroupFilter): 

2720 

2721 RelatedIdsExpression = "Groups[].GroupId" 

2722 

2723 

2724@VpcEndpoint.filter_registry.register('subnet') 

2725class EndpointSubnetFilter(net_filters.SubnetFilter): 

2726 

2727 RelatedIdsExpression = "SubnetIds[]" 

2728 

2729 

2730@VpcEndpoint.filter_registry.register('vpc') 

2731class EndpointVpcFilter(net_filters.VpcFilter): 

2732 

2733 RelatedIdsExpression = "VpcId" 

2734 

2735 

2736@Vpc.filter_registry.register("vpc-endpoint") 

2737class VPCEndpointFilter(RelatedResourceByIdFilter): 

2738 """Filters vpcs based on their vpc-endpoints 

2739 

2740 :example: 

2741 

2742 .. code-block:: yaml 

2743 

2744 policies: 

2745 - name: s3-vpc-endpoint-enabled 

2746 resource: vpc 

2747 filters: 

2748 - type: vpc-endpoint 

2749 key: ServiceName 

2750 value: com.amazonaws.us-east-1.s3 

2751 """ 

2752 RelatedResource = "c7n.resources.vpc.VpcEndpoint" 

2753 RelatedIdsExpression = "VpcId" 

2754 AnnotationKey = "matched-vpc-endpoint" 

2755 

2756 schema = type_schema( 

2757 'vpc-endpoint', 

2758 rinherit=ValueFilter.schema) 

2759 

2760 

2761@Subnet.filter_registry.register("vpc-endpoint") 

2762class SubnetEndpointFilter(RelatedResourceByIdFilter): 

2763 """Filters subnets based on their vpc-endpoints 

2764 

2765 :example: 

2766 

2767 .. code-block:: yaml 

2768 

2769 policies: 

2770 - name: athena-endpoint-enabled 

2771 resource: subnet 

2772 filters: 

2773 - type: vpc-endpoint 

2774 key: ServiceName 

2775 value: com.amazonaws.us-east-1.athena 

2776 """ 

2777 RelatedResource = "c7n.resources.vpc.VpcEndpoint" 

2778 RelatedIdsExpression = "SubnetId" 

2779 RelatedResourceByIdExpression = "SubnetIds" 

2780 AnnotationKey = "matched-vpc-endpoint" 

2781 

2782 schema = type_schema( 

2783 'vpc-endpoint', 

2784 rinherit=ValueFilter.schema) 

2785 

2786 

2787@resources.register('vpc-endpoint-service-configuration') 

2788class VPCEndpointServiceConfiguration(query.QueryResourceManager): 

2789 """ 

2790 Resource manager for VPC Endpoint Service Configurations. 

2791 

2792 :example: 

2793 

2794 .. code-block:: yaml 

2795 

2796 policies: 

2797 - name: acceptance-not-enabled 

2798 resource: aws.vpc-endpoint-service-configuration 

2799 filters: 

2800 - AcceptanceRequired: false 

2801 

2802 """ 

2803 class resource_type(query.TypeInfo): 

2804 service = 'ec2' 

2805 enum_spec = ('describe_vpc_endpoint_service_configurations', 

2806 'ServiceConfigurations', None) 

2807 name = id = 'ServiceId' # ServiceName contains DNS 

2808 id_prefix = 'vpce-svc-' 

2809 filter_name = 'ServiceIds' 

2810 filter_type = 'list' 

2811 cfn_type = config_type = 'AWS::EC2::VPCEndpointService' 

2812 arn_type = 'vpc-endpoint-service' 

2813 arn_separator = '/' 

2814 default_report_fields = ( 

2815 'ServiceId', 

2816 'ServiceState' 

2817 ) 

2818 

2819 

2820@resources.register('key-pair') 

2821class KeyPair(query.QueryResourceManager): 

2822 

2823 class resource_type(query.TypeInfo): 

2824 service = 'ec2' 

2825 arn_type = 'key-pair' 

2826 enum_spec = ('describe_key_pairs', 'KeyPairs', None) 

2827 name = 'KeyName' 

2828 id = 'KeyPairId' 

2829 id_prefix = 'key-' 

2830 filter_name = 'KeyNames' 

2831 filter_type = 'list' 

2832 

2833 

2834@KeyPair.filter_registry.register('unused') 

2835class UnusedKeyPairs(Filter): 

2836 """Filter for used or unused keys. 

2837 

2838 The default is unused but can be changed by using the state property. 

2839 

2840 :example: 

2841 

2842 .. code-block:: yaml 

2843 

2844 policies: 

2845 - name: unused-key-pairs 

2846 resource: aws.key-pair 

2847 filters: 

2848 - unused 

2849 - name: used-key-pairs 

2850 resource: aws.key-pair 

2851 filters: 

2852 - type: unused 

2853 state: false 

2854 """ 

2855 schema = type_schema('unused', 

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

2857 

2858 def get_permissions(self): 

2859 return list(itertools.chain(*[ 

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

2861 for m in ('asg', 'launch-config', 'ec2')])) 

2862 

2863 def _pull_asg_keynames(self): 

2864 asgs = self.manager.get_resource_manager('asg').resources() 

2865 key_names = set() 

2866 lcfgs = set(a['LaunchConfigurationName'] for a in asgs if 'LaunchConfigurationName' in a) 

2867 lcfg_mgr = self.manager.get_resource_manager('launch-config') 

2868 

2869 if lcfgs: 

2870 key_names.update([ 

2871 lcfg['KeyName'] for lcfg in lcfg_mgr.resources() 

2872 if lcfg['LaunchConfigurationName'] in lcfgs]) 

2873 

2874 tmpl_mgr = self.manager.get_resource_manager('launch-template-version') 

2875 for tversion in tmpl_mgr.get_resources( 

2876 list(tmpl_mgr.get_asg_templates(asgs).keys())): 

2877 key_names.add(tversion['LaunchTemplateData'].get('KeyName')) 

2878 return key_names 

2879 

2880 def _pull_ec2_keynames(self): 

2881 ec2_manager = self.manager.get_resource_manager('ec2') 

2882 return {i.get('KeyName', None) for i in ec2_manager.resources()} 

2883 

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

2885 keynames = self._pull_ec2_keynames().union(self._pull_asg_keynames()) 

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

2887 return [r for r in resources if r['KeyName'] not in keynames] 

2888 return [r for r in resources if r['KeyName'] in keynames] 

2889 

2890 

2891@KeyPair.action_registry.register('delete') 

2892class DeleteUnusedKeyPairs(BaseAction): 

2893 """Delete all ec2 keys that are not in use 

2894 

2895 This should always be used with the unused filter 

2896 and it will prevent you from using without it. 

2897 

2898 :example: 

2899 

2900 .. code-block:: yaml 

2901 

2902 policies: 

2903 - name: delete-unused-key-pairs 

2904 resource: aws.key-pair 

2905 filters: 

2906 - unused 

2907 actions: 

2908 - delete 

2909 """ 

2910 permissions = ('ec2:DeleteKeyPair',) 

2911 schema = type_schema('delete') 

2912 

2913 def validate(self): 

2914 if not [f for f in self.manager.iter_filters() if isinstance(f, UnusedKeyPairs)]: 

2915 raise PolicyValidationError( 

2916 "delete should be used in conjunction with the unused filter on %s" % ( 

2917 self.manager.data,)) 

2918 if [True for f in self.manager.iter_filters() if f.data.get('state') is False]: 

2919 raise PolicyValidationError( 

2920 "You policy has filtered used keys you should use this with unused keys %s" % ( 

2921 self.manager.data,)) 

2922 return self 

2923 

2924 def process(self, unused): 

2925 client = local_session(self.manager.session_factory).client('ec2') 

2926 for key in unused: 

2927 client.delete_key_pair(KeyPairId=key['KeyPairId']) 

2928 

2929 

2930@Vpc.action_registry.register('set-flow-log') 

2931@Subnet.action_registry.register('set-flow-log') 

2932@NetworkInterface.action_registry.register('set-flow-log') 

2933@TransitGateway.action_registry.register('set-flow-log') 

2934@TransitGatewayAttachment.action_registry.register('set-flow-log') 

2935class SetFlowLogs(BaseAction): 

2936 """Set flow logs for a network resource 

2937 

2938 :example: 

2939 

2940 .. code-block:: yaml 

2941 

2942 policies: 

2943 - name: vpc-enable-flow-logs 

2944 resource: vpc 

2945 filters: 

2946 - type: flow-logs 

2947 enabled: false 

2948 actions: 

2949 - type: set-flow-log 

2950 attrs: 

2951 DeliverLogsPermissionArn: arn:iam:role 

2952 LogGroupName: /custodian/vpc/flowlogs/ 

2953 

2954 `attrs` are passed through to create_flow_log and are per the api 

2955 documentation 

2956 

2957 https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/create_flow_logs.html 

2958 """ # noqa 

2959 

2960 legacy_schema = { 

2961 'DeliverLogsPermissionArn': {'type': 'string'}, 

2962 'LogGroupName': {'type': 'string'}, 

2963 'LogDestination': {'type': 'string'}, 

2964 'LogFormat': {'type': 'string'}, 

2965 'MaxAggregationInterval': {'type': 'integer'}, 

2966 'LogDestinationType': {'enum': ['s3', 'cloud-watch-logs']}, 

2967 'TrafficType': { 

2968 'type': 'string', 

2969 'enum': ['ACCEPT', 'REJECT', 'ALL'] 

2970 } 

2971 } 

2972 

2973 schema = type_schema( 

2974 'set-flow-log', 

2975 state={'type': 'boolean'}, 

2976 attrs={'type': 'object'}, 

2977 **legacy_schema 

2978 ) 

2979 shape = 'CreateFlowLogsRequest' 

2980 permissions = ('ec2:CreateFlowLogs', 'logs:CreateLogGroup',) 

2981 

2982 RESOURCE_ALIAS = { 

2983 'vpc': 'VPC', 

2984 'subnet': 'Subnet', 

2985 'eni': 'NetworkInterface', 

2986 'transit-gateway': 'TransitGateway', 

2987 'transit-attachment': 'TransitGatewayAttachment' 

2988 } 

2989 

2990 def get_deprecations(self): 

2991 filter_name = self.data["type"] 

2992 return [ 

2993 DeprecatedField(f"{filter_name}.{k}", f"set {k} under attrs: block") 

2994 for k in set(self.legacy_schema).intersection(self.data) 

2995 ] 

2996 

2997 def validate(self): 

2998 if set(self.legacy_schema).intersection(self.data) and 'attrs' in self.data: 

2999 raise PolicyValidationError( 

3000 "set-flow-log: legacy top level keys aren't compatible with `attrs` mapping" 

3001 ) 

3002 

3003 self.convert() 

3004 attrs = dict(self.data['attrs']) 

3005 model = self.manager.get_model() 

3006 attrs['ResourceType'] = self.RESOURCE_ALIAS[model.arn_type] 

3007 attrs['ResourceIds'] = [model.id_prefix + '123'] 

3008 return shape_validate(attrs, self.shape, 'ec2') 

3009 

3010 def convert(self): 

3011 data = dict(self.data) 

3012 attrs = {} 

3013 for k in set(self.legacy_schema).intersection(data): 

3014 attrs[k] = data.pop(k) 

3015 self.source_data = self.data 

3016 self.data['attrs'] = merge_dict(attrs, self.data.get('attrs', {})) 

3017 

3018 def run_client_op(self, op, params, log_err_codes=()): 

3019 try: 

3020 results = op(**params) 

3021 for r in results['Unsuccessful']: 

3022 self.log.exception( 

3023 'Exception: %s for %s: %s', 

3024 op.__name__, r['ResourceId'], r['Error']['Message']) 

3025 except ClientError as e: 

3026 if e.response['Error']['Code'] in log_err_codes: 

3027 self.log.exception( 

3028 'Exception: %s: %s', 

3029 op.response['Error']['Message']) 

3030 else: 

3031 raise 

3032 

3033 def ensure_log_group(self, logroup): 

3034 client = local_session(self.manager.session_factory).client('logs') 

3035 try: 

3036 client.create_log_group(logGroupName=logroup) 

3037 except client.exceptions.ResourceAlreadyExistsException: 

3038 pass 

3039 

3040 def delete_flow_logs(self, client, rids): 

3041 flow_logs = [ 

3042 r for r in self.manager.get_resource_manager('flow-log').resources() 

3043 if r['ResourceId'] in rids] 

3044 self.run_client_op( 

3045 client.delete_flow_logs, 

3046 {'FlowLogIds': [f['FlowLogId'] for f in flow_logs]}, 

3047 ('InvalidParameterValue', 'InvalidFlowLogId.NotFound',) 

3048 ) 

3049 

3050 def process(self, resources): 

3051 client = local_session(self.manager.session_factory).client('ec2') 

3052 enabled = self.data.get('state', True) 

3053 

3054 if not enabled: 

3055 model_id = self.manager.get_model().id 

3056 rids = [r[model_id] for r in resources] 

3057 return self.delete_flow_logs(client, rids) 

3058 

3059 model = self.manager.get_model() 

3060 params = {'ResourceIds': [r[model.id] for r in resources]} 

3061 params['ResourceType'] = self.RESOURCE_ALIAS[model.arn_type] 

3062 params.update(self.data['attrs']) 

3063 if params.get('LogDestinationType', 'cloud-watch-logs') == 'cloud-watch-logs': 

3064 self.ensure_log_group(params['LogGroupName']) 

3065 self.run_client_op( 

3066 client.create_flow_logs, params, ('FlowLogAlreadyExists',)) 

3067 

3068 

3069class PrefixListDescribe(query.DescribeSource): 

3070 

3071 def get_resources(self, ids, cache=True): 

3072 query = {'Filters': [ 

3073 {'Name': 'prefix-list-id', 

3074 'Values': ids}]} 

3075 return self.query.filter(self.manager, **query) 

3076 

3077 

3078@resources.register('prefix-list') 

3079class PrefixList(query.QueryResourceManager): 

3080 

3081 class resource_type(query.TypeInfo): 

3082 service = 'ec2' 

3083 arn_type = 'prefix-list' 

3084 enum_spec = ('describe_managed_prefix_lists', 'PrefixLists', None) 

3085 config_type = cfn_type = "AWS::EC2::PrefixList" 

3086 name = 'PrefixListName' 

3087 id = 'PrefixListId' 

3088 id_prefix = 'pl-' 

3089 universal_taggable = object() 

3090 

3091 source_mapping = {'describe': PrefixListDescribe} 

3092 

3093 

3094@PrefixList.filter_registry.register('entry') 

3095class Entry(Filter): 

3096 

3097 schema = type_schema( 

3098 'entry', rinherit=ValueFilter.schema) 

3099 permissions = ('ec2:GetManagedPrefixListEntries',) 

3100 

3101 annotation_key = 'c7n:prefix-entries' 

3102 match_annotation_key = 'c7n:matched-entries' 

3103 

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

3105 client = local_session(self.manager.session_factory).client('ec2') 

3106 for r in resources: 

3107 if self.annotation_key in r: 

3108 continue 

3109 r[self.annotation_key] = client.get_managed_prefix_list_entries( 

3110 PrefixListId=r['PrefixListId']).get('Entries', ()) 

3111 

3112 vf = ValueFilter(self.data) 

3113 vf.annotate = False 

3114 

3115 results = [] 

3116 for r in resources: 

3117 matched = [] 

3118 for e in r[self.annotation_key]: 

3119 if vf(e): 

3120 matched.append(e) 

3121 if matched: 

3122 results.append(r) 

3123 r[self.match_annotation_key] = matched 

3124 return results 

3125 

3126 

3127@Subnet.action_registry.register('modify') 

3128class SubnetModifyAtrributes(BaseAction): 

3129 """Modify subnet attributes. 

3130 

3131 :example: 

3132 

3133 .. code-block:: yaml 

3134 

3135 policies: 

3136 - name: turn-on-public-ip-protection 

3137 resource: aws.subnet 

3138 filters: 

3139 - type: value 

3140 key: "MapPublicIpOnLaunch.enabled" 

3141 value: false 

3142 actions: 

3143 - type: modify 

3144 MapPublicIpOnLaunch: false 

3145 """ 

3146 

3147 schema = type_schema( 

3148 "modify", 

3149 AssignIpv6AddressOnCreation={'type': 'boolean'}, 

3150 CustomerOwnedIpv4Pool={'type': 'string'}, 

3151 DisableLniAtDeviceIndex={'type': 'boolean'}, 

3152 EnableLniAtDeviceIndex={'type': 'integer'}, 

3153 EnableResourceNameDnsAAAARecordOnLaunch={'type': 'boolean'}, 

3154 EnableResourceNameDnsARecordOnLaunch={'type': 'boolean'}, 

3155 EnableDns64={'type': 'boolean'}, 

3156 MapPublicIpOnLaunch={'type': 'boolean'}, 

3157 MapCustomerOwnedIpOnLaunch={'type': 'boolean'}, 

3158 PrivateDnsHostnameTypeOnLaunch={ 

3159 'type': 'string', 'enum': ['ip-name', 'resource-name'] 

3160 } 

3161 ) 

3162 

3163 permissions = ("ec2:ModifySubnetAttribute",) 

3164 

3165 def process(self, resources): 

3166 client = local_session(self.manager.session_factory).client('ec2') 

3167 params = dict(self.data) 

3168 params.pop('type') 

3169 

3170 for k in list(params): 

3171 if isinstance(params[k], bool): 

3172 params[k] = {'Value': params[k]} 

3173 

3174 for r in resources: 

3175 self.manager.retry( 

3176 client.modify_subnet_attribute, 

3177 SubnetId=r['SubnetId'], **params) 

3178 return resources 

3179 

3180 

3181@resources.register('mirror-session') 

3182class TrafficMirrorSession(query.QueryResourceManager): 

3183 

3184 class resource_type(query.TypeInfo): 

3185 service = 'ec2' 

3186 enum_spec = ('describe_traffic_mirror_sessions', 'TrafficMirrorSessions', None) 

3187 name = id = 'TrafficMirrorSessionId' 

3188 config_type = cfn_type = 'AWS::EC2::TrafficMirrorSession' 

3189 arn_type = 'traffic-mirror-session' 

3190 universal_taggable = object() 

3191 id_prefix = 'tms-' 

3192 

3193 

3194@TrafficMirrorSession.action_registry.register('delete') 

3195class DeleteTrafficMirrorSession(BaseAction): 

3196 """Action to delete traffic mirror session(s) 

3197 

3198 :example: 

3199 

3200 .. code-block:: yaml 

3201 

3202 policies: 

3203 - name: traffic-mirror-session-paclength 

3204 resource: mirror-session 

3205 filters: 

3206 - type: value 

3207 key: tag:Owner 

3208 value: xyz 

3209 actions: 

3210 - delete 

3211 """ 

3212 

3213 schema = type_schema('delete') 

3214 permissions = ('ec2:DeleteTrafficMirrorSession',) 

3215 

3216 def process(self, resources): 

3217 client = local_session(self.manager.session_factory).client('ec2') 

3218 for r in resources: 

3219 client.delete_traffic_mirror_session(TrafficMirrorSessionId=r['TrafficMirrorSessionId']) 

3220 

3221 

3222@resources.register('mirror-target') 

3223class TrafficMirrorTarget(query.QueryResourceManager): 

3224 

3225 class resource_type(query.TypeInfo): 

3226 service = 'ec2' 

3227 enum_spec = ('describe_traffic_mirror_targets', 'TrafficMirrorTargets', None) 

3228 name = id = 'TrafficMirrorTargetId' 

3229 config_type = cfn_type = 'AWS::EC2::TrafficMirrorTarget' 

3230 arn_type = 'traffic-mirror-target' 

3231 universal_taggable = object() 

3232 id_prefix = 'tmt-' 

3233 

3234 

3235@RouteTable.filter_registry.register('cross-az-nat-gateway-route') 

3236class CrossAZRouteTable(Filter): 

3237 """Filter route-tables to find those with routes which send traffic 

3238 from a subnet in an az to a nat gateway in a different az. 

3239 

3240 This filter is useful for cost optimization, resiliency, and 

3241 performance use-cases, where we don't want network traffic to 

3242 cross from one availability zone (AZ) to another AZ. 

3243 

3244 :Example: 

3245 

3246 .. code-block:: yaml 

3247 

3248 policies: 

3249 - name: cross-az-nat-gateway-traffic 

3250 resource: aws.route-table 

3251 filters: 

3252 - type: cross-az-nat-gateway-route 

3253 actions: 

3254 - notify 

3255 

3256 """ 

3257 schema = type_schema('cross-az-nat-gateway-route') 

3258 permissions = ("ec2:DescribeRouteTables", "ec2:DescribeNatGateways", "ec2:DescribeSubnets") 

3259 

3260 table_annotation = "c7n:route-table" 

3261 mismatch_annotation = "c7n:nat-az-mismatch" 

3262 

3263 def resolve_subnets(self, resource, subnets): 

3264 return {s['SubnetId'] for s in subnets 

3265 if s[self.table_annotation] == resource['RouteTableId']} 

3266 

3267 def annotate_subnets_table(self, tables: list, subnets: dict): 

3268 # annotate route table associations onto their respective subnets 

3269 main_tables = [] 

3270 # annotate explicit associations 

3271 for t in tables: 

3272 for association in t['Associations']: 

3273 if association.get('SubnetId'): 

3274 subnets[association['SubnetId']][ 

3275 self.table_annotation] = t['RouteTableId'] 

3276 if association.get('Main'): 

3277 main_tables.append(t) 

3278 # annotate main tables 

3279 for s in subnets.values(): 

3280 if self.table_annotation in s: 

3281 continue 

3282 for t in main_tables: 

3283 if t['VpcId'] == s['VpcId']: 

3284 s[self.table_annotation] = t['RouteTableId'] 

3285 

3286 def process_route_table(self, subnets, nat_subnets, resource): 

3287 matched = {} 

3288 found = False 

3289 associated_subnets = self.resolve_subnets(resource, subnets.values()) 

3290 for route in resource['Routes']: 

3291 if not route.get("NatGatewayId") or route.get("State") != "active": 

3292 continue 

3293 nat_az = subnets[nat_subnets[route['NatGatewayId']]]['AvailabilityZone'] 

3294 mismatch_subnets = { 

3295 s: subnets[s]['AvailabilityZone'] for s in associated_subnets 

3296 if subnets[s]['AvailabilityZone'] != nat_az} 

3297 if not mismatch_subnets: 

3298 continue 

3299 found = True 

3300 matched.setdefault(route['NatGatewayId'], {})['NatGatewayAz'] = nat_az 

3301 matched[route['NatGatewayId']].setdefault('Subnets', {}).update(mismatch_subnets) 

3302 if not found: 

3303 return 

3304 resource[self.mismatch_annotation] = matched 

3305 return resource 

3306 

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

3308 subnets = { 

3309 s['SubnetId']: s for s in 

3310 self.manager.get_resource_manager('aws.subnet').resources() 

3311 } 

3312 nat_subnets = { 

3313 nat_gateway['NatGatewayId']: nat_gateway["SubnetId"] 

3314 for nat_gateway in self.manager.get_resource_manager('nat-gateway').resources()} 

3315 

3316 results = [] 

3317 self.annotate_subnets_table(resources, subnets) 

3318 for resource in resources: 

3319 if self.process_route_table(subnets, nat_subnets, resource): 

3320 results.append(resource) 

3321 

3322 return results 

3323 

3324 

3325@NetworkAddress.filter_registry.register('used-by') 

3326class UsedByNetworkAddress(Filter): 

3327 """Filter Elastic IPs to find the resource type that the network 

3328 interface that the Elastic IP is associated with is attached to. 

3329 

3330 This filter is useful for limiting the types of resources to 

3331 enable AWS Shield Advanced protection. 

3332 

3333 :Example: 

3334 

3335 .. code-block:: yaml 

3336 

3337 policies: 

3338 - name: eip-shield-advanced-enable 

3339 resource: aws.elastic-ip 

3340 filters: 

3341 - type: used-by 

3342 resource-type: elb-net 

3343 - type: shield-enabled 

3344 state: false 

3345 actions: 

3346 - type: set-shield 

3347 state: true 

3348 """ 

3349 schema = type_schema( 

3350 'used-by', required=['resource-type'], **{ 

3351 'resource-type': {'type': 'string'}} 

3352 ) 

3353 permissions = ("ec2:DescribeNetworkInterfaces",) 

3354 

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

3356 eni_ids = [] 

3357 for r in resources: 

3358 if r.get('NetworkInterfaceId'): 

3359 eni_ids.append(r['NetworkInterfaceId']) 

3360 enis = self.manager.get_resource_manager('eni').get_resources(eni_ids) 

3361 results = [] 

3362 for r in resources: 

3363 for eni in enis: 

3364 if r.get('NetworkInterfaceId') == eni['NetworkInterfaceId']: 

3365 rtype = get_eni_resource_type(eni) 

3366 if rtype == self.data.get('resource-type'): 

3367 results.append(r) 

3368 return results