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

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

1533 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('client-vpn-endpoint') 

2653class ClientVpnEndpoint(query.QueryResourceManager): 

2654 

2655 class resource_type(query.TypeInfo): 

2656 service = 'ec2' 

2657 arn_type = 'client-vpn-endpoint' 

2658 enum_spec = ('describe_client_vpn_endpoints', 'ClientVpnEndpoints', None) 

2659 name = id = 'ClientVpnEndpointId' 

2660 filter_name = 'ClientVpnEndpointIds' 

2661 filter_type = 'list' 

2662 cfn_type = config_type = 'AWS::EC2::ClientVpnEndpoint' 

2663 id_prefix = 'cvpn-endpoint-' 

2664 metrics_namespace = 'AWS/ClientVPN' 

2665 dimension = 'ClientVpnEndpointId' 

2666 

2667 

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

2669class VpcEndpoint(query.QueryResourceManager): 

2670 

2671 class resource_type(query.TypeInfo): 

2672 service = 'ec2' 

2673 arn_type = 'vpc-endpoint' 

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

2675 name = id = 'VpcEndpointId' 

2676 metrics_namespace = "AWS/PrivateLinkEndpoints" 

2677 date = 'CreationTimestamp' 

2678 filter_name = 'VpcEndpointIds' 

2679 filter_type = 'list' 

2680 id_prefix = "vpce-" 

2681 universal_taggable = object() 

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

2683 

2684 

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

2686class VpcEndpointMetricsFilter(MetricsFilter): 

2687 

2688 def get_dimensions(self, resource): 

2689 return [ 

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

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

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

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

2694 ] 

2695 

2696 

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

2698class EndpointPolicyStatementFilter(HasStatementFilter): 

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

2700 

2701 :example: 

2702 

2703 .. code-block:: yaml 

2704 

2705 policies: 

2706 - name: vpc-endpoint-policy 

2707 resource: aws.vpc-endpoint 

2708 filters: 

2709 - type: has-statement 

2710 statements: 

2711 - Action: "*" 

2712 Effect: "Allow" 

2713 """ 

2714 

2715 policy_attribute = 'PolicyDocument' 

2716 permissions = ('ec2:DescribeVpcEndpoints',) 

2717 

2718 def get_std_format_args(self, endpoint): 

2719 return { 

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

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

2722 'region': self.manager.config.region 

2723 } 

2724 

2725 

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

2727class EndpointCrossAccountFilter(CrossAccountAccessFilter): 

2728 

2729 policy_attribute = 'PolicyDocument' 

2730 annotation_key = 'c7n:CrossAccountViolations' 

2731 permissions = ('ec2:DescribeVpcEndpoints',) 

2732 

2733 

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

2735class EndpointSecurityGroupFilter(net_filters.SecurityGroupFilter): 

2736 

2737 RelatedIdsExpression = "Groups[].GroupId" 

2738 

2739 

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

2741class EndpointSubnetFilter(net_filters.SubnetFilter): 

2742 

2743 RelatedIdsExpression = "SubnetIds[]" 

2744 

2745 

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

2747class EndpointVpcFilter(net_filters.VpcFilter): 

2748 

2749 RelatedIdsExpression = "VpcId" 

2750 

2751 

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

2753class VPCEndpointFilter(RelatedResourceByIdFilter): 

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

2755 

2756 :example: 

2757 

2758 .. code-block:: yaml 

2759 

2760 policies: 

2761 - name: s3-vpc-endpoint-enabled 

2762 resource: vpc 

2763 filters: 

2764 - type: vpc-endpoint 

2765 key: ServiceName 

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

2767 """ 

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

2769 RelatedIdsExpression = "VpcId" 

2770 AnnotationKey = "matched-vpc-endpoint" 

2771 

2772 schema = type_schema( 

2773 'vpc-endpoint', 

2774 rinherit=ValueFilter.schema) 

2775 

2776 

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

2778class SubnetEndpointFilter(RelatedResourceByIdFilter): 

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

2780 

2781 :example: 

2782 

2783 .. code-block:: yaml 

2784 

2785 policies: 

2786 - name: athena-endpoint-enabled 

2787 resource: subnet 

2788 filters: 

2789 - type: vpc-endpoint 

2790 key: ServiceName 

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

2792 """ 

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

2794 RelatedIdsExpression = "SubnetId" 

2795 RelatedResourceByIdExpression = "SubnetIds" 

2796 AnnotationKey = "matched-vpc-endpoint" 

2797 

2798 schema = type_schema( 

2799 'vpc-endpoint', 

2800 rinherit=ValueFilter.schema) 

2801 

2802 

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

2804class VPCEndpointServiceConfiguration(query.QueryResourceManager): 

2805 """ 

