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

470 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 functools 

4import fnmatch 

5import json 

6import itertools 

7import os 

8 

9from botocore.paginate import Paginator 

10 

11from c7n.query import QueryResourceManager, ChildResourceManager, TypeInfo, RetryPageIterator 

12from c7n.manager import resources 

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

14from c7n.actions import BaseAction 

15from c7n.filters import Filter, ListItemFilter 

16from c7n.resources.shield import IsShieldProtected, SetShieldProtection 

17from c7n.tags import RemoveTag, Tag 

18from c7n.filters.related import RelatedResourceFilter 

19from c7n import tags, query 

20from c7n.filters.iamaccess import CrossAccountAccessFilter 

21from c7n.resolver import ValuesFrom 

22 

23 

24class Route53Base: 

25 

26 permissions = ('route53:ListTagsForResources',) 

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

28 

29 @property 

30 def generate_arn(self): 

31 if self._generate_arn is None: 

32 self._generate_arn = functools.partial( 

33 generate_arn, 

34 self.get_model().service, 

35 resource_type=self.get_model().arn_type) 

36 return self._generate_arn 

37 

38 def get_arn(self, r): 

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

40 

41 def augment(self, resources): 

42 _describe_route53_tags( 

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

44 self.executor_factory, self.retry) 

45 return resources 

46 

47 

