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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

499 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3import functools 

4import fnmatch 

5import json 

6import itertools 

7import os 

8 

9from botocore.paginate import Paginator 

10 

11from c7n.query import ( 

12 QueryResourceManager, ChildResourceManager, TypeInfo, RetryPageIterator) 

13from c7n.manager import resources 

14from c7n.utils import chunks, get_retry, generate_arn, local_session, type_schema 

15from c7n.actions import BaseAction 

16from c7n.filters import Filter, ListItemFilter 

17from c7n.resources.shield import IsShieldProtected, SetShieldProtection 

18from c7n.tags import RemoveTag, Tag 

19from c7n.filters.related import RelatedResourceFilter 

20from c7n import tags, query 

21from c7n.filters.iamaccess import CrossAccountAccessFilter 

22from c7n.resolver import ValuesFrom 

23 

24 

25class Route53Base: 

26 

27 permissions = ('route53:ListTagsForResources',) 

28 retry = staticmethod(get_retry(('Throttled',))) 

29 

30 @property 

31 def generate_arn(self): 

32 if self._generate_arn is None: 

33 self._generate_arn = functools.partial( 

34 generate_arn, 

35 self.get_model().service, 

36 resource_type=self.get_model().arn_type) 

37 return self._generate_arn 

38 

39 def get_arn(self, r): 

40 return self.generate_arn(r[self.get_model().id].split("/")[-1]) 

41 

42 def augment(self, resources): 

43 _describe_route53_tags( 

44 self.get_model(), resources, self.session_factory, 

45 self.executor_factory, self.retry) 

46 return resources 

47 

48 

49def _describe_route53_tags( 

50 model, resources, session_factory, executor_factory, retry): 

51 

52 def process_tags(resources): 

53 client = local_session(session_factory).client('route53') 

54 resource_map = {} 

55 for r in resources: 

56 k = r[model.id] 

57 if "hostedzone" in k: 

58 k = k.split("/")[-1] 

59 resource_map[k] = r 

60 

61 for resource_batch in chunks(list(resource_map.keys()), 10): 

62 results = retry( 

63 client.list_tags_for_resources, 

64 ResourceType=model.arn_type, 

65 ResourceIds=resource_batch) 

66 for resource_tag_set in results['ResourceTagSets']: 

67 if ('ResourceId' in resource_tag_set and 

68 resource_tag_set['ResourceId'] in resource_map): 

69 resource_map[resource_tag_set['ResourceId']]['Tags'] = resource_tag_set['Tags'] 

70 

71 with executor_factory(max_workers=2) as w: 

72 return list(w.map(process_tags, chunks(resources, 20))) 

73 

74 

75def generate_rrset(recordset): 

76 keys = ( 

77 'Name', 'Type', 'TTL', 'SetIdentifier', 'Region', 'AliasTarget', 'ResourceRecords') 

78 rrset_payload = dict() 

79 for key in keys: 

80 if key in recordset: 

81 rrset_payload.update({key: recordset[key]}) 

82 return rrset_payload 

83 

84 

85@resources.register('hostedzone') 

86class HostedZone(Route53Base, QueryResourceManager): 

87 

88 class resource_type(TypeInfo): 

89 service = 'route53' 

90 arn_type = 'hostedzone' 

91 enum_spec = ('list_hosted_zones', 'HostedZones', None) 

92 # detail_spec = ('get_hosted_zone', 'Id', 'Id', None) 

93 id = 'Id' 

94 name = 'Name' 

95 config_id = 'c7n:ConfigHostedZoneId' 

96 universal_taggable = True 

97 # Denotes this resource type exists across regions 

98 global_resource = True 

99 cfn_type = 'AWS::Route53::HostedZone' 

100 permissions_augment = ("route53:ListTagsForResource",) 

101 

102 def get_arns(self, resource_set): 

103 arns = [] 

104 for r in resource_set: 

105 _id = r[self.get_model().id].split("/")[-1] 

106 arns.append(self.generate_arn(_id)) 

107 return arns 

108 

109 def augment(self, resources): 

110 annotation_key = 'c7n:ConfigHostedZoneId' 

111 resources = super(HostedZone, self).augment(resources) 

112 for r in resources: 

113 config_hzone_id = r['Id'].split("/")[-1] 

114 r[annotation_key] = config_hzone_id 

115 return resources 

116 

117 

118HostedZone.filter_registry.register('shield-enabled', IsShieldProtected) 

119HostedZone.action_registry.register('set-shield', SetShieldProtection) 

120 

121 

122@resources.register('healthcheck') 

123class HealthCheck(Route53Base, QueryResourceManager): 

124 

125 class resource_type(TypeInfo): 

126 service = 'route53' 

127 arn_type = 'healthcheck' 

128 enum_spec = ('list_health_checks', 'HealthChecks', None) 

129 name = id = 'Id' 

130 universal_taggable = True 

131 cfn_type = 'AWS::Route53::HealthCheck' 

