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

1483 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

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 local_session, 

20 type_schema, 

21 get_retry, 

22 parse_cidr, 

23 get_eni_resource_type, 

24 jmespath_search, 

25 jmespath_compile 

26) 

27from c7n.resources.aws import shape_validate 

28from c7n.resources.shield import IsEIPShieldProtected, SetEIPShieldProtection 

29from c7n.filters.policystatement import HasStatementFilter 

30 

31 

32@resources.register('vpc') 

33class Vpc(query.QueryResourceManager): 

34 

35 class resource_type(query.TypeInfo): 

36 service = 'ec2' 

37 arn_type = 'vpc' 

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

39 name = id = 'VpcId' 

40 filter_name = 'VpcIds' 

41 filter_type = 'list' 

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

43 id_prefix = "vpc-" 

44 

45 

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

47class VpcMetrics(MetricsFilter): 

48 

49 def get_dimensions(self, resource): 

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

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

52 

53 

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

55class ModifyVpc(BaseAction): 

56 """Modify vpc settings 

57 """ 

58 

59 schema = type_schema( 

60 'modify', 

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

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

63 'addressusage': {'type': 'boolean'}} 

64 ) 

65 

66 key_params = ( 

67 ('dnshostnames', 'EnableDnsHostnames'), 

68 ('dnssupport', 'EnableDnsSupport'), 

69 ('addressusage', 'EnableNetworkAddressUsageMetrics') 

70 ) 

71 

72 permissions = ('ec2:ModifyVpcAttribute',) 

73 

74 def process(self, resources): 

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

76 

77 for policy_key, param_name in self.key_params: 

78 if policy_key not in self.data: 

79 continue 

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

81 # can only modify one attribute per request 

82 for r in resources: 

83 params['VpcId'] = r['VpcId'] 

84 client.modify_vpc_attribute(**params) 

85 

86 

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

88class DeleteVpc(BaseAction): 

89 """Delete an empty VPC 

90 

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

92 

93 :example: 

94 

95 .. code-block:: yaml 

96 

97 - name: aws-ec2-vpc-delete 

98 resource: vpc 

99 actions: 

100 - type: delete-empty 

101 

102 """ 

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

104 permissions = ('ec2:DeleteVpc',) 

105 

106 def process(self, resources): 

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

108 

109 for vpc in resources: 

110 self.manager.retry( 

111 client.delete_vpc, 

112 VpcId=vpc['VpcId'], 

113 ignore_err_codes=( 

114 'NoSuchEntityException', 

115 'DeleteConflictException', 

116 ), 

117 ) 

118 

119 

120class DescribeFlow(query.DescribeSource): 

121 

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

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

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

125 

126 

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

128class FlowLog(query.QueryResourceManager): 

129 

130 class resource_type(query.TypeInfo): 

131 

132 service = 'ec2' 

133 arn_type = 'vpc-flow-log' 

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

135 name = id = 'FlowLogId' 

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

137 id_prefix = 'fl-' 

138 

139 source_mapping = { 

140 'describe': DescribeFlow, 

141 'config': query.ConfigSource 

142 } 

143 

144 

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

146class FlowLogv2Filter(ListItemFilter): 

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

148 

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

150 on the flow log attibutes, it also maintains compatiblity 

151 with the legacy flow-log filter. 

152 

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

154 

155 :example: 

156 

157 .. code-block:: yaml 

158 

159 policies: 

160 - name: flow-logs-enabled 

161 resource: vpc 

162 filters: 

163 - flow-logs 

164 

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

166 particular configuration. 

167 

168 :example: 

169 

170 .. code-block:: yaml 

171 

172 policies: 

173 - name: flow-mis-configured 

174 resource: vpc 

175 filters: 

176 - not: 

177 - type: flow-logs 

178 attrs: 

179 - TrafficType: ALL 

180 - FlowLogStatus: ACTIVE 

181 - LogGroupName: vpc-logs 

182 """ 

183 

184 legacy_schema = { 

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

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

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

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

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

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

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

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

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

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

195 } 

196 

197 schema = type_schema( 

198 'flow-logs', 

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

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

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

202 **legacy_schema 

203 ) 

204 schema_alias = True 

205 annotate_items = True 

206 permissions = ('ec2:DescribeFlowLogs',) 

207 

208 compat_conversion = { 

209 'status': { 

210 'key': 'FlowLogStatus', 

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

212 }, 

213 'deliver-status': { 

214 'key': 'DeliverLogsStatus', 

215 'values': {'success': 'SUCCESS', 

216 'failure': 'FAILED'} 

217 }, 

218 'destination': { 

219 'key': 'LogDestination', 

220 }, 

221 'destination-type': { 

222 'key': 'LogDestinationType', 

223 # values ? 

224 }, 

225 'traffic-type': { 

226 'key': 'TrafficType', 

227 'values': {'all': 'ALL', 

228 'reject': 'REJECT', 

229 'accept': 'ACCEPT'}, 

230 }, 

231 'log-format': { 

232 'key': 'LogFormat', 

233 }, 

234 'log-group': { 

235 'key': 'LogGroupName' 

236 } 

237 } 

238 

239 flow_log_map = None 

240 

241 def get_deprecations(self): 

242 filter_name = self.data["type"] 

243 return [ 

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

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

246 ] 

247 

248 def validate(self): 

249 keys = set(self.data) 

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

251 raise PolicyValidationError( 

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

253 return super().validate() 

254 

255 def convert(self): 

256 self.source_data = {} 

257 # no mixing of legacy and list-item style 

258 if 'attrs' in self.data: 

259 return 

260 data = {} 

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

262 data['count_op'] = 'gte' 

263 data['count'] = 1 

264 else: 

265 data['count'] = 0 

266 attrs = [] 

267 for k in self.compat_conversion: 

268 if k not in self.data: 

269 continue 

270 afilter = {} 

271 cinfo = self.compat_conversion[k] 

272 ak = cinfo['key'] 

273 av = self.data[k] 

274 if 'values' in cinfo: 

275 av = cinfo['values'][av] 

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

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

278 afilter[ak] = av 

279 attrs.append(afilter) 

280 if attrs: 

281 data['attrs'] = attrs 

282 data['type'] = self.type 

283 self.source_data = self.data 

284 self.data = data 

285 

286 def get_item_values(self, resource): 

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

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

289 # for matched flow logs 

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

291 

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

293 # some runtime modification to ensure compatiblity. 

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

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

296 return flogs 

297 

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

299 self.convert() 

300 self.flow_log_map = {} 

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

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

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

304 

305 

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

307class VpcSecurityGroupFilter(RelatedResourceFilter): 

308 """Filter VPCs based on Security Group attributes 

309 

310 :example: 

311 

312 .. code-block:: yaml 

313 

314 policies: 

315 - name: vpc-by-sg 

316 resource: vpc 

317 filters: 

318 - type: security-group 

319 key: tag:Color 

320 value: Gray 

321 """ 

322 schema = type_schema( 

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

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

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

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

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

328 AnnotationKey = "matched-vpcs" 

329 

330 def get_related_ids(self, resources): 

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

332 vpc_group_ids = { 

333 g['GroupId'] for g in 

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

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

336 } 

337 return vpc_group_ids 

338 

339 

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

341class VpcSubnetFilter(RelatedResourceFilter): 

342 """Filter VPCs based on Subnet attributes 

343 

344 :example: 

345 

346 .. code-block:: yaml 

347 

348 policies: 

349 - name: vpc-by-subnet 

350 resource: vpc 

351 filters: 

352 - type: subnet 

353 key: tag:Color 

354 value: Gray 

355 """ 

356 schema = type_schema( 

357 'subnet', rinherit=ValueFilter.schema, 

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

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

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

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

362 AnnotationKey = "MatchedVpcsSubnets" 

363 

364 def get_related_ids(self, resources): 

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

366 vpc_subnet_ids = { 

367 g['SubnetId'] for g in 

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

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

370 } 

371 return vpc_subnet_ids 

372 

373 

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

375class VpcNatGatewayFilter(RelatedResourceFilter): 

376 """Filter VPCs based on NAT Gateway attributes 

377 

378 :example: 

379 

380 .. code-block:: yaml 

381 

382 policies: 

383 - name: vpc-by-nat 

384 resource: vpc 

385 filters: 

386 - type: nat-gateway 

387 key: tag:Color 

388 value: Gray 

389 """ 

390 schema = type_schema( 

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

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

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

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

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

396 AnnotationKey = "MatchedVpcsNatGateways" 

397 

398 def get_related_ids(self, resources): 

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

400 vpc_natgw_ids = { 

401 g['NatGatewayId'] for g in 

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

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

404 } 

405 return vpc_natgw_ids 

406 

407 

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

409class VpcInternetGatewayFilter(RelatedResourceFilter): 

410 """Filter VPCs based on Internet Gateway attributes 

411 

412 :example: 

413 

414 .. code-block:: yaml 

415 

416 policies: 

417 - name: vpc-by-igw 

418 resource: vpc 

419 filters: 

420 - type: internet-gateway 

421 key: tag:Color 

422 value: Gray 

423 """ 

424 schema = type_schema( 

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

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

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

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

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

430 AnnotationKey = "MatchedVpcsIgws" 

431 

432 def get_related_ids(self, resources): 

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

434 vpc_igw_ids = set() 

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

436 for attachment in igw['Attachments']: 

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

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

439 return vpc_igw_ids 

440 

441 

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

443class AttributesFilter(Filter): 

444 """Filters VPCs based on their DNS attributes 

445 

446 :example: 