48def _describe_route53_tags( 

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

50 

51 def process_tags(resources): 

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

53 resource_map = {} 

54 for r in resources: 

55 k = r[model.id] 

56 if "hostedzone" in k: 

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

58 resource_map[k] = r 

59 

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

61 results = retry( 

62 client.list_tags_for_resources, 

63 ResourceType=model.arn_type, 

64 ResourceIds=resource_batch) 

65 for resource_tag_set in results['ResourceTagSets']: 

66 if ('ResourceId' in resource_tag_set and 

67 resource_tag_set['ResourceId'] in resource_map): 

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

69 

70 with executor_factory(max_workers=2) as w: 

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

72 

73 

74@resources.register('hostedzone') 

75class HostedZone(Route53Base, QueryResourceManager): 

76 

77 class resource_type(TypeInfo): 

78 service = 'route53' 

79 arn_type = 'hostedzone' 

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

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

82 id = 'Id' 

83 name = 'Name' 

84 config_id = 'c7n:ConfigHostedZoneId' 

85 universal_taggable = True 

86 # Denotes this resource type exists across regions 

87 global_resource = True 

88 cfn_type = 'AWS::Route53::HostedZone' 

89 

90 def get_arns(self, resource_set): 

91 arns = [] 

92 for r in resource_set: 

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

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

95 return arns 

96 

97 def augment(self, resources): 

98 annotation_key = 'c7n:ConfigHostedZoneId' 

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

100 for r in resources: 

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

102 r[annotation_key] = config_hzone_id 

103 return resources 

104 

105 

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

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

108 

109 

110@resources.register('healthcheck') 

111class HealthCheck(Route53Base, QueryResourceManager): 

112 

113 class resource_type(TypeInfo): 

114 service = 'route53' 

115 arn_type = 'healthcheck' 

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

117 name = id = 'Id' 

118 universal_taggable = True 

119 cfn_type = 'AWS::Route53::HealthCheck' 

120 global_resource = True 

121 

122 

123@resources.register('rrset') 

124class ResourceRecordSet(ChildResourceManager): 

125 

126 class resource_type(TypeInfo): 

127 service = 'route53' 

128 arn_type = 'rrset' 

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

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

131 name = id = 'Name' 

132 cfn_type = 'AWS::Route53::RecordSet' 

133 global_resource = True 

134 

135 

136@resources.register('r53domain') 

137class Route53Domain(QueryResourceManager): 

138 

139 class resource_type(TypeInfo): 

140 service = 'route53domains' 

141 arn_type = 'r53domain' 

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

143 name = id = 'DomainName' 

144 global_resource = False 

145 

146 permissions = ('route53domains:ListTagsForDomain',) 

147 

148 def augment(self, domains): 

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

150 for d in domains: 

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

152 client.list_tags_for_domain, 

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

154 return domains 

155 

156 

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

158class Route53DomainAddTag(Tag): 

159 """Adds tags to a route53 domain 

160 

161 :example: 

162 

163 .. code-block:: yaml 

164 

165 policies: 

166 - name: route53-tag 

167 resource: r53domain 

168 filters: 

169 - "tag:DesiredTag": absent 

170 actions: 

171 - type: tag 

172 key: DesiredTag 

173 value: DesiredValue 

174 """ 

175 permissions = ('route53domains:UpdateTagsForDomain',) 

176 

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

178 mid = self.manager.resource_type.id 

179 for d in domains: 

180 client.update_tags_for_domain( 

181 DomainName=d[mid], 

182 TagsToUpdate=tags) 

183 

184 

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

186class Route53DomainRemoveTag(RemoveTag): 

187 """Remove tags from a route53 domain 

188 

189 :example: 

190 

191 .. code-block:: yaml 

192 

193 policies: 

194 - name: route53-expired-tag 

195 resource: r53domain 

196 filters: 

197 - "tag:ExpiredTag": present 

198 actions: 

199 - type: remove-tag 

200 tags: ['ExpiredTag'] 

201 """ 

202 permissions = ('route53domains:DeleteTagsForDomain',) 

203 

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

205 for d in domains: 

206 client.delete_tags_for_domain( 

207 DomainName=d[self.id_key], 

208 TagsToDelete=keys) 

209 

210 

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

212class Delete(BaseAction): 

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

214 

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

216 

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

218 before deleting the zone. 

219 

220 :example: 

221 

222 .. code-block:: yaml 

223 

224 policies: 

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

226 resource: aws.hostedzone 

227 filters: 

228 - 'tag:TestTag': present 

229 actions: 

230 - type: delete 

231 force: true 

232 

233 """ 

234 

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

236 permissions = ('route53:DeleteHostedZone',) 

237 

238 def process(self, hosted_zones): 

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

240 error = None 

241 for hz in hosted_zones: 

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

243 self.delete_records(client, hz) 

244 try: 

245 self.manager.retry( 

246 client.delete_hosted_zone, 

247 Id=hz['Id'], 

248 ignore_err_codes=('NoSuchHostedZone')) 

249 except client.exceptions.HostedZoneNotEmpty as e: 

250 self.log.warning( 

251 "HostedZone: %s cannot be deleted, " 

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

253 hz['Name']) 

254 error = e 

255 if error: 

256 raise error 

257 

258 def delete_records(self, client, hz): 

259 paginator = client.get_paginator('list_resource_record_sets') 

260 paginator.PAGE_ITERATOR_CLS = RetryPageIterator 

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

262 

263 for rrset in rrsets['ResourceRecordSets']: 

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

265 # the hosted zone 

266 

267 # Exempt the two zone associated mandatory records 

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

269 continue 

270 self.manager.retry( 

271 client.change_resource_record_sets, 

272 HostedZoneId=hz['Id'], 

273 ChangeBatch={ 

274 'Changes': [ 

275 { 

276 'Action': 'DELETE', 

277 'ResourceRecordSet': { 

278 'Name': rrset['Name'], 

279 'Type': rrset['Type'], 

280 'TTL': rrset['TTL'], 

281 'ResourceRecords': rrset['ResourceRecords'] 

282 }, 

283 } 

284 ] 

285 }, 

286 ignore_err_codes=('InvalidChangeBatch')) 

287 

288 

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

290class SetQueryLogging(BaseAction): 

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

292 

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

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

295 

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

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

298 

299 This action can optionally setup the resource permissions needed for 

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

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

302 

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

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

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

306 

307 :example: 

308 

309 .. code-block:: yaml 

310 

311 policies: 

312 - name: enablednsquerylogging 

313 resource: hostedzone 

314 region: us-east-1 

315 filters: 

316 - type: query-logging-enabled 

317 state: false 

318 actions: 

319 - type: set-query-logging 

320 state: true 

321 

322 """ 

323 

324 permissions = ( 

325 'route53:GetQueryLoggingConfig', 

326 'route53:CreateQueryLoggingConfig', 

327 'route53:DeleteQueryLoggingConfig', 

328 'logs:DescribeLogGroups', 

329 'logs:CreateLogGroup', 

330 'logs:GetResourcePolicy', 

331 'logs:PutResourcePolicy') 

332 

333 schema = type_schema( 

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

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

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

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

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

339 

340 statement = { 

341 "Sid": "Route53LogsToCloudWatchLogs", 

342 "Effect": "Allow", 

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

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

345 "Resource": None} 

346 

347 def validate(self): 

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

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

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

351 # the annotation from the filter for logging config. 

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

353 f, IsQueryLoggingEnabled)]: 