132 global_resource = True 

133 permissions_augment = ("route53:ListTagsForResource",) 

134 

135 

136@resources.register('rrset') 

137class ResourceRecordSet(ChildResourceManager): 

138 

139 class resource_type(TypeInfo): 

140 service = 'route53' 

141 arn_type = 'rrset' 

142 parent_spec = ('hostedzone', 'HostedZoneId', True) 

143 enum_spec = ('list_resource_record_sets', 'ResourceRecordSets', None) 

144 name = id = 'Name' 

145 cfn_type = 'AWS::Route53::RecordSet' 

146 global_resource = True 

147 

148 

149@resources.register('r53domain') 

150class Route53Domain(QueryResourceManager): 

151 

152 class resource_type(TypeInfo): 

153 service = 'route53domains' 

154 arn_type = 'r53domain' 

155 enum_spec = ('list_domains', 'Domains', None) 

156 name = id = 'DomainName' 

157 global_resource = False 

158 

159 permissions = ('route53domains:ListTagsForDomain',) 

160 

161 def augment(self, domains): 

162 client = local_session(self.session_factory).client('route53domains') 

163 for d in domains: 

164 d['Tags'] = self.retry( 

165 client.list_tags_for_domain, 

166 DomainName=d['DomainName'])['TagList'] 

167 return domains 

168 

169 

170@Route53Domain.action_registry.register('tag') 

171class Route53DomainAddTag(Tag): 

172 """Adds tags to a route53 domain 

173 

174 :example: 

175 

176 .. code-block:: yaml 

177 

178 policies: 

179 - name: route53-tag 

180 resource: r53domain 

181 filters: 

182 - "tag:DesiredTag": absent 

183 actions: 

184 - type: tag 

185 key: DesiredTag 

186 value: DesiredValue 

187 """ 

188 permissions = ('route53domains:UpdateTagsForDomain',) 

189 

190 def process_resource_set(self, client, domains, tags): 

191 mid = self.manager.resource_type.id 

192 for d in domains: 

193 client.update_tags_for_domain( 

194 DomainName=d[mid], 

195 TagsToUpdate=tags) 

196 

197 

198@Route53Domain.action_registry.register('remove-tag') 

199class Route53DomainRemoveTag(RemoveTag): 

200 """Remove tags from a route53 domain 

201 

202 :example: 

203 

204 .. code-block:: yaml 

205 

206 policies: 

207 - name: route53-expired-tag 

208 resource: r53domain 

209 filters: 

210 - "tag:ExpiredTag": present 

211 actions: 

212 - type: remove-tag 

213 tags: ['ExpiredTag'] 

214 """ 

215 permissions = ('route53domains:DeleteTagsForDomain',) 

216 

217 def process_resource_set(self, client, domains, keys): 

218 for d in domains: 

219 client.delete_tags_for_domain( 

220 DomainName=d[self.id_key], 

221 TagsToDelete=keys) 

222 

223 

224@ResourceRecordSet.action_registry.register('delete') 

225class ResourceRecordSetRemove(BaseAction): 

226 """Action to delete resource records from Route 53 hosted zones. 

227 

228 It is recommended to use a filter to avoid unwanted deletion 

229 of R53 records from all hosted zones. 

230 

231 :example: 

232 

233 .. code-block:: yaml 

234 

235 policies: 

236 - name: route53-remove-filtered-records 

237 resource: aws.rrset 

238 filters: 

239 - type: value 

240 key: AliasTarget.DNSName 

241 value: "email.gc.example.com." 

242 actions: 

243 - type: delete 

244 

245 """ 

246 schema = type_schema('delete',) 

247 permissions = ('route53:ChangeResourceRecordSets',) 

248 

249 def process(self, recordsets): 

250 client = local_session(self.manager.session_factory).client('route53') 

251 try: 

252 for rrset in recordsets: 

253 

254 # Exempt the two zone associated mandatory records 

255 if rrset['Type'] in ('NS', 'SOA'): 

256 continue 

257 

258 rrsetdata = generate_rrset(rrset) 

259 self.manager.retry( 

260 client.change_resource_record_sets, 

261 HostedZoneId=rrset['c7n:parent-id'], 

262 ChangeBatch={ 

263 'Changes': [ 

264 { 

265 'Action': 'DELETE', 

266 'ResourceRecordSet': rrsetdata, 

267 } 

268 ] 

269 }, 

270 ignore_err_codes=('InvalidChangeBatch')) 

271 except Exception as e: 

272 self.log.warning( 

273 "ResourceRecordSet delete error: %s", e) 

274 

275 

276@HostedZone.action_registry.register('delete') 

277class Delete(BaseAction): 

278 """Action to delete Route 53 hosted zones. 

279 

280 It is recommended to use a filter to avoid unwanted deletion of R53 hosted zones. 

281 

282 If set to force this action will wipe out all records in the hosted zone 

283 before deleting the zone. 

284 

285 :example: 

286 

287 .. code-block:: yaml 

288 

289 policies: 

290 - name: route53-delete-testing-hosted-zones 

291 resource: aws.hostedzone 

292 filters: 

293 - 'tag:TestTag': present 

294 actions: 

295 - type: delete 

296 force: true 

297 

298 """ 