447 

448 .. code-block:: yaml 

449 

450 policies: 

451 - name: dns-hostname-enabled 

452 resource: vpc 

453 filters: 

454 - type: vpc-attributes 

455 dnshostnames: True 

456 """ 

457 schema = type_schema( 

458 'vpc-attributes', 

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

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

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

462 

463 permissions = ('ec2:DescribeVpcAttribute',) 

464 

465 key_params = ( 

466 ('dnshostnames', 'enableDnsHostnames'), 

467 ('dnssupport', 'enableDnsSupport'), 

468 ('addressusage', 'enableNetworkAddressUsageMetrics') 

469 ) 

470 annotation_key = 'c7n:attributes' 

471 

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

473 results = [] 

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

475 

476 for r in resources: 

477 found = True 

478 for policy_key, vpc_attr in self.key_params: 

479 if policy_key not in self.data: 

480 continue 

481 policy_value = self.data[policy_key] 

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

483 value = client.describe_vpc_attribute( 

484 VpcId=r['VpcId'], 

485 Attribute=vpc_attr 

486 ) 

487 value = value[response_attr]['Value'] 

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

489 if policy_value != value: 

490 found = False 

491 break 

492 if found: 

493 results.append(r) 

494 return results 

495 

496 

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

498class DhcpOptionsFilter(Filter): 

499 """Filter VPCs based on their dhcp options 

500 

501 :example: 

502 

503 .. code-block:: yaml 

504 

505 policies: 

506 - name: vpcs-in-domain 

507 resource: vpc 

508 filters: 

509 - type: dhcp-options 

510 domain-name: ec2.internal 

511 

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

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

514 

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

516 a `present: false` parameter. 

517 

518 """ 

519 

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

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

522 k: {'oneOf': [ 

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

524 {'type': 'string'}]} 

525 for k in option_keys}) 

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

527 permissions = ('ec2:DescribeDhcpOptions',) 

528 

529 def validate(self): 

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

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

532 return self 

533 

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

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

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

537 options_map = {} 

538 results = [] 

539 for options in client.describe_dhcp_options( 

540 Filters=[{ 

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

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

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

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

545 for o in options['DhcpConfigurations']} 

546 

547 for vpc in resources: 

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

549 results.append(vpc) 

550 return results 

551 

552 def process_vpc(self, vpc, dhcp): 

553 vpc['c7n:DhcpConfiguration'] = dhcp 

554 found = True 

555 for k in self.option_keys: 

556 if k not in self.data: 

557 continue 

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

559 if k not in dhcp: 

560 found = False 

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

562 found = False 

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

564 found = False 

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

566 found = not found 

567 return found 

568 

569 

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

571class VpcPostFinding(PostFinding): 

572 

573 resource_type = "AwsEc2Vpc" 

574 

575 def format_resource(self, r): 

576 envelope, payload = self.format_envelope(r) 

577 # more inane sechub formatting deltas 

578 detail = { 

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

580 'State': r['State']} 

581 

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

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

584 AssociationId=assoc['AssociationId'], 

585 CidrBlock=assoc['CidrBlock'], 

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

587 

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

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

590 AssociationId=assoc['AssociationId'], 

591 Ipv6CidrBlock=assoc['Ipv6CidrBlock'], 

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

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

594 return envelope 

595 

596 

597class DescribeSubnets(query.DescribeSource): 

598 

599 def get_resources(self, resource_ids): 

600 while resource_ids: 

601 try: 

602 return super().get_resources(resource_ids) 

603 except ClientError as e: 

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

605 raise 

606 sid = extract_subnet_id(e) 

607 if sid: 

608 resource_ids.remove(sid) 

609 else: 

610 return [] 

611 

612 

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

614 

615 

616def extract_subnet_id(state_error): 

617 "Extract an subnet id from an error" 

618 subnet_id = None 

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

620 if match: 

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

622 return subnet_id 

623 

624 

625@resources.register('subnet') 

626class Subnet(query.QueryResourceManager): 

627 

628 class resource_type(query.TypeInfo): 

629 service = 'ec2' 

630 arn_type = 'subnet' 

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

632 name = id = 'SubnetId' 

633 filter_name = 'SubnetIds' 

634 filter_type = 'list' 

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

636 id_prefix = "subnet-" 

637 

638 source_mapping = { 

639 'describe': DescribeSubnets, 

640 'config': query.ConfigSource} 

641 

642 

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

644 

645 

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

647class SubnetVpcFilter(net_filters.VpcFilter): 

648 

649 RelatedIdsExpression = "VpcId" 

650 

651 

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

653class SubnetIpAddressUsageFilter(ValueFilter): 

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

655 

656 :example: 

657 

658 Show subnets with no addresses in use. 

659 

660 .. code-block:: yaml 

661 

662 policies: 

663 - name: empty-subnets 

664 resource: aws.subnet 

665 filters: 

666 - type: ip-address-usage 

667 key: NumberUsed 

668 value: 0 

669 

670 :example: 

671 

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

673 

674 .. code-block:: yaml 

675 

676 policies: 

677 - name: almost-full-subnets 

678 resource: aws.subnet 

679 filters: 

680 - type: ip-address-usage 

681 key: PercentUsed 

682 op: greater-than 

683 value: 90 

684 

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

686 

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

688 (minus the 5 addresses 

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

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

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

692 """ 

693 annotation_key = 'c7n:IpAddressUsage' 

694 aws_reserved_addresses = 5 

695 schema_alias = False 

696 schema = type_schema( 

697 'ip-address-usage', 

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

699 rinherit=ValueFilter.schema, 

700 ) 

701 

702 def augment(self, resource): 

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

704 max_addresses = cidr_block.num_addresses - self.aws_reserved_addresses 

705 resource[self.annotation_key] = dict( 

706 MaxAvailable=max_addresses, 

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

708 PercentUsed=round( 

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

710 2 

711 ), 

712 ) 

713 

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

715 results = [] 

716 for r in resources: 

717 if self.annotation_key not in r: 

718 self.augment(r) 

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

720 results.append(r) 

721 return results 

722 

723class ConfigSG(query.ConfigSource): 

724 

725 def load_resource(self, item): 

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

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

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

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

730 p.pop('FromPort') 

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

732 p.pop('ToPort') 

733 if 'Ipv6Ranges' not in p: 

734 p[u'Ipv6Ranges'] = [] 

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

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

737 if v is None: 

738 i.pop(k) 

739 # legacy config form, still version 1.2 

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

741 if attribute not in p: 

742 continue 

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

744 if 'Ipv4Ranges' in p: 

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

746 return r 

747 

748 

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

750class SecurityGroup(query.QueryResourceManager): 

751 

752 class resource_type(query.TypeInfo): 

753 service = 'ec2' 

754 arn_type = 'security-group' 

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

756 id = 'GroupId' 

757 name = 'GroupName' 

758 filter_name = "GroupIds" 

759 filter_type = 'list' 

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

761 id_prefix = "sg-" 

762 

763 source_mapping = { 

764 'config': ConfigSG, 

765 'describe': query.DescribeSource 

766 } 

767 

768 

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

770class SecurityGroupDiffFilter(Diff): 

771 

772 def diff(self, source, target): 

773 differ = SecurityGroupDiff() 

774 return differ.diff(source, target) 

775 

776 

777class SecurityGroupDiff: 

778 """Diff two versions of a security group 

779 

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

781 Mutable: Tags, Rules 

782 """ 

783 

784 def diff(self, source, target): 

785 delta = {} 

786 tag_delta = self.get_tag_delta(source, target) 

787 if tag_delta: 

788 delta['tags'] = tag_delta 

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

790 if ingress_delta: 

791 delta['ingress'] = ingress_delta 

792 egress_delta = self.get_rule_delta( 

793 'IpPermissionsEgress', source, target) 

794 if egress_delta: 

795 delta['egress'] = egress_delta 

796 if delta: 

797 return delta 

798 

799 def get_tag_delta(self, source, target): 

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

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

802 target_keys = set(target_tags.keys()) 

803 source_keys = set(source_tags.keys()) 

804 removed = source_keys.difference(target_keys) 

805 added = target_keys.difference(source_keys) 

806 changed = set() 

807 for k in target_keys.intersection(source_keys): 

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

809 changed.add(k) 

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

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

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

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

814 

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