354 raise ValueError( 

355 "set-query-logging when deleting requires " 

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

357 return self 

358 

359 def get_permissions(self): 

360 perms = [] 

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

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

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

364 perms.append('route53:CreateQueryLoggingConfig') 

365 perms.append('logs:CreateLogGroup') 

366 perms.append('logs:DescribeLogGroups') 

367 perms.append('tag:GetResources') 

368 else: 

369 perms.append('route53:DeleteQueryLoggingConfig') 

370 return perms 

371 

372 def process(self, resources): 

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

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

375 

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

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

378 

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

380 if state: 

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

382 

383 for r in resources: 

384 if not state: 

385 try: 

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

387 except client.exceptions.NoSuchQueryLoggingConfig: 

388 pass 

389 continue 

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

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

392 client.create_query_logging_config( 

393 HostedZoneId=r['Id'], 

394 CloudWatchLogsLogGroupArn=log_arn) 

395 

396 def get_zone_log_name(self, zone): 

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

398 log_group_name = "%s/%s" % ( 

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

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

401 else: 

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

403 return log_group_name 

404 

405 def ensure_log_groups(self, group_names): 

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

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

408 

409 if len(group_names) == 1: 

410 groups = [] 

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

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

413 else: 

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

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

416 groups = log_manager.get_resources( 

417 [common_prefix], augment=False) 

418 else: 

419 groups = list(itertools.chain(*[ 

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

421 

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

423 

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

425 client = local_session( 

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

427 

428 for g in missing: 

429 client.create_log_group(logGroupName=g) 

430 

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

432 self.ensure_route53_permissions(client, group_names) 

433 

434 def ensure_route53_permissions(self, client, group_names): 

435 if self.check_route53_permissions(client, group_names): 

436 return 

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

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

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

440 else: 

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

442 self.manager.account_id, 

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

444 

445 statement = dict(self.statement) 

446 statement['Resource'] = p_resource 

447 

448 client.put_resource_policy( 

449 policyName='Route53LogWrites', 

450 policyDocument=json.dumps( 

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

452 

453 def check_route53_permissions(self, client, group_names): 

454 group_names = set(group_names) 

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

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

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

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

459 group_names.difference_update( 

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

461 if not group_names: 

462 return True 

463 return not bool(group_names) 

464 

465 

466def get_logging_config_paginator(client): 

467 return Paginator( 

468 client.list_query_logging_configs, 

469 {'input_token': 'NextToken', 'output_token': 'NextToken', 

470 'result_key': 'QueryLoggingConfigs'}, 

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

472 

473 

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

475class IsQueryLoggingEnabled(Filter): 

476 

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

478 'logs:DescribeSubscriptionFilters') 

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

480 

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

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

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

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

485 results = [] 

486 

487 enabled_zones = { 

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

489 get_logging_config_paginator( 

490 client).paginate().build_full_result().get( 

491 'QueryLoggingConfigs', ())} 

492 for r in resources: 

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

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

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

496 continue 

497 logging = zid in enabled_zones 

498 if logging and state: 

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

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

501 response = cw_client.describe_subscription_filters(logGroupName=log_group_name) 

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

503 results.append(r) 

504 elif not logging and not state: 

505 results.append(r) 

506 

507 return results 

508 

509 

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

511class ResolverQueryLogConfig(QueryResourceManager): 

512 

513 class resource_type(TypeInfo): 

514 service = 'route53resolver' 

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

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

517 name = 'Name' 

518 id = 'Id' 

519 cfn_type = 'AWS::Route53Resolver::ResolverQueryLoggingConfig' 

520 

521 annotation_key = 'c7n:Associations' 

522 permissions = ( 

523 'route53resolver:ListResolverQueryLogConfigs', 

524 'route53resolver:ListResolverQueryLogConfigAssociations') 

525 

526 def augment(self, rqlcs): 

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

528 for rqlc in rqlcs: 

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

530 client.list_tags_for_resource, 

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

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

533 'ResolverQueryLogConfigAssociations') 

534 return rqlcs 

535 

536 

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

538class ResolverQueryLogConfigAssociate(BaseAction): 

539 """Associates ResolverQueryLogConfig to a VPC 

540 

541 :example: 

542 

543 .. code-block:: yaml 

544 

545 policies: 

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

547 resource: resolver-logs 

548 filters: 

549 - type: value 

550 key: 'Name' 

551 op: eq 

552 value: "Test-rqlc" 

553 actions: 

554 - type: associate-vpc 

555 vpcid: all 

556 

557 """ 

558 permissions = ( 

559 'route53resolver:AssociateResolverQueryLogConfig', 

560 'route53resolver:ListResolverQueryLogConfigAssociations') 

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

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

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

564 RelatedIdsExpression = 'ResourceArn' 

565 

566 def get_vpc_id(self): 

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

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

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

570 else: 

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

572 return vpc_ids 

573 

574 def is_associated(self, resource, vpc_id): 

575 associated = False 

576 for association in resource['c7n:Associations']: 

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

578 associated = True 

579 break 

580 return associated 

581 

582 def process(self, resources): 

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

584 vpc_ids = self.get_vpc_id() 

585 

586 for resource in resources: 

587 for vpc_id in vpc_ids: 

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

589 client.associate_resolver_query_log_config( 

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

591 

592 

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

594class LogConfigAssociationsFilter(Filter): 

595 """ Checks LogConfig Associations for VPCs. 

596 

597 :example: 

598 

599 .. code-block:: yaml 

600 

601 policies: 

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

603 resource: resolver-logs 

604 filters: 

605 - type: is-associated 

606 vpcid: "vpc-12345678" 

607 

608 """ 

609 permissions = ('route53resolver:ListResolverQueryLogConfigAssociations',) 

610 schema = type_schema('is-associated', 

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

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

613 RelatedIdsExpression = 'ResourceArn' 

614 

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

616 results = [] 

617 vpc_ids = ResolverQueryLogConfigAssociate.get_vpc_id(self) 

618 for resource in resources: 

619 status = True 

620 for vpc_id in vpc_ids: 

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

622 status = False 

623 break 

624 

625 if status: 

626 results.append(resource) 

627 

628 return results 

629 

630 

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

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

633# Please reference this AWS document: 

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

635ARC_REGION = 'us-west-2' 

636 

637 

638class DescribeCheck(query.DescribeSource): 

639 

640 def augment(self, readiness_checks): 

641 for r in readiness_checks: 

642 Tags = self.manager.retry( 

643 self.manager.get_client().list_tags_for_resources, 

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

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

646 return readiness_checks 

647 

648 

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

650class ReadinessCheck(QueryResourceManager): 

651 

652 class resource_type(TypeInfo): 

653 service = 'route53-recovery-readiness' 

654 arn_type = 'readiness-check' 

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

656 name = id = 'ReadinessCheckName' 

657 global_resource = True 

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

659 

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

661 

662 def get_client(self): 

663 return local_session(self.session_factory) \ 

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

665 

666 

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

668class ReadinessAddTag(Tag): 

669 """Adds tags to a readiness check 

670 

671 :example: 

672 

673 .. code-block:: yaml 

674 

675 policies: 

676 - name: readiness-tag 

677 resource: readiness-check 

678 filters: 

679 - "tag:DesiredTag": absent 

680 actions: 

681 - type: tag 

682 key: DesiredTag 

683 value: DesiredValue 

684 """ 

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

686 

687 def get_client(self): 

688 return self.manager.get_client() 

689 

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

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

692 for r in readiness_checks: 

693 client.tag_resource( 

694 ResourceArn=r['ReadinessCheckArn'], 

695 Tags=Tags) 

696 

697 

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

699class ReadinessCheckRemoveTag(RemoveTag): 

700 """Remove tags from a readiness check 

701 

702 :example: 

703 

704 .. code-block:: yaml 

705 

706 policies: 

707 - name: readiness-check-remove-tag 

708 resource: readiness-check 

709 filters: 

710 - "tag:ExpiredTag": present 

711 actions: 

712 - type: remove-tag 

713 tags: ['ExpiredTag'] 

714 """ 

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

716 

717 def get_client(self): 

718 return self.manager.get_client() 

719 

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

721 for r in readiness_checks: 

722 client.untag_resource( 

723 ResourceArn=r['ReadinessCheckArn'], 

724 TagKeys=keys) 

725 

726 

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

728class MarkForOpReadinessCheck(tags.TagDelayedAction): 

729 

730 def get_client(self): 

731 return self.manager.get_client() 

732 

733 

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

735class MarkedForOpReadinessCheck(tags.TagActionFilter): 

736 

737 def get_client(self): 

738 return self.manager.get_client() 

739 

740 

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

742class ReadinessCheckCrossAccount(CrossAccountAccessFilter): 

743 

744 schema = type_schema( 

745 'cross-account', 

746 # white list accounts 

747 whitelist_from=ValuesFrom.schema, 

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

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

750 

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

752 allowed_accounts = set(self.get_accounts()) 

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

754 

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

756 paginator.PAGE_ITERATOR_CLASS = RetryPageIterator 

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

758 

759 for arn in arns: 

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

761 if account_id not in allowed_accounts: 

762 account_ids.append(account_id) 

763 found = True 

764 if (found): 

765 for r in resources: 

766 r['c7n:CrossAccountViolations'] = account_ids 

767 results.append(r) 

768 

769 return results 

770 

771 

772 

773class DescribeCluster(query.DescribeSource): 

774 def augment(self, clusters): 

775 for r in clusters: 

776 Tags = self.manager.retry( 

777 self.manager.get_client().list_tags_for_resource, 

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

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

780 return clusters 

781 

782 

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

784class RecoveryCluster(QueryResourceManager): 

785 

786 class resource_type(TypeInfo): 

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

788 arn_type = 'cluster' 

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

790 name = id = 'ClusterArn' 

791 global_resource = True 

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

793 

794 source_mapping = { 

795 'describe': DescribeCluster, 

796 'config': query.ConfigSource 

797 } 

798 

799 def get_client(self): 

800 return local_session(self.session_factory) \ 

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

802 

803 

804 

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

806class RecoveryClusterAddTag(Tag): 

807 """Adds tags to a cluster 

808 

809 :example: 

810 

811 .. code-block:: yaml 

812 

813 policies: 

814 - name: recovery-cluster-tag 

815 resource: recovery-cluster 

816 filters: 

817 - "tag:DesiredTag": absent 

818 actions: 

819 - type: tag 

820 key: DesiredTag 

821 value: DesiredValue 

822 """ 

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

824 

825 def get_client(self): 

826 return self.manager.get_client() 

827 

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

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

830 for r in clusters: 

831 client.tag_resource( 

832 ResourceArn=r['ClusterArn'], 

833 Tags=Tags) 

834 

835 

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

837class RecoveryClusterRemoveTag(RemoveTag): 

838 """Remove tags from a cluster 

839 

840 :example: 

841 

842 

843 

844 .. code-block:: yaml 

845 

846 policies: 

847 - name: recovery-cluster-remove-tag 

848 resource: recovery-cluster 

849 filters: 

850 - "tag:ExpiredTag": present 

851 actions: 

852 - type: remove-tag 

853 tags: ['ExpiredTag'] 

854 """ 

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

856 

857 def get_client(self): 

858 return self.manager.get_client() 

859 

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

861 for r in clusters: 

862 client.untag_resource( 

863 ResourceArn=r['ClusterArn'], 

864 TagKeys=keys) 

865 

866 

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

868class DescribeControlPanel(query.ChildDescribeSource): 

869 

870 def __init__(self, manager): 

871 self.manager = manager 

872 self.query = query.ChildResourceQuery( 

873 self.manager.session_factory, self.manager) 

874 

875 def augment(self, controlpanels): 

876 for r in controlpanels: 

877 Tags = self.manager.retry( 

878 self.manager.get_client().list_tags_for_resource, 

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

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

881 return controlpanels 

882 

883 

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

885class ControlPanel(query.ChildResourceManager): 

886 

887 class resource_type(query.TypeInfo): 

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

889 arn_type = 'controlpanel' 

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

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

892 name = id = 'ControlPanelArn' 

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

894 global_resource = True 

895 

896 child_source = 'describe' 

897 source_mapping = { 

898 'describe': DescribeControlPanel, 

899 'config': query.ConfigSource 

900 } 

901 

902 def get_client(self): 

903 return local_session(self.session_factory) \ 

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

905 

906 

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

908class ControlPanelAddTag(Tag): 

909 """Adds tags to a control panel 

910 

911 :example: 

912 

913 .. code-block:: yaml 

914 

915 policies: 

916 - name: control-panel-tag 

917 resource: recovery-control-panel 

918 filters: 

919 - "tag:DesiredTag": absent 

920 actions: 

921 - type: tag 

922 key: DesiredTag 

923 value: DesiredValue 

924 """ 

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

926 

927 def get_client(self): 

928 return self.manager.get_client() 

929 

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

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

932 for r in controlpanels: 

933 client.tag_resource( 

934 ResourceArn=r['ControlPanelArn'], 

935 Tags=Tags) 

936 

937 

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

939class ControlPanelRemoveTag(RemoveTag): 

940 """Remove tags from a control panel 

941 

942 :example: 

943 

944 .. code-block:: yaml 

945 

946 policies: 

947 - name: control-panel-remove-tag 

948 resource: recovery-control-panel 

949 filters: 

950 - "tag:ExpiredTag": present 

951 actions: 

952 - type: remove-tag 

953 tags: ['ExpiredTag'] 

954 """ 

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

956 

957 def get_client(self): 

958 return self.manager.get_client() 

959 

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

961 for r in control_panels: 

962 client.untag_resource( 

963 ResourceArn=r['ControlPanelArn'], 

964 TagKeys=keys) 

965 

966 

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

968class SafeRule(ListItemFilter): 

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

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

971 

972 

973 :example: 

974 

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

976 with a mininum of 30m wait period. 

977 

978 .. code-block:: yaml 

979 

980 policies: 

981 - name: check-safety 

982 resource: aws.recovery-control-panel 

983 filters: 

984 - type: safety-rule 

985 count: 2 

986 count_op: gte 

987 attrs: 

988 - Type: ASSERTION 

989 - Status: Deployed 

990 - type: value 

991 key: WaitPeriodMs 

992 op: gte 

993 value: 30 

994 """ 

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

996 schema = type_schema( 

997 'safety-rule', 

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

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

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

1001 ) 

1002 

1003 _client = None 

1004 

1005 def get_client(self): 

1006 if self._client: 

1007 return self._client 

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

1009 return self._client 

1010 

1011 def get_item_values(self, resource): 

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

1013 paginator.PAGE_ITERATOR_CLASS = RetryPageIterator 

1014 rules = [] 

1015 results = paginator.paginate( 

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

1017 for block in results: 

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

1019 type_rule['Type'] = rule_type 

1020 rules.append(type_rule) 

1021 return rules