299 

300 schema = type_schema('delete', force={'type': 'boolean'}) 

301 permissions = ('route53:DeleteHostedZone',) 

302 keys = ( 

303 'Name', 'Type', 'TTL', 'SetIdentifier', 'Region', 'AliasTarget', 'ResourceRecords') 

304 

305 def process(self, hosted_zones): 

306 client = local_session(self.manager.session_factory).client('route53') 

307 error = None 

308 for hz in hosted_zones: 

309 if self.data.get('force'): 

310 self.delete_records(client, hz) 

311 try: 

312 self.manager.retry( 

313 client.delete_hosted_zone, 

314 Id=hz['Id'], 

315 ignore_err_codes=('NoSuchHostedZone')) 

316 except client.exceptions.HostedZoneNotEmpty as e: 

317 self.log.warning( 

318 "HostedZone: %s cannot be deleted, " 

319 "set force to remove all records in zone", 

320 hz['Name']) 

321 error = e 

322 if error: 

323 raise error 

324 

325 def delete_records(self, client, hz): 

326 paginator = client.get_paginator('list_resource_record_sets') 

327 paginator.PAGE_ITERATOR_CLS = RetryPageIterator 

328 rrsets = paginator.paginate(HostedZoneId=hz['Id']).build_full_result() 

329 

330 for rrset in rrsets['ResourceRecordSets']: 

331 # Trigger the deletion of all the resource record sets before deleting 

332 # the hosted zone 

333 

334 # Exempt the two zone associated mandatory records 

335 if rrset['Name'] == hz['Name'] and rrset['Type'] in ('NS', 'SOA'): 

336 continue 

337 rrsetdata = generate_rrset(rrset) 

338 self.manager.retry( 

339 client.change_resource_record_sets, 

340 HostedZoneId=hz['Id'], 

341 ChangeBatch={ 

342 'Changes': [ 

343 { 

344 'Action': 'DELETE', 

345 'ResourceRecordSet': rrsetdata, 

346 } 

347 ] 

348 }, 

349 ignore_err_codes=('InvalidChangeBatch')) 

350 

351 

352@HostedZone.action_registry.register('set-query-logging') 

353class SetQueryLogging(BaseAction): 

354 """Enables query logging on a hosted zone. 

355 

356 By default this enables a log group per route53 domain, alternatively 

357 a log group name can be specified for a unified log across domains. 

358 

359 Note this only applicable to public route53 domains, and log groups 

360 must be created in us-east-1 region. 

361 

362 This action can optionally setup the resource permissions needed for 

363 route53 to log to cloud watch logs via `set-permissions: true`, else 

364 the cloud watch logs resource policy would need to be set separately. 

365 

366 Its recommended to use a separate custodian policy on the log 

367 groups to set the log retention period for the zone logs. See 

368 `custodian schema aws.log-group.actions.set-retention` 

369 

370 :example: 

371 

372 .. code-block:: yaml 

373 

374 policies: 

375 - name: enablednsquerylogging 

376 resource: hostedzone 

377 region: us-east-1 

378 filters: 

379 - type: query-logging-enabled 

380 state: false 

381 actions: 

382 - type: set-query-logging 

383 state: true 

384 

385 """ 

386 

387 permissions = ( 

388 'route53:GetQueryLoggingConfig', 

389 'route53:CreateQueryLoggingConfig', 

390 'route53:DeleteQueryLoggingConfig', 

391 'logs:DescribeLogGroups', 

392 'logs:CreateLogGroup', 

393 'logs:GetResourcePolicy', 

394 'logs:PutResourcePolicy') 

395 

396 schema = type_schema( 

397 'set-query-logging', **{ 

398 'set-permissions': {'type': 'boolean'}, 

399 'log-group-prefix': {'type': 'string', 'default': '/aws/route53'}, 

400 'log-group': {'type': 'string', 'default': 'auto'}, 

401 'state': {'type': 'boolean'}}) 

402 

403 statement = { 

404 "Sid": "Route53LogsToCloudWatchLogs", 

405 "Effect": "Allow", 

406 "Principal": {"Service": ["route53.amazonaws.com"]}, 

407 "Action": ["logs:PutLogEvents", "logs:CreateLogStream"], 

408 "Resource": None} 

409 

410 def validate(self): 

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

412 # By forcing use of a filter we ensure both getting to right set of 

413 # resources as well avoiding an extra api call here, as we'll reuse 

414 # the annotation from the filter for logging config. 

415 if not [f for f in self.manager.iter_filters() if isinstance( 

416 f, IsQueryLoggingEnabled)]: 

417 raise ValueError( 

418 "set-query-logging when deleting requires " 

419 "use of query-logging-enabled filter in policy") 