816 source_rules = { 

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

818 target_rules = { 

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

820 source_keys = set(source_rules.keys()) 

821 target_keys = set(target_rules.keys()) 

822 removed = source_keys.difference(target_keys) 

823 added = target_keys.difference(source_keys) 

824 return {k: v for k, v in 

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

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

827 

828 RULE_ATTRS = ( 

829 ('PrefixListIds', 'PrefixListId'), 

830 ('UserIdGroupPairs', 'GroupId'), 

831 ('IpRanges', 'CidrIp'), 

832 ('Ipv6Ranges', 'CidrIpv6') 

833 ) 

834 

835 def compute_rule_hash(self, rule): 

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

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

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

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

840 ) 

841 for a, ke in self.RULE_ATTRS: 

842 if a not in rule: 

843 continue 

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

845 ev.sort() 

846 for e in ev: 

847 buf += "%s-" % e 

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

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

850 

851 

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

853class SecurityGroupApplyPatch(BaseAction): 

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

855 """ 

856 schema = type_schema('patch') 

857 

858 permissions = ('ec2:AuthorizeSecurityGroupIngress', 

859 'ec2:AuthorizeSecurityGroupEgress', 

860 'ec2:RevokeSecurityGroupIngress', 

861 'ec2:RevokeSecurityGroupEgress', 

862 'ec2:CreateTags', 

863 'ec2:DeleteTags') 

864 

865 def validate(self): 

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

867 n, SecurityGroupDiffFilter)] 

868 if not len(diff_filters): 

869 raise PolicyValidationError( 

870 "resource patching requires diff filter") 

871 return self 

872 

873 def process(self, resources): 

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

875 differ = SecurityGroupDiff() 

876 patcher = SecurityGroupPatch() 

877 for r in resources: 

878 # reverse the patch by computing fresh, the forward 

879 # patch is for notifications 

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

881 patcher.apply_delta(client, r, d) 

882 

883 

884class SecurityGroupPatch: 

885 

886 RULE_TYPE_MAP = { 

887 'egress': ('IpPermissionsEgress', 

888 'revoke_security_group_egress', 

889 'authorize_security_group_egress'), 

890 'ingress': ('IpPermissions', 

891 'revoke_security_group_ingress', 

892 'authorize_security_group_ingress')} 

893 

894 retry = staticmethod(get_retry(( 

895 'RequestLimitExceeded', 'Client.RequestLimitExceeded'))) 

896 

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

898 if 'tags' in change_set: 

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

900 if 'ingress' in change_set: 

901 self.process_rules( 

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

903 if 'egress' in change_set: 

904 self.process_rules( 

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

906 

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

908 if 'removed' in tag_delta: 

909 self.retry(client.delete_tags, 

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

911 Tags=[{'Key': k} 

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

913 tags = [] 

914 if 'added' in tag_delta: 

915 tags.extend( 

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

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

918 if 'updated' in tag_delta: 

919 tags.extend( 

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

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

922 if tags: 

923 self.retry( 

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

925 

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

927 key, revoke_op, auth_op = self.RULE_TYPE_MAP[rule_type] 

928 revoke, authorize = getattr( 

929 client, revoke_op), getattr(client, auth_op) 

930 

931 # Process removes 

932 if 'removed' in delta: 

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

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

935 

936 # Process adds 

937 if 'added' in delta: 

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

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

940 

941 

942class SGUsage(Filter): 

943 

944 nics = () 

945 

946 def get_permissions(self): 

947 return list(itertools.chain( 

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

949 for m in 

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

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

952 

953 def filter_peered_refs(self, resources): 

954 if not resources: 

955 return resources 

956 # Check that groups are not referenced across accounts 

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

958 peered_ids = set() 

959 for resource_set in chunks(resources, 200): 

960 for sg_ref in client.describe_security_group_references( 

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

962 )['SecurityGroupReferenceSet']: 

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

964 self.log.debug( 

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

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

967 

968 def get_scanners(self): 

969 return ( 

970 ("nics", self.get_eni_sgs), 

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

972 ('lambdas', self.get_lambda_sgs), 

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

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

975 ("codebuild", self.get_codebuild_sgs), 

976 ("batch", self.get_batch_sgs), 

977 ) 

978 

979 def scan_groups(self): 

980 used = set() 

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

982 sg_ids = scanner() 

983 new_refs = sg_ids.difference(used) 

984 used = used.union(sg_ids) 

985 self.log.debug( 

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

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

988 

989 return used 

990 

991 def get_launch_config_sgs(self): 

992 # Note assuming we also have launch config garbage collection 

993 # enabled. 

994 sg_ids = set() 

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

996 for g in cfg['SecurityGroups']: 

997 sg_ids.add(g) 

998 for g in cfg['ClassicLinkVPCSecurityGroups']: 

999 sg_ids.add(g) 

1000 return sg_ids 

1001 

1002 def get_lambda_sgs(self): 

1003 sg_ids = set() 

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

1005 if 'VpcConfig' not in func: 

1006 continue 

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

1008 sg_ids.add(g) 

1009 return sg_ids 

1010 

1011 def get_eni_sgs(self): 

1012 sg_ids = set() 

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

1014 for nic in self.nics: 

1015 for g in nic['Groups']: 

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

1017 return sg_ids 

1018 

1019 def get_codebuild_sgs(self): 

1020 sg_ids = set() 

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

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

1023 return sg_ids 

1024 

1025 def get_sg_refs(self): 

1026 sg_ids = set() 

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

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

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

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

1031 # self references aren't usage. 

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

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

1034 return sg_ids 

1035 

1036 def get_ecs_cwe_sgs(self): 

1037 sg_ids = set() 

1038 expr = jmespath_compile( 

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

1040 for rule in self.manager.get_resource_manager( 

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

1042 ids = expr.search(rule) 

1043 if ids: 

1044 sg_ids.update(ids) 

1045 return sg_ids 

1046 

1047 def get_batch_sgs(self): 

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

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

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

1051 

1052 

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

1054class UnusedSecurityGroup(SGUsage): 

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

1056 

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

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

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

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

1061 other security group either within the vpc or across peered 

1062 connections. Also checks cloud watch event targeting ecs. 

1063 

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

1065 targets. 

1066 

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

1068 

1069 :example: 

1070 

1071 .. code-block:: yaml 

1072 

1073 policies: 

1074 - name: security-groups-unused 

1075 resource: security-group 

1076 filters: 

1077 - unused 

1078 

1079 """ 

1080 schema = type_schema('unused') 

1081 

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

1083 used = self.scan_groups() 

1084 unused = [ 

1085 r for r in resources 

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

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

1088 

1089 

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

1091class UsedSecurityGroup(SGUsage): 

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

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

1094 workflows. 

1095 

1096 :example: 

1097 

1098 .. code-block:: yaml 

1099 

1100 policies: 

1101 - name: security-groups-in-use 

1102 resource: security-group 

1103 filters: 

1104 - used 

1105 

1106 policies: 

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

1108 resource: security-group 

1109 filters: 

1110 - used 

1111 - type: value 

1112 key: c7n:InstanceOwnerIds 

1113 op: intersect 

1114 value: 

1115 - amazon-rds 

1116 

1117 policies: 

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

1119 resource: security-group 

1120 filters: 

1121 - used 

1122 - type: value 

1123 key: c7n:InterfaceTypes 

1124 op: intersect 

1125 value: 

1126 - nat_gateway 

1127 

1128 policies: 

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

1130 resource: security-group 

1131 filters: 

1132 - used 

1133 - type: value 

1134 key: c7n:InterfaceResourceTypes 

1135 op: intersect 

1136 value: 

1137 - elb-app 

1138 """ 

1139 schema = type_schema('used') 

1140 

1141 instance_owner_id_key = 'c7n:InstanceOwnerIds' 

1142 interface_type_key = 'c7n:InterfaceTypes' 

1143 interface_resource_type_key = 'c7n:InterfaceResourceTypes' 

1144 

1145 def _get_eni_attributes(self): 

1146 group_enis = {} 

1147 for nic in self.nics: 

1148 instance_owner_id, interface_resource_type = '', '' 

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

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

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

1152 interface_resource_type = get_eni_resource_type(nic) 

1153 interface_type = nic.get('InterfaceType') 

1154 for g in nic['Groups']: 

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

1156 'InstanceOwnerId': instance_owner_id, 

1157 'InterfaceType': interface_type, 

1158 'InterfaceResourceType': interface_resource_type 

1159 }) 

1160 return group_enis 

1161 

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

1163 used = self.scan_groups() 

1164 unused = [ 

1165 r for r in resources 

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

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

1168 group_enis = self._get_eni_attributes() 

1169 for r in resources: 

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

1171 r[self.instance_owner_id_key] = list({ 

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

1173 r[self.interface_type_key] = list({ 

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

1175 r[self.interface_resource_type_key] = list({ 

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

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

1178 

1179 

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

1181class Stale(Filter): 

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

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

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

1185 Security groups only and will implicitly filter security groups. 

1186 

1187 AWS Docs: 

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

1189 

1190 :example: 

1191 

1192 .. code-block:: yaml 

1193 

1194 policies: 

1195 - name: stale-security-groups 

1196 resource: security-group 

1197 filters: 

1198 - stale 

1199 """ 

1200 schema = type_schema('stale') 

1201 permissions = ('ec2:DescribeStaleSecurityGroups',) 

1202 

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

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

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

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

1207 results = [] 

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

1209 stale_count = 0 

1210 for vpc_id in vpc_ids: 

1211 stale_groups = client.describe_stale_security_groups( 

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

1213 

1214 stale_count += len(stale_groups) 

1215 for s in stale_groups: 

1216 if s['GroupId'] in group_map: 

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

1218 if 'StaleIpPermissions' in s: 

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

1220 if 'StaleIpPermissionsEgress' in s: 

1221 r['MatchedIpPermissionsEgress'] = s[ 

1222 'StaleIpPermissionsEgress'] 

1223 results.append(r) 

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

1225 return results 

1226 

1227 

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

1229class SGDefaultVpc(net_filters.DefaultVpcBase): 

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

1231 

1232 :example: 

1233 

1234 .. code-block:: yaml 

1235 

1236 policies: 

1237 - name: security-group-default-vpc 

1238 resource: security-group 

1239 filters: 

1240 - default-vpc 

1241 """ 

1242 

1243 schema = type_schema('default-vpc') 

1244 

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

1246 if 'VpcId' not in resource: 

1247 return False 

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

1249 

1250 

1251class SGPermission(Filter): 

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

1253 

1254 All attributes of a security group permission are available as 

1255 value filters. 

1256 

1257 If multiple attributes are specified the permission must satisfy 

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

1259 of a permission we default to or. 

1260 

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

1262 matches the filter. 

1263 

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

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

1266 

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

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

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

1270 

1271 .. code-block:: yaml 

1272 

1273 - type: ingress 

1274 Ports: [22, 443, 80] 

1275 

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

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

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

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

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

1281 

1282 .. code-block:: yaml 

1283 

1284 - type: ingress 

1285 OnlyPorts: [22] 

1286 

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

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

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

1290 matches. 

1291 

1292 .. code-block:: yaml 

1293 

1294 - type: ingress 

1295 IpProtocol: -1 

1296 FromPort: 445 

1297 

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

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

1300 rules which allow traffic its own same security group. 

1301 

1302 .. code-block:: yaml 

1303 

1304 - type: ingress 

1305 SelfReference: True 

1306 

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

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

1309 

1310 .. code-block:: yaml 

1311 

1312 - type: egress 

1313 OnlyPorts: [22, 443, 80] 

1314 

1315 - type: egress 

1316 Cidr: 

1317 value_type: cidr 

1318 op: in 

1319 value: x.y.z 

1320 

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

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

1323 allowed_cidrs.csv. 

1324 

1325 .. code-block:: yaml 

1326 

1327 - type: ingress 

1328 Cidr: 

1329 value_type: cidr 

1330 op: not-in 

1331 value_from: 

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

1333 format: csv 

1334 

1335 or value can be specified as a list. 

1336 

1337 .. code-block:: yaml 

1338 

1339 - type: ingress 

1340 Cidr: 

1341 value_type: cidr 

1342 op: not-in 

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

1344 

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

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

1347 RDP. 

1348 

1349 .. code-block:: yaml 

1350 

1351 - or: 

1352 - type: ingress 

1353 Ports: [22, 3389] 

1354 Cidr: 

1355 value: "0.0.0.0/0" 

1356 - type: ingress 

1357 Ports: [22, 3389] 

1358 CidrV6: 

1359 value: "::/0" 

1360 

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

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

1363 that is tagged with `Access: Public`. 

1364 

1365 .. code-block:: yaml 

1366 

1367 - type: ingress 

1368 SGReferences: 

1369 key: "tag:Access" 

1370 value: "Public" 

1371 op: equal 

1372 

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

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

1375 that reference SGs are only referencing security groups within a 

1376 specified VPC. 

1377 

1378 .. code-block:: yaml 

1379 

1380 - type: egress 

1381 SGReferences: 

1382 key: 'VpcId' 

1383 value: 'vpc-11a1a1aa' 

1384 op: equal 

1385 

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

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

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

1389 

1390 .. code-block:: yaml 

1391 

1392 - type: egress 

1393 SGReferences: 

1394 key: 'Description' 

1395 value: 'default - DO NOT USE' 

1396 op: equal 

1397 

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

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

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

1401 cases that would otherwise require multiple filters. To find 

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

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

1404 

1405 .. code-block:: yaml 

1406 

1407 - or: 

1408 - type: ingress 

1409 Cidr: "0.0.0.0/0" 

1410 - type: ingress 

1411 CidrV6: "::/0" 

1412 

1413 or combine them into a single filter: 

1414 

1415 .. code-block:: yaml 

1416 

1417 - type: ingress 

1418 match-operator: or 

1419 Cidr: "0.0.0.0/0" 

1420 CidrV6: "::/0" 

1421 

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

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

1424 """ 

1425 

1426 perm_attrs = { 

1427 'IpProtocol', 'FromPort', 'ToPort', 'UserIdGroupPairs', 

1428 'IpRanges', 'PrefixListIds'} 

1429 filter_attrs = { 

1430 'Cidr', 'CidrV6', 'Ports', 'OnlyPorts', 

1431 'SelfReference', 'Description', 'SGReferences'} 

1432 attrs = perm_attrs.union(filter_attrs) 

1433 attrs.add('match-operator') 

1434 attrs.add('match-operator') 

1435 

1436 def validate(self): 

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

1438 delta.remove('type') 

1439 if delta: 

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

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

1442 return self 

1443 

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

1445 self.vfilters = [] 

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

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

1448 self.only_ports = ( 

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

1450 for f in fattrs: 

1451 fv = self.data.get(f) 

1452 if isinstance(fv, dict): 

1453 fv['key'] = f 

1454 else: 

1455 fv = {f: fv} 

1456 vf = ValueFilter(fv, self.manager) 

1457 vf.annotate = False 

1458 self.vfilters.append(vf) 

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

1460 

1461 def process_ports(self, perm): 

1462 found = None 

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

1464 for port in self.ports: 

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

1466 found = True 

1467 break 

1468 found = False 

1469 only_found = False 

1470 for port in self.only_ports: 

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

1472 only_found = True 

1473 if self.only_ports and not only_found: 

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

1475 if self.only_ports and only_found: 

1476 found = False 

1477 return found 

1478 

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

1480 

1481 found = None 

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

1483 if not ip_perms: 

1484 return False 

1485 

1486 match_range = self.data[cidr_key] 

1487 

1488 if isinstance(match_range, dict): 

1489 match_range['key'] = cidr_type 

1490 else: 

1491 match_range = {cidr_type: match_range} 

1492 

1493 vf = ValueFilter(match_range, self.manager) 

1494 vf.annotate = False 

1495 

1496 for ip_range in ip_perms: 

1497 found = vf(ip_range) 

1498 if found: 

1499 break 

1500 else: 

1501 found = False 

1502 return found 

1503 

1504 def process_cidrs(self, perm): 

1505 found_v6 = found_v4 = None 

1506 if 'CidrV6' in self.data: 

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

1508 if 'Cidr' in self.data: 

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

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

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

1512 if not cidr_match: 

1513 return None 

1514 return match_op(cidr_match) 

1515 

1516 def process_description(self, perm): 

1517 if 'Description' not in self.data: 

1518 return None 

1519 

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

1521 d['key'] = 'Description' 

1522 

1523 vf = ValueFilter(d, self.manager) 

1524 vf.annotate = False 

1525 

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

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

1528 continue 

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

1530 return False 

1531 

1532 def process_self_reference(self, perm, sg_id): 

1533 found = None 

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

1535 if ref_match is not None: 

1536 found = False 

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

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

1539 for p in perm['UserIdGroupPairs']] 

1540 if ref_match is False and not self_reference: 

1541 found = True 

1542 if ref_match is True and self_reference: 

1543 found = True 

1544 return found 

1545 

1546 def process_sg_references(self, perm, owner_id): 

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

1548 if not sg_refs: 

1549 return None 

1550 

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

1552 if not sg_perm: 

1553 return False 

1554 

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

1556 sg_resources = self.manager.get_resources(sg_group_ids) 

1557 vf = ValueFilter(sg_refs, self.manager) 

1558 vf.annotate = False 

1559 

1560 for sg in sg_resources: 

1561 if vf(sg): 

1562 return True 

1563 return False 

1564 

1565 def expand_permissions(self, permissions): 

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

1567 by port/protocol as an individual rule. 

1568 

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

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

1571 """ 

1572 for p in permissions: 

1573 np = dict(p) 

1574 values = {} 

1575 for k in (u'IpRanges', 

1576 u'Ipv6Ranges', 

1577 u'PrefixListIds', 

1578 u'UserIdGroupPairs'): 

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

1580 np[k] = [] 

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

1582 if not v: 

1583 continue 

1584 for e in v: 

1585 ep = dict(np) 

1586 ep[k] = [e] 

1587 yield ep 

1588 

1589 def __call__(self, resource): 

1590 matched = [] 

1591 sg_id = resource['GroupId'] 

1592 owner_id = resource['OwnerId'] 

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

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

1595 perm_matches = {} 

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

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

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

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

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

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

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

1603 perm_match_values = list(filter( 

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

1605 

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

1607 if match_op == all and not perm_match_values: 

1608 continue 

1609 

1610 match = match_op(perm_match_values) 

1611 if match: 

1612 matched.append(perm) 

1613 

1614 if matched: 

1615 resource.setdefault('Matched%s' % self.ip_permissions_key, []).extend(matched) 

1616 return True 

1617 

1618 

1619SGPermissionSchema = { 

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

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

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

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

1624 'IpProtocol': { 

1625 'oneOf': [ 

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

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

1628 ] 

1629 }, 

1630 'FromPort': {'oneOf': [ 

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

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

1633 'ToPort': {'oneOf': [ 

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

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

1636 'UserIdGroupPairs': {}, 

1637 'IpRanges': {}, 

1638 'PrefixListIds': {}, 

1639 'Description': {}, 

1640 'Cidr': {}, 

1641 'CidrV6': {}, 

1642 'SGReferences': {} 

1643} 

1644 

1645 

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

1647class IPPermission(SGPermission): 

1648 

1649 ip_permissions_key = "IpPermissions" 

1650 schema = { 

1651 'type': 'object', 

1652 'additionalProperties': False, 

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

1654 'required': ['type']} 

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

1656 

1657 

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

1659class IPPermissionEgress(SGPermission): 

1660 

1661 ip_permissions_key = "IpPermissionsEgress" 

1662 schema = { 

1663 'type': 'object', 

1664 'additionalProperties': False, 

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

1666 'required': ['type']} 

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

1668 

1669 

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

1671class Delete(BaseAction): 

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

1673 

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

1675 deletion of all security groups returned. 

1676 

1677 :example: 

1678 

1679 .. code-block:: yaml 

1680 

1681 policies: 

1682 - name: security-groups-unused-delete 

1683 resource: security-group 

1684 filters: 

1685 - type: unused 

1686 actions: 

1687 - delete 

1688 """ 

1689 

1690 schema = type_schema('delete') 

1691 permissions = ('ec2:DeleteSecurityGroup',) 

1692 

1693 def process(self, resources): 

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

1695 for r in resources: 

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

1697 

1698 

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

1700class RemovePermissions(BaseAction): 

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

1702 

1703 :example: 

1704 

1705 .. code-block:: yaml 

1706 

1707 policies: 

1708 - name: security-group-revoke-8080 

1709 resource: security-group 

1710 filters: 

1711 - type: ingress 

1712 IpProtocol: tcp 

1713 Ports: [8080] 

1714 actions: 

1715 - type: remove-permissions 

1716 ingress: matched 

1717 

1718 """ 

1719 schema = type_schema( 

1720 'remove-permissions', 

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

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

1723 

1724 permissions = ('ec2:RevokeSecurityGroupIngress', 

1725 'ec2:RevokeSecurityGroupEgress') 

1726 

1727 def process(self, resources): 

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

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

1730 

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

1732 for r in resources: 

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

1734 if perms == 'matched': 

1735 key = 'MatchedIpPermissions%s' % ( 

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

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

1738 elif perms == 'all': 

1739 key = 'IpPermissions%s' % ( 

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

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

1742 elif isinstance(perms, list): 

1743 groups = perms 

1744 else: 

1745 continue 

1746 if not groups: 

1747 continue 

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

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

1750 

1751 

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

1753class SetPermissions(BaseAction): 

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

1755 

1756 :example: 

1757 

1758 .. code-block:: yaml 

1759 

1760 policies: 

1761 - name: ops-access-via 

1762 resource: aws.security-group 

1763 filters: 

1764 - type: ingress 

1765 IpProtocol: "-1" 

1766 Ports: [22, 3389] 

1767 Cidr: "0.0.0.0/0" 

1768 actions: 

1769 - type: set-permissions 

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

1771 remove-ingress: matched 

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

1773 # access. 

1774 remove-egress: 

1775 - IpProtocol: "-1" 

1776 Cidr: "0.0.0.0/0" 

1777 

1778 # add a list of permissions to the group. 

1779 add-ingress: 

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

1781 - IpPermissions: 

1782 - IpProtocol: TCP 

1783 FromPort: 22 

1784 ToPort: 22 

1785 IpRanges: 

1786 - Description: Ops SSH Access 

1787 CidrIp: "1.1.1.1/32" 

1788 - Description: Security SSH Access 

1789 CidrIp: "2.2.2.2/32" 

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

1791 add-egress: 

1792 - IpProtocol: "TCP" 

1793 FromPort: 5044 

1794 ToPort: 5044 

1795 CidrIp: "192.168.1.2/32" 

1796 

1797 """ 

1798 schema = type_schema( 

1799 'set-permissions', 

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

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

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

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

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

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

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

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

1808 ) 

1809 permissions = ( 

1810 'ec2:AuthorizeSecurityGroupEgress', 

1811 'ec2:AuthorizeSecurityGroupIngress',) 

1812 

1813 ingress_shape = "AuthorizeSecurityGroupIngressRequest" 

1814 egress_shape = "AuthorizeSecurityGroupEgressRequest" 

1815 

1816 def validate(self): 

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

1818 for perm_type, shape in ( 

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

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

1821 params = dict(request_template) 

1822 params.update(perm) 

1823 shape_validate(params, shape, 'ec2') 

1824 

1825 def get_permissions(self): 

1826 perms = () 

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

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

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

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

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

1832 perms += RemovePermissions.permissions 

1833 if not perms: 

1834 perms = self.permissions + RemovePermissions.permissions 

1835 return perms 

1836 

1837 def process(self, resources): 

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

1839 for r in resources: 

1840 for method, permissions in ( 

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

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

1843 for p in permissions: 

1844 p = dict(p) 

1845 p['GroupId'] = r['GroupId'] 

1846 try: 

1847 method(**p) 

1848 except ClientError as e: 

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

1850 raise 

1851 

1852 remover = RemovePermissions( 

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

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

1855 remover.process(resources) 

1856 

1857 

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

1859class SecurityGroupPostFinding(OtherResourcePostFinding): 

1860 

1861 def format_resource(self, r): 

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

1863 fr['Type'] = 'AwsEc2SecurityGroup' 

1864 return fr 

1865 

1866 

1867class DescribeENI(query.DescribeSource): 

1868 

1869 def augment(self, resources): 

1870 for r in resources: 

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

1872 return resources 

1873 

1874 

1875@resources.register('eni') 

1876class NetworkInterface(query.QueryResourceManager): 

1877 

1878 class resource_type(query.TypeInfo): 

1879 service = 'ec2' 

1880 arn_type = 'network-interface' 

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

1882 name = id = 'NetworkInterfaceId' 

1883 filter_name = 'NetworkInterfaceIds' 

1884 filter_type = 'list' 

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

1886 id_prefix = "eni-" 

1887 

1888 source_mapping = { 

1889 'describe': DescribeENI, 

1890 'config': query.ConfigSource 

1891 } 

1892 

1893 

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

1895NetworkInterface.filter_registry.register( 

1896 'network-location', net_filters.NetworkLocation) 

1897 

1898 

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

1900class InterfaceSubnetFilter(net_filters.SubnetFilter): 

1901 """Network interface subnet filter 

1902 

1903 :example: 

1904 

1905 .. code-block:: yaml 

1906 

1907 policies: 

1908 - name: network-interface-in-subnet 

1909 resource: eni 

1910 filters: 

1911 - type: subnet 

1912 key: CidrBlock 

1913 value: 10.0.2.0/24 

1914 """ 

1915 

1916 RelatedIdsExpression = "SubnetId" 

1917 

1918 

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

1920class InterfaceSecurityGroupFilter(net_filters.SecurityGroupFilter): 

1921 """Network interface security group filter 

1922 

1923 :example: 

1924 

1925 .. code-block:: yaml 

1926 

1927 policies: 

1928 - name: network-interface-ssh 

1929 resource: eni 

1930 filters: 

1931 - type: security-group 

1932 match-resource: true 

1933 key: FromPort 

1934 value: 22 

1935 """ 

1936 

1937 RelatedIdsExpression = "Groups[].GroupId" 

1938 

1939 

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

1941class InterfaceVpcFilter(net_filters.VpcFilter): 

1942 

1943 RelatedIdsExpression = "VpcId" 

1944 

1945 

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

1947class InterfaceModifyVpcSecurityGroups(ModifyVpcSecurityGroupsAction): 

1948 """Remove security groups from an interface. 

1949 

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

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

1952 the annotations of the 'group' interface filter. 

1953 

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

1955 we also allow specification of an isolation/quarantine group 

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

1957 

1958 

1959 :example: 

1960 

1961 .. code-block:: yaml 

1962 

1963 policies: 

1964 - name: network-interface-remove-group 

1965 resource: eni 

1966 filters: 

1967 - type: security-group 

1968 match-resource: true 

1969 key: FromPort 

1970 value: 22 

1971 actions: 

1972 - type: modify-security-groups 

1973 isolation-group: sg-01ab23c4 

1974 add: [] 

1975 """ 

1976 permissions = ('ec2:ModifyNetworkInterfaceAttribute',) 

1977 

1978 def process(self, resources): 

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

1980 groups = super( 

1981 InterfaceModifyVpcSecurityGroups, self).get_groups(resources) 

1982 for idx, r in enumerate(resources): 

1983 client.modify_network_interface_attribute( 

1984 NetworkInterfaceId=r['NetworkInterfaceId'], 

1985 Groups=groups[idx]) 

1986 

1987 

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

1989class DeleteNetworkInterface(BaseAction): 

1990 """Delete a network interface. 

1991 

1992 :example: 

1993 

1994 .. code-block:: yaml 

1995 

1996 policies: 

1997 - name: mark-orphaned-enis 

1998 comment: Flag abandoned Lambda VPC ENIs for deletion 

1999 resource: eni 

2000 filters: 

2001 - Status: available 

2002 - type: value 

2003 op: glob 

2004 key: Description 

2005 value: "AWS Lambda VPC ENI*" 

2006 - "tag:custodian_status": absent 

2007 actions: 

2008 - type: mark-for-op 

2009 tag: custodian_status 

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

2011 op: delete 

2012 days: 1 

2013 

2014 - name: delete-marked-enis 

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

2016 resource: eni 

2017 filters: 

2018 - type: marked-for-op 

2019 tag: custodian_status 

2020 op: delete 

2021 actions: 

2022 - type: delete 

2023 """ 

2024 permissions = ('ec2:DeleteNetworkInterface',) 

2025 schema = type_schema('delete') 

2026 

2027 def process(self, resources): 

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

2029 for r in resources: 

2030 try: 

2031 self.manager.retry( 

2032 client.delete_network_interface, 

2033 NetworkInterfaceId=r['NetworkInterfaceId']) 

2034 except ClientError as err: 

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

2036 raise 

2037 

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

2039class DetachNetworkInterface(BaseAction): 

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

2041 

2042 :example: 

2043 

2044 .. code-block:: yaml 

2045 

2046 policies: 

2047 - name: detach-enis 

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

2049 resource: eni 

2050 filters: 

2051 - type: value 

2052 key: Attachment.InstanceId 

2053 value: present 

2054 - type: value 

2055 key: Association.PublicIp 

2056 value: present 

2057 actions: 

2058 - type: detach 

2059 """ 

2060 permissions = ('ec2:DetachNetworkInterface',) 

2061 schema = type_schema('detach') 

2062 

2063 def process(self, resources): 

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

2065 att_resources = [ ar for ar in resources if ('Attachment' in ar \ 

2066 and ar['Attachment'].get('InstanceId') \ 

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

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

2069 self.log.warning( 

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

2071 len(att_resources), len(resources)) 

2072 ) 

2073 elif not att_resources: 

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

2075 for r in att_resources: 

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

2077 

2078 

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

2080class RouteTable(query.QueryResourceManager): 

2081 

2082 class resource_type(query.TypeInfo): 

2083 service = 'ec2' 

2084 arn_type = 'route-table' 

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

2086 name = id = 'RouteTableId' 

2087 filter_name = 'RouteTableIds' 

2088 filter_type = 'list' 

2089 id_prefix = "rtb-" 

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

2091 

2092 

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

2094class RouteTableVpcFilter(net_filters.VpcFilter): 

2095 

2096 RelatedIdsExpression = "VpcId" 

2097 

2098 

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

2100class SubnetRoute(net_filters.SubnetFilter): 

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

2102 

2103 RelatedIdsExpression = "Associations[].SubnetId" 

2104 

2105 RelatedMapping = None 

2106 

2107 def get_related_ids(self, resources): 

2108 if self.RelatedIdMapping is None: 

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

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

2111 

2112 def get_related(self, resources): 

2113 rt_subnet_map = {} 

2114 main_tables = {} 

2115 

2116 manager = self.get_resource_manager() 

2117 for r in resources: 

2118 rt_subnet_map[r['RouteTableId']] = [] 

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

2120 if 'SubnetId' in a: 

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

2122 elif a.get('Main'): 

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

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

2125 subnets = manager.resources() 

2126 for s in subnets: 

2127 if s['SubnetId'] in explicit_subnet_ids: 

2128 continue 

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

2130 continue 

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

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

2133 self.RelatedIdMapping = rt_subnet_map 

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

2135 

2136 

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

2138class Route(ValueFilter): 

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

2140 

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

2142 schema_alias = False 

2143 

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

2145 results = [] 

2146 for r in resources: 

2147 matched = [] 

2148 for route in r['Routes']: 

2149 if self.match(route): 

2150 matched.append(route) 

2151 if matched: 

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

2153 results.append(r) 

2154 return results 

2155 

2156 

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

2158class TransitGateway(query.QueryResourceManager): 

2159 

2160 class resource_type(query.TypeInfo): 

2161 service = 'ec2' 

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

2163 name = id = 'TransitGatewayId' 

2164 arn = "TransitGatewayArn" 

2165 id_prefix = "tgw-" 

2166 filter_name = 'TransitGatewayIds' 

2167 filter_type = 'list' 

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

2169 

2170 

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

2172 

2173 

2174class TransitGatewayAttachmentQuery(query.ChildResourceQuery): 

2175 

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

2177 merged_params = dict(params) 

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

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

2180 return merged_params 

2181 

2182 

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

2184class TransitAttachmentSource(query.ChildDescribeSource): 

2185 

2186 resource_query_factory = TransitGatewayAttachmentQuery 

2187 

2188 

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

2190class TransitGatewayAttachment(query.ChildResourceManager): 

2191 

2192 child_source = 'transit-attachment' 

2193 

2194 class resource_type(query.TypeInfo): 

2195 service = 'ec2' 

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

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

2198 id_prefix = 'tgw-attach-' 

2199 name = id = 'TransitGatewayAttachmentId' 

2200 metrics_namespace = 'AWS/TransitGateway' 

2201 arn = False 

2202 cfn_type = 'AWS::EC2::TransitGatewayAttachment' 

2203 supports_trailevents = True 

2204 

2205 

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

2207class TransitGatewayAttachmentMetricsFilter(MetricsFilter): 

2208 

2209 def get_dimensions(self, resource): 

2210 return [ 

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

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

2213 ] 

2214 

2215 

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

2217class PeeringConnection(query.QueryResourceManager): 

2218 

2219 class resource_type(query.TypeInfo): 

2220 service = 'ec2' 

2221 arn_type = 'vpc-peering-connection' 

2222 enum_spec = ('describe_vpc_peering_connections', 

2223 'VpcPeeringConnections', None) 

2224 name = id = 'VpcPeeringConnectionId' 

2225 filter_name = 'VpcPeeringConnectionIds' 

2226 filter_type = 'list' 

2227 id_prefix = "pcx-" 

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

2229 

2230 

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

2232class CrossAccountPeer(CrossAccountAccessFilter): 

2233 

2234 schema = type_schema( 

2235 'cross-account', 

2236 # white list accounts 

2237 whitelist_from=resolver.ValuesFrom.schema, 

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

2239 

2240 permissions = ('ec2:DescribeVpcPeeringConnections',) 

2241 

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

2243 results = [] 

2244 accounts = self.get_accounts() 

2245 owners = map(jmespath_compile, ( 

2246 'AccepterVpcInfo.OwnerId', 'RequesterVpcInfo.OwnerId')) 

2247 

2248 for r in resources: 

2249 for o_expr in owners: 

2250 account_id = o_expr.search(r) 

2251 if account_id and account_id not in accounts: 

2252 r.setdefault( 

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

2254 results.append(r) 

2255 return results 

2256 

2257 

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

2259class MissingRoute(Filter): 

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

2261 

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

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

2264 each vpc. 

2265 

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

2267 route table is checked. 

2268 """ 

2269 

2270 schema = type_schema('missing-route') 

2271 permissions = ('ec2:DescribeRouteTables',) 

2272 

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

2274 tables = self.manager.get_resource_manager( 

2275 'route-table').resources() 

2276 routed_vpcs = {} 

2277 mid = 'VpcPeeringConnectionId' 

2278 for t in tables: 

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

2280 if mid in r: 

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

2282 results = [] 

2283 for r in resources: 

2284 if r[mid] not in routed_vpcs: 

2285 results.append(r) 

2286 continue 

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

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

2289 continue 

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

2291 continue 

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

2293 results.append(r) 

2294 break 

2295 return results 

2296 

2297 

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

2299class NetworkAcl(query.QueryResourceManager): 

2300 

2301 class resource_type(query.TypeInfo): 

2302 service = 'ec2' 

2303 arn_type = 'network-acl' 

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

2305 name = id = 'NetworkAclId' 

2306 filter_name = 'NetworkAclIds' 

2307 filter_type = 'list' 

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

2309 id_prefix = "acl-" 

2310 

2311 

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

2313class AclSubnetFilter(net_filters.SubnetFilter): 

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

2315 

2316 :example: 

2317 

2318 .. code-block:: yaml 

2319 

2320 policies: 

2321 - name: subnet-acl 

2322 resource: network-acl 

2323 filters: 

2324 - type: subnet 

2325 key: "tag:Location" 

2326 value: Public 

2327 """ 

2328 

2329 RelatedIdsExpression = "Associations[].SubnetId" 

2330 

2331 

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

2333class AclAwsS3Cidrs(Filter): 

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

2335 

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

2337 

2338 :example: 

2339 

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

2341 

2342 .. code-block:: yaml 

2343 

2344 policies: 

2345 - name: s3-not-allowed-nacl 

2346 resource: network-acl 

2347 filters: 

2348 - s3-cidr 

2349 """ 

2350 # TODO allow for port specification as range 

2351 schema = type_schema( 

2352 's3-cidr', 

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

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

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

2356 

2357 permissions = ('ec2:DescribePrefixLists',) 

2358 

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

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

2361 cidrs = jmespath_search( 

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

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

2364 results = [] 

2365 

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

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

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

2369 

2370 for r in resources: 

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

2372 for entry in r['Entries']: 

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

2374 continue 

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

2376 continue 

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

2378 for c in matched: 

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

2380 matched[c] = ( 

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

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

2383 results.append(r) 

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

2385 results.append(r) 

2386 return results 

2387 

2388 

2389class DescribeElasticIp(query.DescribeSource): 

2390 

2391 def augment(self, resources): 

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

2393 

2394 

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

2396class NetworkAddress(query.QueryResourceManager): 

2397 

2398 class resource_type(query.TypeInfo): 

2399 service = 'ec2' 

2400 arn_type = 'elastic-ip' 

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

2402 name = 'PublicIp' 

2403 id = 'AllocationId' 

2404 id_prefix = 'eipalloc-' 

2405 filter_name = 'AllocationIds' 

2406 filter_type = 'list' 

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

2408 

2409 source_mapping = { 

2410 'describe': DescribeElasticIp, 

2411 'config': query.ConfigSource 

2412 } 

2413 

2414 

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

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

2417 

2418 

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

2420class AddressRelease(BaseAction): 

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

2422 

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

2424 also be released. Otherwise, only unattached elastic IPs 

2425 will be released. 

2426 

2427 :example: 

2428 

2429 .. code-block:: yaml 

2430 

2431 policies: 

2432 - name: release-network-addr 

2433 resource: network-addr 

2434 filters: 

2435 - AllocationId: ... 

2436 actions: 

2437 - type: release 

2438 force: True 

2439 """ 

2440 

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

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

2443 

2444 def process_attached(self, client, associated_addrs): 

2445 for aa in list(associated_addrs): 

2446 try: 

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

2448 except ClientError as e: 

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

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

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

2452 raise e 

2453 associated_addrs.remove(aa) 

2454 return associated_addrs 

2455 

2456 def process(self, network_addrs): 

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

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

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

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

2461 

2462 if len(assoc_addrs) and not force: 

2463 self.log.warning( 

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

2465 len(assoc_addrs), len(network_addrs)) 

2466 elif len(assoc_addrs) and force: 

2467 unassoc_addrs = itertools.chain( 

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

2469 

2470 for r in unassoc_addrs: 

2471 try: 

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

2473 except ClientError as e: 

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

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

2476 self.log.warning( 

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

2478 r['AllocationId']) 

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

2480 self.log.warning( 

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

2482 r['AllocationId']) 

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

2484 raise 

2485 

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

2487class DisassociateAddress(BaseAction): 

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

2489 

2490 :example: 

2491 

2492 .. code-block:: yaml 

2493 

2494 policies: 

2495 - name: disassociate-network-addr 

2496 resource: network-addr 

2497 filters: 

2498 - AllocationId: ... 

2499 actions: 

2500 - type: disassociate 

2501 """ 

2502 

2503 schema = type_schema('disassociate') 

2504 permissions = ('ec2:DisassociateAddress',) 

2505 

2506 def process(self, network_addrs): 

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

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

2509 

2510 for aa in assoc_addrs: 

2511 try: 

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

2513 except ClientError as e: 

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

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

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

2517 raise e 

2518 

2519 

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

2521class CustomerGateway(query.QueryResourceManager): 

2522 

2523 class resource_type(query.TypeInfo): 

2524 service = 'ec2' 

2525 arn_type = 'customer-gateway' 

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

2527 id = 'CustomerGatewayId' 

2528 filter_name = 'CustomerGatewayIds' 

2529 filter_type = 'list' 

2530 name = 'CustomerGatewayId' 

2531 id_prefix = "cgw-" 

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

2533 

2534 

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

2536class InternetGateway(query.QueryResourceManager): 

2537 

2538 class resource_type(query.TypeInfo): 

2539 service = 'ec2' 

2540 arn_type = 'internet-gateway' 

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

2542 name = id = 'InternetGatewayId' 

2543 filter_name = 'InternetGatewayIds' 

2544 filter_type = 'list' 

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

2546 id_prefix = "igw-" 

2547 

2548 

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

2550class DeleteInternetGateway(BaseAction): 

2551 

2552 """Action to delete Internet Gateway 

2553 

2554 :example: 

2555 

2556 .. code-block:: yaml 

2557 

2558 policies: 

2559 - name: delete-internet-gateway 

2560 resource: internet-gateway 

2561 actions: 

2562 - type: delete 

2563 """ 

2564 

2565 schema = type_schema('delete') 

2566 permissions = ('ec2:DeleteInternetGateway',) 

2567 

2568 def process(self, resources): 

2569 

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

2571 for r in resources: 

2572 try: 

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

2574 except ClientError as err: 

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

2576 self.log.warning( 

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

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

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

2580 ) 

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

2582 pass 

2583 else: 

2584 raise 

2585 

2586 

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

2588class NATGateway(query.QueryResourceManager): 

2589 

2590 class resource_type(query.TypeInfo): 

2591 service = 'ec2' 

2592 arn_type = 'natgateway' 

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

2594 name = id = 'NatGatewayId' 

2595 filter_name = 'NatGatewayIds' 

2596 filter_type = 'list' 

2597 date = 'CreateTime' 

2598 dimension = 'NatGatewayId' 

2599 metrics_namespace = 'AWS/NATGateway' 

2600 id_prefix = "nat-" 

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

2602 

2603 

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

2605class DeleteNATGateway(BaseAction): 

2606 

2607 schema = type_schema('delete') 

2608 permissions = ('ec2:DeleteNatGateway',) 

2609 

2610 def process(self, resources): 

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

2612 for r in resources: 

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

2614 

2615 

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

2617class VPNConnection(query.QueryResourceManager): 

2618 

2619 class resource_type(query.TypeInfo): 

2620 service = 'ec2' 

2621 arn_type = 'vpn-connection' 

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

2623 name = id = 'VpnConnectionId' 

2624 filter_name = 'VpnConnectionIds' 

2625 filter_type = 'list' 

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

2627 id_prefix = "vpn-" 

2628 

2629 

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

2631class VPNGateway(query.QueryResourceManager): 

2632 

2633 class resource_type(query.TypeInfo): 

2634 service = 'ec2' 

2635 arn_type = 'vpn-gateway' 

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

2637 name = id = 'VpnGatewayId' 

2638 filter_name = 'VpnGatewayIds' 

2639 filter_type = 'list' 

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

2641 id_prefix = "vgw-" 

2642 

2643 

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

2645class VpcEndpoint(query.QueryResourceManager): 

2646 

2647 class resource_type(query.TypeInfo): 

2648 service = 'ec2' 

2649 arn_type = 'vpc-endpoint' 

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

2651 name = id = 'VpcEndpointId' 

2652 metrics_namespace = "AWS/PrivateLinkEndpoints" 

2653 date = 'CreationTimestamp' 

2654 filter_name = 'VpcEndpointIds' 

2655 filter_type = 'list' 

2656 id_prefix = "vpce-" 

2657 universal_taggable = object() 

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

2659 

2660 

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

2662class VpcEndpointMetricsFilter(MetricsFilter): 

2663 

2664 def get_dimensions(self, resource): 

2665 return [ 

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

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

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

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

2670 ] 

2671 

2672 

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

2674class EndpointPolicyStatementFilter(HasStatementFilter): 

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

2676 

2677 :example: 

2678 

2679 .. code-block:: yaml 

2680 

2681 policies: 

2682 - name: vpc-endpoint-policy 

2683 resource: aws.vpc-endpoint 

2684 filters: 

2685 - type: has-statement 

2686 statements: 

2687 - Action: "*" 

2688 Effect: "Allow" 

2689 """ 

2690 

2691 policy_attribute = 'PolicyDocument' 

2692 permissions = ('ec2:DescribeVpcEndpoints',) 

2693 

2694 def get_std_format_args(self, endpoint): 

2695 return { 

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

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

2698 'region': self.manager.config.region 

2699 } 

2700 

2701 

2702 

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

2704class EndpointCrossAccountFilter(CrossAccountAccessFilter): 

2705 

2706 policy_attribute = 'PolicyDocument' 

2707 annotation_key = 'c7n:CrossAccountViolations' 

2708 permissions = ('ec2:DescribeVpcEndpoints',) 

2709 

2710 

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

2712class EndpointSecurityGroupFilter(net_filters.SecurityGroupFilter): 

2713 

2714 RelatedIdsExpression = "Groups[].GroupId" 

2715 

2716 

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

2718class EndpointSubnetFilter(net_filters.SubnetFilter): 

2719 

2720 RelatedIdsExpression = "SubnetIds[]" 

2721 

2722 

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

2724class EndpointVpcFilter(net_filters.VpcFilter): 

2725 

2726 RelatedIdsExpression = "VpcId" 

2727 

2728 

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

2730class VPCEndpointFilter(RelatedResourceByIdFilter): 

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

2732 

2733 :example: 

2734 

2735 .. code-block:: yaml 

2736 

2737 policies: 

2738 - name: s3-vpc-endpoint-enabled 

2739 resource: vpc 

2740 filters: 

2741 - type: vpc-endpoint 

2742 key: ServiceName 

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

2744 """ 

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

2746 RelatedIdsExpression = "VpcId" 

2747 AnnotationKey = "matched-vpc-endpoint" 

2748 

2749 schema = type_schema( 

2750 'vpc-endpoint', 

2751 rinherit=ValueFilter.schema) 

2752 

2753 

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

2755class SubnetEndpointFilter(RelatedResourceByIdFilter): 

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

2757 

2758 :example: 

2759 

2760 .. code-block:: yaml 

2761 

2762 policies: 

2763 - name: athena-endpoint-enabled 

2764 resource: subnet 

2765 filters: 

2766 - type: vpc-endpoint 

2767 key: ServiceName 

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

2769 """ 

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

2771 RelatedIdsExpression = "SubnetId" 

2772 RelatedResourceByIdExpression = "SubnetIds" 

2773 AnnotationKey = "matched-vpc-endpoint" 

2774 

2775 schema = type_schema( 

2776 'vpc-endpoint', 

2777 rinherit=ValueFilter.schema) 

2778 

2779 

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

2781class KeyPair(query.QueryResourceManager): 

2782 

2783 class resource_type(query.TypeInfo): 

2784 service = 'ec2' 

2785 arn_type = 'key-pair' 

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

2787 name = 'KeyName' 

2788 id = 'KeyPairId' 

2789 id_prefix = 'key-' 

2790 filter_name = 'KeyNames' 

2791 filter_type = 'list' 

2792 

2793 

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

2795class UnusedKeyPairs(Filter): 

2796 """Filter for used or unused keys. 

2797 

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

2799 

2800 :example: 

2801 

2802 .. code-block:: yaml 

2803 

2804 policies: 

2805 - name: unused-key-pairs 

2806 resource: aws.key-pair 

2807 filters: 

2808 - unused 

2809 - name: used-key-pairs 

2810 resource: aws.key-pair 

2811 filters: 

2812 - type: unused 

2813 state: false 

2814 """ 

2815 schema = type_schema('unused', 

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

2817 

2818 def get_permissions(self): 

2819 return list(itertools.chain(*[ 

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

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

2822 

2823 def _pull_asg_keynames(self): 

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

2825 key_names = set() 

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

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

2828 

2829 if lcfgs: 

2830 key_names.update([ 

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

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

2833 

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

2835 for tversion in tmpl_mgr.get_resources( 

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

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

2838 return key_names 

2839 

2840 def _pull_ec2_keynames(self): 

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

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

2843 

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

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

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

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

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

2849 

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

2851class DeleteUnusedKeyPairs(BaseAction): 

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

2853 

2854 This should always be used with the unused filter 

2855 and it will prevent you from using without it. 

2856 

2857 :example: 

2858 

2859 .. code-block:: yaml 

2860 

2861 policies: 

2862 - name: delete-unused-key-pairs 

2863 resource: aws.key-pair 

2864 filters: 

2865 - unused 

2866 actions: 

2867 - delete 

2868 """ 

2869 permissions = ('ec2:DeleteKeyPair',) 

2870 schema = type_schema('delete') 

2871 

2872 def validate(self): 

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

2874 raise PolicyValidationError( 

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

2876 self.manager.data,)) 

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

2878 raise PolicyValidationError( 

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

2880 self.manager.data,)) 

2881 return self 

2882 

2883 def process(self, unused): 

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

2885 for key in unused: 

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

2887 

2888 

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

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

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

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

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

2894class SetFlowLogs(BaseAction): 

2895 """Set flow logs for a network resource 

2896 

2897 :example: 

2898 

2899 .. code-block:: yaml 

2900 

2901 policies: 

2902 - name: vpc-enable-flow-logs 

2903 resource: vpc 

2904 filters: 

2905 - type: flow-logs 

2906 enabled: false 

2907 actions: 

2908 - type: set-flow-log 

2909 attrs: 

2910 DeliverLogsPermissionArn: arn:iam:role 

2911 LogGroupName: /custodian/vpc/flowlogs/ 

2912 

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

2914 documentation 

2915 

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

2917 """ # noqa 

2918 

2919 legacy_schema = { 

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

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

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

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

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

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

2926 'TrafficType': { 

2927 'type': 'string', 

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

2929 } 

2930 } 

2931 

2932 schema = type_schema( 

2933 'set-flow-log', 

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

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

2936 **legacy_schema 

2937 ) 

2938 shape = 'CreateFlowLogsRequest' 

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

2940 

2941 RESOURCE_ALIAS = { 

2942 'vpc': 'VPC', 

2943 'subnet': 'Subnet', 

2944 'eni': 'NetworkInterface', 

2945 'transit-gateway': 'TransitGateway', 

2946 'transit-attachment': 'TransitGatewayAttachment' 

2947 } 

2948 

2949 def get_deprecations(self): 

2950 filter_name = self.data["type"] 

2951 return [ 

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

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

2954 ] 

2955 

2956 def validate(self): 

2957 self.convert() 

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

2959 model = self.manager.get_model() 

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

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

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

2963 

2964 def convert(self): 

2965 data = dict(self.data) 

2966 attrs = {} 

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

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

2969 self.source_data = self.data 

2970 self.data['attrs'] = attrs 

2971 

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

2973 try: 

2974 results = op(**params) 

2975 for r in results['Unsuccessful']: 

2976 self.log.exception( 

2977 'Exception: %s for %s: %s', 

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

2979 except ClientError as e: 

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

2981 self.log.exception( 

2982 'Exception: %s: %s', 

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

2984 else: 

2985 raise 

2986 

2987 def ensure_log_group(self, logroup): 

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

2989 try: 

2990 client.create_log_group(logGroupName=logroup) 

2991 except client.exceptions.ResourceAlreadyExistsException: 

2992 pass 

2993 

2994 def delete_flow_logs(self, client, rids): 

2995 flow_logs = [ 

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

2997 if r['ResourceId'] in rids] 

2998 self.run_client_op( 

2999 client.delete_flow_logs, 

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

3001 ('InvalidParameterValue',) 

3002 ) 

3003 

3004 def process(self, resources): 

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

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

3007 

3008 if not enabled: 

3009 return self.delete_flow_logs(client, resources) 

3010 

3011 model = self.manager.get_model() 

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

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

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

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

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

3017 self.run_client_op( 

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

3019 

3020 

3021class PrefixListDescribe(query.DescribeSource): 

3022 

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

3024 query = {'Filters': [ 

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

3026 'Values': ids}]} 

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

3028 

3029 

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

3031class PrefixList(query.QueryResourceManager): 

3032 

3033 class resource_type(query.TypeInfo): 

3034 service = 'ec2' 

3035 arn_type = 'prefix-list' 

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

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

3038 name = 'PrefixListName' 

3039 id = 'PrefixListId' 

3040 id_prefix = 'pl-' 

3041 universal_taggable = object() 

3042 

3043 source_mapping = {'describe': PrefixListDescribe} 

3044 

3045 

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

3047class Entry(Filter): 

3048 

3049 schema = type_schema( 

3050 'entry', rinherit=ValueFilter.schema) 

3051 permissions = ('ec2:GetManagedPrefixListEntries',) 

3052 

3053 annotation_key = 'c7n:prefix-entries' 

3054 match_annotation_key = 'c7n:matched-entries' 

3055 

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

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

3058 for r in resources: 

3059 if self.annotation_key in r: 

3060 continue 

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

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

3063 

3064 vf = ValueFilter(self.data) 

3065 vf.annotate = False 

3066 

3067 results = [] 

3068 for r in resources: 

3069 matched = [] 

3070 for e in r[self.annotation_key]: 

3071 if vf(e): 

3072 matched.append(e) 

3073 if matched: 

3074 results.append(r) 

3075 r[self.match_annotation_key] = matched 

3076 return results 

3077 

3078 

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

3080class SubnetModifyAtrributes(BaseAction): 

3081 """Modify subnet attributes. 

3082 

3083 :example: 

3084 

3085 .. code-block:: yaml 

3086 

3087 policies: 

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

3089 resource: aws.subnet 

3090 filters: 

3091 - type: value 

3092 key: "MapPublicIpOnLaunch.enabled" 

3093 value: false 

3094 actions: 

3095 - type: modify 

3096 MapPublicIpOnLaunch: false 

3097 """ 

3098 

3099 schema = type_schema( 

3100 "modify", 

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

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

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

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

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

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

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

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

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

3110 PrivateDnsHostnameTypeOnLaunch={ 

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

3112 } 

3113 ) 

3114 

3115 permissions = ("ec2:ModifySubnetAttribute",) 

3116 

3117 def process(self, resources): 

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

3119 params = dict(self.data) 

3120 params.pop('type') 

3121 

3122 for k in list(params): 

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

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

3125 

3126 for r in resources: 

3127 self.manager.retry( 

3128 client.modify_subnet_attribute, 

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

3130 return resources 

3131 

3132 

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

3134class TrafficMirrorSession(query.QueryResourceManager): 

3135 

3136 class resource_type(query.TypeInfo): 

3137 service = 'ec2' 

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

3139 name = id = 'TrafficMirrorSessionId' 

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

3141 arn_type = 'traffic-mirror-session' 

3142 universal_taggable = object() 

3143 id_prefix = 'tms-' 

3144 

3145 

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

3147class DeleteTrafficMirrorSession(BaseAction): 

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

3149 

3150 :example: 

3151 

3152 .. code-block:: yaml 

3153 

3154 policies: 

3155 - name: traffic-mirror-session-paclength 

3156 resource: mirror-session 

3157 filters: 

3158 - type: value 

3159 key: tag:Owner 

3160 value: xyz 

3161 actions: 

3162 - delete 

3163 """ 

3164 

3165 schema = type_schema('delete') 

3166 permissions = ('ec2:DeleteTrafficMirrorSession',) 

3167 

3168 def process(self, resources): 

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

3170 for r in resources: 

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

3172 

3173 

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

3175class TrafficMirrorTarget(query.QueryResourceManager): 

3176 

3177 class resource_type(query.TypeInfo): 

3178 service = 'ec2' 

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

3180 name = id = 'TrafficMirrorTargetId' 

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

3182 arn_type = 'traffic-mirror-target' 

3183 universal_taggable = object() 

3184 id_prefix = 'tmt-' 

3185 

3186 

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

3188class CrossAZRouteTable(Filter): 

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

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

3191 

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

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

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

3195 

3196 :Example: 

3197 

3198 .. code-block:: yaml 

3199 

3200 policies: 

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

3202 resource: aws.route-table 

3203 filters: 

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

3205 actions: 

3206 - notify 

3207 

3208 """ 

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

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

3211 

3212 table_annotation = "c7n:route-table" 

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

3214 

3215 def resolve_subnets(self, resource, subnets): 

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

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

3218 

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

3220 # annotate route table associations onto their respective subnets 

3221 main_tables = [] 

3222 # annotate explicit associations 

3223 for t in tables: 

3224 for association in t['Associations']: 

3225 if association.get('SubnetId'): 

3226 subnets[association['SubnetId']][ 

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

3228 if association.get('Main'): 

3229 main_tables.append(t) 

3230 # annotate main tables 

3231 for s in subnets.values(): 

3232 if self.table_annotation in s: 

3233 continue 

3234 for t in main_tables: 

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

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

3237 

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

3239 matched = {} 

3240 found = False 

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

3242 for route in resource['Routes']: 

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

3244 continue 

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

3246 mismatch_subnets = { 

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

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

3249 if not mismatch_subnets: 

3250 continue 

3251 found = True 

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

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

3254 if not found: 

3255 return 

3256 resource[self.mismatch_annotation] = matched 

3257 return resource 

3258 

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

3260 subnets = { 

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

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

3263 } 

3264 nat_subnets = { 

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

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

3267 

3268 results = [] 

3269 self.annotate_subnets_table(resources, subnets) 

3270 for resource in resources: 

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

3272 results.append(resource) 

3273 

3274 return results