2806 Resource manager for VPC Endpoint Service Configurations. 

2807 

2808 :example: 

2809 

2810 .. code-block:: yaml 

2811 

2812 policies: 

2813 - name: acceptance-not-enabled 

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

2815 filters: 

2816 - AcceptanceRequired: false 

2817 

2818 """ 

2819 class resource_type(query.TypeInfo): 

2820 service = 'ec2' 

2821 enum_spec = ('describe_vpc_endpoint_service_configurations', 

2822 'ServiceConfigurations', None) 

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

2824 id_prefix = 'vpce-svc-' 

2825 filter_name = 'ServiceIds' 

2826 filter_type = 'list' 

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

2828 arn_type = 'vpc-endpoint-service' 

2829 arn_separator = '/' 

2830 default_report_fields = ( 

2831 'ServiceId', 

2832 'ServiceState' 

2833 ) 

2834 

2835 

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

2837class KeyPair(query.QueryResourceManager): 

2838 

2839 class resource_type(query.TypeInfo): 

2840 service = 'ec2' 

2841 arn_type = 'key-pair' 

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

2843 name = 'KeyName' 

2844 id = 'KeyPairId' 

2845 id_prefix = 'key-' 

2846 filter_name = 'KeyNames' 

2847 filter_type = 'list' 

2848 

2849 

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

2851class UnusedKeyPairs(Filter): 

2852 """Filter for used or unused keys. 

2853 

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

2855 

2856 :example: 

2857 

2858 .. code-block:: yaml 

2859 

2860 policies: 

2861 - name: unused-key-pairs 

2862 resource: aws.key-pair 

2863 filters: 

2864 - unused 

2865 - name: used-key-pairs 

2866 resource: aws.key-pair 

2867 filters: 

2868 - type: unused 

2869 state: false 

2870 """ 

2871 schema = type_schema('unused', 

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

2873 

2874 def get_permissions(self): 

2875 return list(itertools.chain(*[ 

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

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

2878 

2879 def _pull_asg_keynames(self): 

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

2881 key_names = set() 

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

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

2884 

2885 if lcfgs: 

2886 key_names.update([ 

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

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

2889 

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

2891 for tversion in tmpl_mgr.get_resources( 

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

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

2894 return key_names 

2895 

2896 def _pull_ec2_keynames(self): 

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

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

2899 

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

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

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

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

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

2905 

2906 

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

2908class DeleteUnusedKeyPairs(BaseAction): 

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

2910 

2911 This should always be used with the unused filter 

2912 and it will prevent you from using without it. 

2913 

2914 :example: 

2915 

2916 .. code-block:: yaml 

2917 

2918 policies: 

2919 - name: delete-unused-key-pairs 

2920 resource: aws.key-pair 

2921 filters: 

2922 - unused 

2923 actions: 

2924 - delete 

2925 """ 

2926 permissions = ('ec2:DeleteKeyPair',) 

2927 schema = type_schema('delete') 

2928 

2929 def validate(self): 

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

2931 raise PolicyValidationError( 

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

2933 self.manager.data,)) 

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

2935 raise PolicyValidationError( 

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

2937 self.manager.data,)) 

2938 return self 

2939 

2940 def process(self, unused): 

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

2942 for key in unused: 

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

2944 

2945 

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

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

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

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

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

2951class SetFlowLogs(BaseAction): 

2952 """Set flow logs for a network resource 

2953 

2954 :example: 

2955 

2956 .. code-block:: yaml 

2957 

2958 policies: 

2959 - name: vpc-enable-flow-logs 

2960 resource: vpc 

2961 filters: 

2962 - type: flow-logs 

2963 enabled: false 

2964 actions: 

2965 - type: set-flow-log 

2966 attrs: 

2967 DeliverLogsPermissionArn: arn:iam:role 

2968 LogGroupName: /custodian/vpc/flowlogs/ 

2969 

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

2971 documentation 

2972 

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

2974 """ # noqa 

2975 

2976 legacy_schema = { 

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

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

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

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

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

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

2983 'TrafficType': { 

2984 'type': 'string', 

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

2986 } 

2987 } 

2988 

2989 schema = type_schema( 

2990 'set-flow-log', 

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

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

2993 **legacy_schema 

2994 ) 

2995 shape = 'CreateFlowLogsRequest' 

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

2997 

2998 RESOURCE_ALIAS = { 

2999 'vpc': 'VPC', 

3000 'subnet': 'Subnet', 

3001 'eni': 'NetworkInterface', 

3002 'transit-gateway': 'TransitGateway', 

3003 'transit-attachment': 'TransitGatewayAttachment' 

3004 } 

3005 

3006 def get_deprecations(self): 

3007 filter_name = self.data["type"] 