420 return self 

421 

422 def get_permissions(self): 

423 perms = [] 

424 if self.data.get('set-permissions'): 

425 perms.extend(('logs:GetResourcePolicy', 'logs:PutResourcePolicy')) 

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

427 perms.append('route53:CreateQueryLoggingConfig') 

428 perms.append('logs:CreateLogGroup') 

429 perms.append('logs:DescribeLogGroups') 

430 perms.append('tag:GetResources') 

431 else: 

432 perms.append('route53:DeleteQueryLoggingConfig') 

433 return perms 

434 

435 def process(self, resources): 

436 if self.manager.config.region != 'us-east-1': 

437 self.log.warning("set-query-logging should be only be performed region: us-east-1") 

438 

439 client = local_session(self.manager.session_factory).client('route53') 

440 state = self.data.get('state', True) 

441 

442 zone_log_names = {z['Id']: self.get_zone_log_name(z) for z in resources} 

443 if state: 

444 self.ensure_log_groups(set(zone_log_names.values())) 

445 

446 for r in resources: 

447 if not state: 

448 try: 

449 client.delete_query_logging_config(Id=r['c7n:log-config']['Id']) 

450 except client.exceptions.NoSuchQueryLoggingConfig: 

451 pass 

452 continue 

453 log_arn = "arn:aws:logs:us-east-1:{}:log-group:{}".format( 

454 self.manager.account_id, zone_log_names[r['Id']]) 

455 client.create_query_logging_config( 

456 HostedZoneId=r['Id'], 

457 CloudWatchLogsLogGroupArn=log_arn) 

458 

459 def get_zone_log_name(self, zone): 

460 if self.data.get('log-group', 'auto') == 'auto': 

461 log_group_name = "%s/%s" % ( 

462 self.data.get('log-group-prefix', '/aws/route53').rstrip('/'), 

463 zone['Name'][:-1]) 

464 else: 

465 log_group_name = self.data['log-group'] 

466 return log_group_name 

467 

468 def ensure_log_groups(self, group_names): 

469 log_manager = self.manager.get_resource_manager('log-group') 

470 log_manager.config = self.manager.config.copy(region='us-east-1') 

471 

472 if len(group_names) == 1: 

473 groups = [] 

474 if log_manager.get_resources(list(group_names), augment=False): 

475 groups = [{'logGroupName': g} for g in group_names] 

476 else: 

477 common_prefix = os.path.commonprefix(list(group_names)) 

478 if common_prefix not in ('', '/'): 

479 groups = log_manager.get_resources( 

480 [common_prefix], augment=False) 

481 else: 

482 groups = list(itertools.chain(*[ 

483 log_manager.get_resources([g]) for g in group_names])) 

484 

485 missing = group_names.difference({g['logGroupName'] for g in groups}) 

486 

487 # Logs groups must be created in us-east-1 for route53. 

488 client = local_session( 

489 self.manager.session_factory).client('logs', region_name='us-east-1') 

490 

491 for g in missing: 

492 client.create_log_group(logGroupName=g) 

493 

494 if self.data.get('set-permissions', False): 

495 self.ensure_route53_permissions(client, group_names) 

496 

497 def ensure_route53_permissions(self, client, group_names): 

498 if self.check_route53_permissions(client, group_names): 

499 return 

500 if self.data.get('log-group', 'auto') != 'auto': 

501 p_resource = "arn:aws:logs:us-east-1:{}:log-group:{}:*".format( 

502 self.manager.account_id, self.data['log-group']) 

503 else: 

504 p_resource = "arn:aws:logs:us-east-1:{}:log-group:{}/*".format( 

505 self.manager.account_id, 

506 self.data.get('log-group-prefix', '/aws/route53').rstrip('/')) 

507 

508 statement = dict(self.statement) 

509 statement['Resource'] = p_resource 

510 

511 client.put_resource_policy( 

512 policyName='Route53LogWrites', 

513 policyDocument=json.dumps( 

514 {"Version": "2012-10-17", "Statement": [statement]})) 

515 

516 def check_route53_permissions(self, client, group_names): 

517 group_names = set(group_names) 

518 for p in client.describe_resource_policies().get('resourcePolicies', []): 

519 for s in json.loads(p['policyDocument']).get('Statement', []): 

520 if (s['Effect'] == 'Allow' and 

521 s['Principal'].get('Service', ['']) == "route53.amazonaws.com"): 

522 group_names.difference_update( 

523 fnmatch.filter(group_names, s['Resource'].rsplit(':', 1)[-1])) 

524 if not group_names: 

525 return True 

526 return not bool(group_names) 

527 

528 

529def get_logging_config_paginator(client): 

530 return Paginator( 

531 client.list_query_logging_configs, 

532 {'input_token': 'NextToken', 'output_token': 'NextToken', 

533 'result_key': 'QueryLoggingConfigs'}, 

534 client.meta.service_model.operation_model('ListQueryLoggingConfigs')) 

535 

536 

537@HostedZone.filter_registry.register('query-logging-enabled') 

538class IsQueryLoggingEnabled(Filter): 

539 

540 permissions = ('route53:GetQueryLoggingConfig', 'route53:GetHostedZone', 

541 'logs:DescribeSubscriptionFilters') 

542 schema = type_schema('query-logging-enabled', state={'type': 'boolean'}) 

543 

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

545 client = local_session(self.manager.session_factory).client('route53') 

546 cw_client = local_session(self.manager.session_factory).client('logs') 

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

548 results = [] 

549 

550 enabled_zones = { 

551 c['HostedZoneId']: c for c in 

552 get_logging_config_paginator( 

553 client).paginate().build_full_result().get( 

554 'QueryLoggingConfigs', ())} 

555 for r in resources: 

556 zid = r['Id'].split('/', 2)[-1] 

557 # query logging is only supported for Public Hosted Zones. 

558 if r['Config']['PrivateZone'] is True: 

559 continue 

560 logging = zid in enabled_zones 

561 if logging and state: 

562 r['c7n:log-config'] = enabled_zones[zid] 

563 log_group_name = r['c7n:log-config']['CloudWatchLogsLogGroupArn'].split(":")[6] 

564 response = cw_client.describe_subscription_filters(logGroupName=log_group_name) 

565 r['c7n:log-config']['loggroup_subscription'] = response['subscriptionFilters'] 

566 results.append(r) 

567 elif not logging and not state: 

568 results.append(r) 

569 

570 return results 

571 

572 

573@resources.register('resolver-logs') 

574class ResolverQueryLogConfig(QueryResourceManager): 

575 

576 class resource_type(TypeInfo): 

577 service = 'route53resolver' 

578 arn_type = 'resolver-query-log-config' 

579 enum_spec = ('list_resolver_query_log_configs', 'ResolverQueryLogConfigs', None) 

580 name = 'Name' 

581 id = 'Id' 

582 cfn_type = 'AWS::Route53Resolver::ResolverQueryLoggingConfig' 

583 

584 annotation_key = 'c7n:Associations' 

585 permissions = ( 

586 'route53resolver:ListResolverQueryLogConfigs', 

587 'route53resolver:ListResolverQueryLogConfigAssociations') 

588 

589 def augment(self, rqlcs): 

590 client = local_session(self.session_factory).client('route53resolver') 

591 for rqlc in rqlcs: 

592 if rqlc['OwnerId'] != self.account_id: 

593 continue # don't try to fetch tags for shared resources 

594 rqlc['Tags'] = self.retry( 

595 client.list_tags_for_resource, 

596 ResourceArn=rqlc['Arn'])['Tags'] 

597 rqlc[self.annotation_key] = client.list_resolver_query_log_config_associations().get( 

598 'ResolverQueryLogConfigAssociations') 

599 return rqlcs 

600 

601 

602@ResolverQueryLogConfig.action_registry.register('associate-vpc') 

603class ResolverQueryLogConfigAssociate(BaseAction): 

604 """Associates ResolverQueryLogConfig to a VPC 

605 

606 :example: 

607 

608 .. code-block:: yaml 

609 

610 policies: 

611 - name: r53-resolver-query-log-config-associate 

612 resource: resolver-logs 

613 filters: 

614 - type: value 

615 key: 'Name' 

616 op: eq 

617 value: "Test-rqlc" 

618 actions: 

619 - type: associate-vpc 

620 vpcid: all 

621 

622 """ 

623 permissions = ( 

624 'route53resolver:AssociateResolverQueryLogConfig', 

625 'route53resolver:ListResolverQueryLogConfigAssociations') 

626 schema = type_schema('associate-vpc', vpcid={'type': 'string', 

627 'pattern': '^(?:vpc-[0-9a-f]{8,17}|all)$'}, required=['vpcid']) 

628 RelatedResource = 'c7n.resources.vpc.Vpc' 

629 RelatedIdsExpression = 'ResourceArn' 

630 

631 def get_vpc_id(self): 

632 vpcs = RelatedResourceFilter.get_resource_manager(self).resources() 

633 if self.data.get('vpcid') == 'all': 

634 vpc_ids = [v['VpcId'] for v in vpcs] 

635 else: 

636 vpc_ids = [self.data.get('vpcid')] 

637 return vpc_ids 

638 

639 def is_associated(self, resource, vpc_id): 

640 associated = False 

641 for association in resource.get('c7n:Associations', ()): 

642 if association['ResourceId'] == vpc_id: 

643 associated = True 

644 break 

645 return associated 

646 

647 def process(self, resources): 

648 client = local_session(self.manager.session_factory).client('route53resolver') 

649 vpc_ids = self.get_vpc_id() 

650 

651 # Don't try to take action on resources the active account doesn't own 

652 resources = self.filter_resources(resources, 'OwnerId', (self.manager.account_id,)) 

653 