3008 return [ 

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

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

3011 ] 

3012 

3013 def validate(self): 

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

3015 raise PolicyValidationError( 

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

3017 ) 

3018 

3019 self.convert() 

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

3021 model = self.manager.get_model() 

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

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

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

3025 

3026 def convert(self): 

3027 data = dict(self.data) 

3028 attrs = {} 

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

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

3031 self.source_data = self.data 

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

3033 

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

3035 try: 

3036 results = op(**params) 

3037 for r in results['Unsuccessful']: 

3038 self.log.exception( 

3039 'Exception: %s for %s: %s', 

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

3041 except ClientError as e: 

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

3043 self.log.exception( 

3044 'Exception: %s: %s', 

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

3046 else: 

3047 raise 

3048 

3049 def ensure_log_group(self, logroup): 

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

3051 try: 

3052 client.create_log_group(logGroupName=logroup) 

3053 except client.exceptions.ResourceAlreadyExistsException: 

3054 pass 

3055 

3056 def delete_flow_logs(self, client, rids): 

3057 flow_logs = [ 

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

3059 if r['ResourceId'] in rids] 

3060 self.run_client_op( 

3061 client.delete_flow_logs, 

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

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

3064 ) 

3065 

3066 def process(self, resources): 

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

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

3069 

3070 if not enabled: 

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

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

3073 return self.delete_flow_logs(client, rids) 

3074 

3075 model = self.manager.get_model() 

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

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

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

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

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

3081 self.run_client_op( 

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

3083 

3084 

3085class PrefixListDescribe(query.DescribeSource): 

3086 

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

3088 query = {'Filters': [ 

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

3090 'Values': ids}]} 

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

3092 

3093 

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

3095class PrefixList(query.QueryResourceManager): 

3096 

3097 class resource_type(query.TypeInfo): 

3098 service = 'ec2' 

3099 arn_type = 'prefix-list' 

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

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

3102 name = 'PrefixListName' 

3103 id = 'PrefixListId' 

3104 id_prefix = 'pl-' 

3105 universal_taggable = object() 

3106 

3107 source_mapping = {'describe': PrefixListDescribe} 

3108 

3109 

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

3111class Entry(Filter): 

3112 

3113 schema = type_schema( 

3114 'entry', rinherit=ValueFilter.schema) 

3115 permissions = ('ec2:GetManagedPrefixListEntries',) 

3116 

3117 annotation_key = 'c7n:prefix-entries' 

3118 match_annotation_key = 'c7n:matched-entries' 

3119 

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

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

3122 for r in resources: 

3123 if self.annotation_key in r: 

3124 continue 

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

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

3127 

3128 vf = ValueFilter(self.data) 

3129 vf.annotate = False 

3130 

3131 results = [] 

3132 for r in resources: 

3133 matched = [] 

3134 for e in r[self.annotation_key]: 

3135 if vf(e): 

3136 matched.append(e) 

3137 if matched: 

3138 results.append(r) 

3139 r[self.match_annotation_key] = matched 

3140 return results 

3141 

3142 

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

3144class SubnetModifyAtrributes(BaseAction): 

3145 """Modify subnet attributes. 

3146 

3147 :example: 

3148 

3149 .. code-block:: yaml 

3150 

3151 policies: 

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

3153 resource: aws.subnet 

3154 filters: 

3155 - type: value 

3156 key: "MapPublicIpOnLaunch.enabled" 

3157 value: false 

3158 actions: 

3159 - type: modify 

3160 MapPublicIpOnLaunch: false 

3161 """ 

3162 

3163 schema = type_schema( 

3164 "modify", 

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

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

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

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

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

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

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

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

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

3174 PrivateDnsHostnameTypeOnLaunch={ 

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

3176 } 

3177 ) 

3178 

3179 permissions = ("ec2:ModifySubnetAttribute",) 

3180 

3181 def process(self, resources): 

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

3183 params = dict(self.data) 

3184 params.pop('type') 

3185 

3186 for k in list(params): 

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

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

3189 

3190 for r in resources: 

3191 self.manager.retry( 

3192 client.modify_subnet_attribute, 

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

3194 return resources 

3195 

3196 

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

3198class TrafficMirrorSession(query.QueryResourceManager): 

3199 

3200 class resource_type(query.TypeInfo): 

3201 service = 'ec2' 

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

3203 name = id = 'TrafficMirrorSessionId' 

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

3205 arn_type = 'traffic-mirror-session' 

3206 universal_taggable = object() 

3207 id_prefix = 'tms-' 

3208 

3209 

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

3211class DeleteTrafficMirrorSession(BaseAction): 

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

3213 

3214 :example: 

3215 

3216 .. code-block:: yaml 

3217 

3218 policies: 

3219 - name: traffic-mirror-session-paclength 

3220 resource: mirror-session 

3221 filters: 

3222 - type: value 

3223 key: tag:Owner 

3224 value: xyz 

3225 actions: 

3226 - delete 

3227 """ 

3228 

3229 schema = type_schema('delete') 

3230 permissions = ('ec2:DeleteTrafficMirrorSession',) 

3231 

3232 def process(self, resources): 

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

3234 for r in resources: 

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

3236 

3237 

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

3239class TrafficMirrorTarget(query.QueryResourceManager): 

3240 

3241 class resource_type(query.TypeInfo): 

3242 service = 'ec2' 

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

3244 name = id = 'TrafficMirrorTargetId' 

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

3246 arn_type = 'traffic-mirror-target' 

3247 universal_taggable = object() 

3248 id_prefix = 'tmt-' 

3249 

3250 

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

3252class CrossAZRouteTable(Filter): 

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

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

3255 

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

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

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

3259 

3260 :Example: 

3261 

3262 .. code-block:: yaml 

3263 

3264 policies: 

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

3266 resource: aws.route-table 

3267 filters: 

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

3269 actions: 

3270 - notify 

3271 

3272 """ 

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

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

3275 

3276 table_annotation = "c7n:route-table" 

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

3278 

3279 def resolve_subnets(self, resource, subnets): 

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

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

3282 

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

3284 # annotate route table associations onto their respective subnets 

3285 main_tables = [] 

3286 # annotate explicit associations 

3287 for t in tables: 

3288 for association in t['Associations']: 

3289 if association.get('SubnetId'): 

3290 subnets[association['SubnetId']][ 

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

3292 if association.get('Main'): 

3293 main_tables.append(t) 

3294 # annotate main tables 

3295 for s in subnets.values(): 

3296 if self.table_annotation in s: 

3297 continue 

3298 for t in main_tables: 

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

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

3301 

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

3303 matched = {} 

3304 found = False 

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

3306 for route in resource['Routes']: 

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

3308 continue 

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

3310 mismatch_subnets = { 

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

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

3313 if not mismatch_subnets: 

3314 continue 

3315 found = True 

3316 matched.setdefault(route['NatGatewayId'], {})['NatGatewayAz'] = nat_az 

3317 matched[route['NatGatewayId']].setdefault('Subnets', {}).update(mismatch_subnets) 

3318 if not found: 

3319 return 

3320 resource[self.mismatch_annotation] = matched 

3321 return resource 

3322 

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

3324 subnets = { 

3325 s['SubnetId']: s for s in 

3326 self.manager.get_resource_manager('aws.subnet').resources() 

3327 } 

3328 nat_subnets = { 

3329 nat_gateway['NatGatewayId']: nat_gateway["SubnetId"] 

3330 for nat_gateway in self.manager.get_resource_manager('nat-gateway').resources()} 

3331 

3332 results = [] 

3333 self.annotate_subnets_table(resources, subnets) 

3334 for resource in resources: 

3335 if self.process_route_table(subnets, nat_subnets, resource): 

3336 results.append(resource) 

3337 

3338 return results 

3339 

3340 

3341@NetworkAddress.filter_registry.register('used-by') 

3342class UsedByNetworkAddress(Filter): 

3343 """Filter Elastic IPs to find the resource type that the network 

3344 interface that the Elastic IP is associated with is attached to. 

3345 

3346 This filter is useful for limiting the types of resources to 

3347 enable AWS Shield Advanced protection. 

3348 

3349 :Example: 

3350 

3351 .. code-block:: yaml 

3352 

3353 policies: 

3354 - name: eip-shield-advanced-enable 

3355 resource: aws.elastic-ip 

3356 filters: 

3357 - type: used-by 

3358 resource-type: elb-net 

3359 - type: shield-enabled 

3360 state: false 

3361 actions: 

3362 - type: set-shield 

3363 state: true 

3364 """ 

3365 schema = type_schema( 

3366 'used-by', required=['resource-type'], **{ 

3367 'resource-type': {'type': 'string'}} 

3368 ) 

3369 permissions = ("ec2:DescribeNetworkInterfaces",) 

3370 

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

3372 eni_ids = [] 

3373 for r in resources: 

3374 if r.get('NetworkInterfaceId'): 

3375 eni_ids.append(r['NetworkInterfaceId']) 

3376 enis = self.manager.get_resource_manager('eni').get_resources(eni_ids) 

3377 results = [] 

3378 for r in resources: 

3379 for eni in enis: 

3380 if r.get('NetworkInterfaceId') == eni['NetworkInterfaceId']: 

3381 rtype = get_eni_resource_type(eni) 

3382 if rtype == self.data.get('resource-type'): 

3383 results.append(r) 

3384 return results