654 for resource in resources: 

655 for vpc_id in vpc_ids: 

656 if not self.is_associated(resource, vpc_id): 

657 client.associate_resolver_query_log_config( 

658 ResolverQueryLogConfigId=resource['Id'], ResourceId=vpc_id) 

659 

660 

661@ResolverQueryLogConfig.filter_registry.register('is-associated') 

662class LogConfigAssociationsFilter(Filter): 

663 """ Checks LogConfig Associations for VPCs. 

664 

665 :example: 

666 

667 .. code-block:: yaml 

668 

669 policies: 

670 - name: r53-resolver-query-log-config-associations 

671 resource: resolver-logs 

672 filters: 

673 - type: is-associated 

674 vpcid: "vpc-12345678" 

675 

676 """ 

677 permissions = ('route53resolver:ListResolverQueryLogConfigAssociations',) 

678 schema = type_schema('is-associated', 

679 vpcid={'type': 'string', 'pattern': '^(?:vpc-[0-9a-f]{8,17}|all)$'},) 

680 RelatedResource = 'c7n.resources.vpc.Vpc' 

681 RelatedIdsExpression = 'ResourceArn' 

682 

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

684 results = [] 

685 vpc_ids = ResolverQueryLogConfigAssociate.get_vpc_id(self) 

686 for resource in resources: 

687 status = True 

688 for vpc_id in vpc_ids: 

689 if not ResolverQueryLogConfigAssociate.is_associated(self, resource, vpc_id): 

690 status = False 

691 break 

692 

693 if status: 

694 results.append(resource) 

695 

696 return results 

697 

698 

699# Readiness check in Amazon Route53 ARC is global feature. However, 

700# US West (N. California) Region must be specified in Route53 ARC readiness check api call. 

701# Please reference this AWS document: 

702# https://docs.aws.amazon.com/r53recovery/latest/dg/introduction-regions.html 

703ARC_REGION = 'us-west-2' 

704 

705 

706class DescribeCheck(query.DescribeSource): 

707 

708 def augment(self, readiness_checks): 

709 for r in readiness_checks: 

710 Tags = self.manager.retry( 

711 self.manager.get_client().list_tags_for_resources, 

712 ResourceArn=r['ReadinessCheckArn'])['Tags'] 

713 r['Tags'] = [{'Key': k, "Value": v} for k, v in Tags.items()] 

714 return readiness_checks 

715 

716 

717@resources.register('readiness-check') 

718class ReadinessCheck(QueryResourceManager): 

719 

720 class resource_type(TypeInfo): 

721 service = 'route53-recovery-readiness' 

722 arn_type = 'readiness-check' 

723 enum_spec = ('list_readiness_checks', 'ReadinessChecks', None) 

724 name = id = 'ReadinessCheckName' 

725 global_resource = True 

726 config_type = cfn_type = 'AWS::Route53RecoveryReadiness::ReadinessCheck' 

727 

728 source_mapping = {'describe': DescribeCheck, 'config': query.ConfigSource} 

729 

730 def get_client(self): 

731 return local_session(self.session_factory) \ 

732 .client('route53-recovery-readiness', region_name=ARC_REGION) 

733 

734 

735@ReadinessCheck.action_registry.register('tag') 

736class ReadinessAddTag(Tag): 

737 """Adds tags to a readiness check 

738 

739 :example: 

740 

741 .. code-block:: yaml 

742 

743 policies: 

744 - name: readiness-tag 

745 resource: readiness-check 

746 filters: 

747 - "tag:DesiredTag": absent 

748 actions: 

749 - type: tag 

750 key: DesiredTag 

751 value: DesiredValue 

752 """ 

753 permissions = ('route53-recovery-readiness:TagResource',) 

754 

755 def get_client(self): 

756 return self.manager.get_client() 

757 

758 def process_resource_set(self, client, readiness_checks, tags): 

759 Tags = {r['Key']: r['Value'] for r in tags} 

760 for r in readiness_checks: 

761 client.tag_resource( 

762 ResourceArn=r['ReadinessCheckArn'], 

763 Tags=Tags) 

764 

765 

766@ReadinessCheck.action_registry.register('remove-tag') 

767class ReadinessCheckRemoveTag(RemoveTag): 

768 """Remove tags from a readiness check 

769 

770 :example: 

771 

772 .. code-block:: yaml 

773 

774 policies: 

775 - name: readiness-check-remove-tag 

776 resource: readiness-check 

777 filters: 

778 - "tag:ExpiredTag": present 

779 actions: 

780 - type: remove-tag 

781 tags: ['ExpiredTag'] 

782 """ 

783 permissions = ('route53-recovery-readiness:UntagResource',) 

784 

785 def get_client(self): 

786 return self.manager.get_client() 

787 

788 def process_resource_set(self, client, readiness_checks, keys): 

789 for r in readiness_checks: 

790 client.untag_resource( 

791 ResourceArn=r['ReadinessCheckArn'], 

792 TagKeys=keys) 

793 

794 

795@ReadinessCheck.action_registry.register('mark-for-op') 

796class MarkForOpReadinessCheck(tags.TagDelayedAction): 

797 

798 def get_client(self): 

799 return self.manager.get_client() 

800 

801 

802@ReadinessCheck.filter_registry.register('marked-for-op') 

803class MarkedForOpReadinessCheck(tags.TagActionFilter): 

804 

805 def get_client(self): 

806 return self.manager.get_client() 

807 

808 

809@ReadinessCheck.filter_registry.register('cross-account') 

810class ReadinessCheckCrossAccount(CrossAccountAccessFilter): 

811 

812 schema = type_schema( 

813 'cross-account', 

814 # white list accounts 

815 whitelist_from=ValuesFrom.schema, 

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

817 permissions = ('route53-recovery-readiness:ListCrossAccountAuthorizations',) 

818 

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

820 allowed_accounts = set(self.get_accounts()) 

821 results, account_ids, found = [], [], False 

822 

823 paginator = self.manager.get_client().get_paginator('list_cross_account_authorizations') 

824 paginator.PAGE_ITERATOR_CLASS = RetryPageIterator 

825 arns = paginator.paginate().build_full_result()["CrossAccountAuthorizations"] 

826 

827 for arn in arns: 

828 account_id = arn.split(':', 5)[4] 

829 if account_id not in allowed_accounts: 

830 account_ids.append(account_id) 

831 found = True 

832 if (found): 

833 for r in resources: 

834 r['c7n:CrossAccountViolations'] = account_ids 

835 results.append(r) 

836 

837 return results 

838 

839 

840class DescribeCluster(query.DescribeSource): 

841 def augment(self, clusters): 

842 for r in clusters: 

843 Tags = self.manager.retry( 

844 self.manager.get_client().list_tags_for_resource, 

845 ResourceArn=r['ClusterArn'])['Tags'] 

846 r['Tags'] = [{'Key': k, "Value": v} for k, v in Tags.items()] 

847 return clusters 

848 

849 

850@resources.register('recovery-cluster') 

851class RecoveryCluster(QueryResourceManager): 

852 

853 class resource_type(TypeInfo): 

854 service = 'route53-recovery-control-config' 

855 arn_type = 'cluster' 

856 enum_spec = ('list_clusters', 'Clusters', None) 

857 name = id = 'ClusterArn' 

858 global_resource = True 

859 config_type = cfn_type = "AWS::Route53RecoveryControl::Cluster" 

860 

861 source_mapping = { 

862 'describe': DescribeCluster, 

863 'config': query.ConfigSource 

864 } 

865 

866 def get_client(self): 

867 return local_session(self.session_factory) \ 

868 .client('route53-recovery-control-config', region_name=ARC_REGION) 

869 

870 

871@RecoveryCluster.action_registry.register('tag') 

872class RecoveryClusterAddTag(Tag): 

873 """Adds tags to a cluster 

874 

875 :example: 

876 

877 .. code-block:: yaml 

878 

879 policies: 

880 - name: recovery-cluster-tag 

881 resource: recovery-cluster 

882 filters: 

883 - "tag:DesiredTag": absent 

884 actions: 

885 - type: tag 

886 key: DesiredTag 

887 value: DesiredValue 

888 """ 

889 permissions = ('route53-recovery-control-config:TagResource',) 

890 

891 def get_client(self): 

892 return self.manager.get_client() 

893 

894 def process_resource_set(self, client, clusters, tags): 

895 Tags = {r['Key']: r['Value'] for r in tags} 

896 for r in clusters: 

897 client.tag_resource( 

898 ResourceArn=r['ClusterArn'], 

899 Tags=Tags) 

900 

901 

902@RecoveryCluster.action_registry.register('remove-tag') 

903class RecoveryClusterRemoveTag(RemoveTag): 

904 """Remove tags from a cluster 

905 

906 :example: 

907 

908 

909 

910 .. code-block:: yaml 

911 

912 policies: 

913 - name: recovery-cluster-remove-tag 

914 resource: recovery-cluster 

915 filters: 

916 - "tag:ExpiredTag": present 

917 actions: 

918 - type: remove-tag 

919 tags: ['ExpiredTag'] 

920 """ 

921 permissions = ('route53-recovery-control-config:UntagResource',) 

922 

923 def get_client(self): 

924 return self.manager.get_client() 

925 

926 def process_resource_set(self, client, clusters, keys): 

927 for r in clusters: 

928 client.untag_resource( 

929 ResourceArn=r['ClusterArn'], 

930 TagKeys=keys) 

931 

932 

933@query.sources.register('describe-control-panel') 

934class DescribeControlPanel(query.ChildDescribeSource): 

935 

936 def __init__(self, manager): 

937 self.manager = manager 

938 self.query = query.ChildResourceQuery( 

939 self.manager.session_factory, self.manager) 

940 

941 def augment(self, controlpanels): 

942 for r in controlpanels: 

943 Tags = self.manager.retry( 

944 self.manager.get_client().list_tags_for_resource, 

945 ResourceArn=r['ControlPanelArn'])['Tags'] 

946 r['Tags'] = [{'Key': k, "Value": v} for k, v in Tags.items()] 

947 return controlpanels 

948 

949 

950@resources.register('recovery-control-panel') 

951class ControlPanel(query.ChildResourceManager): 

952 

953 class resource_type(query.TypeInfo): 

954 service = 'route53-recovery-control-config' 

955 arn_type = 'controlpanel' 

956 parent_spec = ('recovery-cluster', 'ClusterArn', None) 

957 enum_spec = ('list_control_panels', 'ControlPanels', None) 

958 name = id = 'ControlPanelArn' 

959 config_type = cfn_type = "AWS::Route53RecoveryControl::ControlPanel" 

960 global_resource = True 

961 

962 child_source = 'describe' 

963 source_mapping = { 

964 'describe': DescribeControlPanel, 

965 'config': query.ConfigSource 

966 } 

967 

968 def get_client(self): 

969 return local_session(self.session_factory) \ 

970 .client('route53-recovery-control-config', region_name=ARC_REGION) 

971 

972 

973@ControlPanel.action_registry.register('tag') 

974class ControlPanelAddTag(Tag): 

975 """Adds tags to a control panel 

976 

977 :example: 

978 

979 .. code-block:: yaml 

980 

981 policies: 

982 - name: control-panel-tag 

983 resource: recovery-control-panel 

984 filters: 

985 - "tag:DesiredTag": absent 

986 actions: 

987 - type: tag 

988 key: DesiredTag 

989 value: DesiredValue 

990 """ 

991 permissions = ('route53-recovery-control-config:TagResource',) 

992 

993 def get_client(self): 

994 return self.manager.get_client() 

995 

996 def process_resource_set(self, client, controlpanels, tags): 

997 Tags = {r['Key']: r['Value'] for r in tags} 

998 for r in controlpanels: 

999 client.tag_resource( 

1000 ResourceArn=r['ControlPanelArn'], 

1001 Tags=Tags) 

1002 

1003 

1004@ControlPanel.action_registry.register('remove-tag') 

1005class ControlPanelRemoveTag(RemoveTag): 

1006 """Remove tags from a control panel 

1007 

1008 :example: 

1009 

1010 .. code-block:: yaml 

1011 

1012 policies: 

1013 - name: control-panel-remove-tag 

1014 resource: recovery-control-panel 

1015 filters: 

1016 - "tag:ExpiredTag": present 

1017 actions: 

1018 - type: remove-tag 

1019 tags: ['ExpiredTag'] 

1020 """ 

1021 permissions = ('route53-recovery-control-config:UntagResource',) 

1022 

1023 def get_client(self): 

1024 return self.manager.get_client() 

1025 

1026 def process_resource_set(self, client, control_panels, keys): 

1027 for r in control_panels: 

1028 client.untag_resource( 

1029 ResourceArn=r['ControlPanelArn'], 

1030 TagKeys=keys) 

1031 

1032 

1033@ControlPanel.filter_registry.register('safety-rule') 

1034class SafeRule(ListItemFilter): 

1035 """Filter the safety rules (the assertion rules and gating rules) 

1036 that you’ve defined for the routing controls in a control panel. 

1037 

1038 

1039 :example: 

1040 

1041 find a recovery control panel with at least two deployed assertion safety rules 

1042 with a mininum of 30m wait period. 

1043 

1044 .. code-block:: yaml 

1045 

1046 policies: 

1047 - name: check-safety 

1048 resource: aws.recovery-control-panel 

1049 filters: 

1050 - type: safety-rule 

1051 count: 2 

1052 count_op: gte 

1053 attrs: 

1054 - Type: ASSERTION 

1055 - Status: Deployed 

1056 - type: value 

1057 key: WaitPeriodMs 

1058 op: gte 

1059 value: 30 

1060 """ 

1061 permissions = ('route53-recovery-control-config:ListSafetyRules',) 

1062 schema = type_schema( 

1063 'safety-rule', 

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

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

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

1067 ) 

1068 

1069 _client = None 

1070 

1071 def get_client(self): 

1072 if self._client: 

1073 return self._client 

1074 self._client = self.manager.get_client() 

1075 return self._client 

1076 

1077 def get_item_values(self, resource): 

1078 paginator = self.get_client().get_paginator('list_safety_rules') 

1079 paginator.PAGE_ITERATOR_CLASS = RetryPageIterator 

1080 rules = [] 

1081 results = paginator.paginate( 

1082 ControlPanelArn=resource['ControlPanelArn']).build_full_result().get('SafetyRules', []) 

1083 for block in results: 

1084 for rule_type, type_rule in block.items(): 

1085 type_rule['Type'] = rule_type 

1086 rules.append(type_rule) 

1087 return rules