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

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

357 statements  

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3import re 

4 

5from c7n.actions import BaseAction 

6from c7n.filters import MetricsFilter, ShieldMetrics, Filter 

7from c7n.manager import resources 

8from c7n.query import ConfigSource, QueryResourceManager, DescribeSource, TypeInfo 

9from c7n.tags import universal_augment 

10from c7n.utils import local_session, merge_dict, type_schema, get_retry 

11from c7n.filters import ValueFilter, WafV2FilterBase 

12from .aws import shape_validate 

13from c7n.exceptions import PolicyValidationError 

14 

15from c7n.resources.aws import Arn 

16from c7n.resources.shield import IsShieldProtected, SetShieldProtection 

17from c7n.resources.securityhub import PostFinding 

18 

19 

20class DescribeDistribution(DescribeSource): 

21 

22 def augment(self, resources): 

23 return universal_augment(self.manager, resources) 

24 

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

26 results = [] 

27 distribution_ids = [] 

28 for i in ids: 

29 # if we get cloudfront distribution arn, we pick distribution id 

30 if i.startswith('arn:'): 

31 distribution_ids.append(Arn.parse(i).resource) 

32 else: 

33 distribution_ids.append(i) 

34 if distribution_ids: 

35 results = super().get_resources(distribution_ids, cache) 

36 return results 

37 

38 

39@resources.register('distribution') 

40class Distribution(QueryResourceManager): 

41 

42 class resource_type(TypeInfo): 

43 service = 'cloudfront' 

44 arn_type = 'distribution' 

45 enum_spec = ('list_distributions', 'DistributionList.Items', None) 

46 id = 'Id' 

47 arn = 'ARN' 

48 name = 'DomainName' 

49 date = 'LastModifiedTime' 

50 dimension = "DistributionId" 

51 universal_taggable = True 

52 cfn_type = config_type = "AWS::CloudFront::Distribution" 

53 # Denotes this resource type exists across regions 

54 global_resource = True 

55 permission_augment = ("cloudfront:ListTagsForResource",) 

56 

57 source_mapping = { 

58 'describe': DescribeDistribution, 

59 'config': ConfigSource 

60 } 

61 

62 

63class DescribeStreamingDistribution(DescribeSource): 

64 

65 def augment(self, resources): 

66 return universal_augment(self.manager, resources) 

67 

68 

69@resources.register('streaming-distribution') 

70class StreamingDistribution(QueryResourceManager): 

71 

72 class resource_type(TypeInfo): 

73 service = 'cloudfront' 

74 arn_type = 'streaming-distribution' 

75 enum_spec = ('list_streaming_distributions', 

76 'StreamingDistributionList.Items', 

77 None) 

78 id = 'Id' 

79 arn = 'ARN' 

80 name = 'DomainName' 

81 date = 'LastModifiedTime' 

82 dimension = "DistributionId" 

83 universal_taggable = True 

84 cfn_type = config_type = "AWS::CloudFront::StreamingDistribution" 

85 

86 source_mapping = { 

87 'describe': DescribeStreamingDistribution, 

88 'config': ConfigSource 

89 } 

90 

91 

92@resources.register("origin-access-control") 

93class OriginAccessControl(QueryResourceManager): 

94 class resource_type(TypeInfo): 

95 service = "cloudfront" 

96 arn_type = "origin-access-control" 

97 enum_spec = ( 

98 "list_origin_access_controls", 

99 "OriginAccessControlList.Items", 

100 None, 

101 ) 

102 id = "Id" 

103 description = "Description" 

104 name = "Name" 

105 signing_protocol = "SigningProtocol" 

106 signing_behavior = "SigningBehavior" 

107 origin_type = "OriginAccessControlOriginType" 

108 cfn_type = "AWS::CloudFront::OriginAccessControl" 

109 

110 

111Distribution.filter_registry.register('shield-metrics', ShieldMetrics) 

112Distribution.filter_registry.register('shield-enabled', IsShieldProtected) 

113Distribution.action_registry.register('set-shield', SetShieldProtection) 

114 

115 

116@Distribution.filter_registry.register('metrics') 

117@StreamingDistribution.filter_registry.register('metrics') 

118class DistributionMetrics(MetricsFilter): 

119 """Filter cloudfront distributions based on metric values 

120 

121 :example: 

122 

123 .. code-block:: yaml 

124 

125 policies: 

126 - name: cloudfront-distribution-errors 

127 resource: distribution 

128 filters: 

129 - type: metrics 

130 name: Requests 

131 value: 3 

132 op: ge 

133 """ 

134 

135 def get_dimensions(self, resource): 

136 return [{'Name': self.model.dimension, 

137 'Value': resource[self.model.id]}, 

138 {'Name': 'Region', 'Value': 'Global'}] 

139 

140 

141@Distribution.filter_registry.register('waf-enabled') 

142class IsWafEnabled(Filter): 

143 """Filter CloudFront distribution by waf-regional web-acl 

144 

145 :example: 

146 

147 .. code-block:: yaml 

148 

149 policies: 

150 - name: filter-distribution-waf 

151 resource: distribution 

152 filters: 

153 - type: waf-enabled 

154 state: false 

155 web-acl: test 

156 """ 

157 schema = type_schema( 

158 'waf-enabled', **{ 

159 'web-acl': {'type': 'string'}, 

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

161 

162 permissions = ('waf:ListWebACLs',) 

163 

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

165 target_acl = self.data.get('web-acl') 

166 wafs = self.manager.get_resource_manager('waf').resources() 

167 waf_name_id_map = {w['Name']: w['WebACLId'] for w in wafs} 

168 target_acl = self.data.get('web-acl') 

169 target_acl_id = waf_name_id_map.get(target_acl, target_acl) 

170 

171 if target_acl_id and target_acl_id not in waf_name_id_map.values(): 

172 raise ValueError("invalid web acl: %s" % (target_acl_id)) 

173 

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

175 results = [] 

176 for r in resources: 

177 if state and target_acl_id is None and r.get('WebACLId'): 

178 results.append(r) 

179 elif not state and target_acl_id is None and (not r.get('WebACLId') or 

180 r.get('WebACLId') not in waf_name_id_map.values()): 

181 results.append(r) 

182 elif state and target_acl_id and r['WebACLId'] == target_acl_id: 

183 results.append(r) 

184 elif not state and target_acl_id and r['WebACLId'] != target_acl_id: 

185 results.append(r) 

186 return results 

187 

188 

189@Distribution.filter_registry.register('wafv2-enabled') 

190class IsWafV2Enabled(WafV2FilterBase): 

191 """Filter CloudFront distribution by wafv2 web-acl 

192 

193 :example: 

194 

195 .. code-block:: yaml 

196 

197 policies: 

198 - name: filter-distribution-wafv2 

199 description: | 

200 match resources that are NOT associated with any wafV2 web-acls 

201 resource: distribution 

202 filters: 

203 - type: wafv2-enabled 

204 state: false 

205 

206 - name: filter-distribution-wafv2-specific-acl 

207 description: | 

208 match resources that are NOT associated with wafV2's testv2 web-acl 

209 resource: distribution 

210 filters: 

211 - type: wafv2-enabled 

212 state: false 

213 web-acl: testv2 

214 

215 - name: filter-distribution-wafv2-regex 

216 description: | 

217 match resources that are NOT associated with specified 

218 wafV2 web-acl regex 

219 resource: distribution 

220 filters: 

221 - type: wafv2-enabled 

222 state: false 

223 web-acl: .*FMManagedWebACLV2-?FMS-.* 

224 """ 

225 

226 def get_associated_web_acl(self, resource): 

227 # for WAFv2 Cloudfront stores the ARN of the WebACL even though the attribute is 'WebACLId' 

228 return self.get_web_acl_by_arn(resource.get('WebACLId'), scope='CLOUDFRONT') 

229 

230 

231class BaseDistributionConfig(ValueFilter): 

232 schema = type_schema('distribution-config', rinherit=ValueFilter.schema) 

233 schema_alias = False 

234 annotation_key = 'c7n:distribution-config' 

235 annotate = False 

236 

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

238 

239 self.augment([r for r in resources if self.annotation_key not in r]) 

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

241 

242 def __call__(self, r): 

243 return super(BaseDistributionConfig, self).__call__(r[self.annotation_key]) 

244 

245 

246@Distribution.filter_registry.register('distribution-config') 

247class DistributionConfig(BaseDistributionConfig): 

248 """Check for Cloudfront distribution config values 

249 

250 :example: 

251 

252 .. code-block:: yaml 

253 

254 policies: 

255 - name: logging-enabled 

256 resource: distribution 

257 filters: 

258 - type: distribution-config 

259 key: Logging.Enabled 

260 value: False 

261 """ 

262 permissions = ('cloudfront:GetDistributionConfig',) 

263 

264 def augment(self, resources): 

265 client = local_session(self.manager.session_factory).client( 

266 'cloudfront', region_name=self.manager.config.region) 

267 

268 for r in resources: 

269 try: 

270 r[self.annotation_key] = client.get_distribution_config(Id=r['Id']) \ 

271 .get('DistributionConfig') 

272 except (client.exceptions.NoSuchDistribution): 

273 r[self.annotation_key] = {} 

274 except Exception as e: 

275 self.log.warning( 

276 "Exception trying to get Distribution Config: %s error: %s", 

277 r['ARN'], e) 

278 raise e 

279 

280 

281@StreamingDistribution.filter_registry.register('distribution-config') 

282class StreamingDistributionConfig(BaseDistributionConfig): 

283 """Check for Cloudfront streaming distribution config values 

284 

285 :example: 

286 

287 .. code-block:: yaml 

288 

289 policies: 

290 - name: streaming-distribution-logging-enabled 

291 resource: streaming-distribution 

292 filters: 

293 - type: distribution-config 

294 key: Logging.Enabled 

295 value: true 

296 """ 

297 permissions = ('cloudfront:GetStreamingDistributionConfig',) 

298 

299 def augment(self, resources): 

300 

301 client = local_session(self.manager.session_factory).client( 

302 'cloudfront', region_name=self.manager.config.region) 

303 

304 for r in resources: 

305 try: 

306 r[self.annotation_key] = client.get_streaming_distribution_config(Id=r['Id']) \ 

307 .get('StreamingDistributionConfig') 

308 except (client.exceptions.NoSuchStreamingDistribution): 

309 r[self.annotation_key] = {} 

310 except Exception as e: 

311 self.log.warning( 

312 "Exception trying to get Streaming Distribution Config: %s error: %s", 

313 r['ARN'], e) 

314 raise e 

315 

316 

317@Distribution.filter_registry.register('mismatch-s3-origin') 

318class MismatchS3Origin(Filter): 

319 """Check for existence of S3 bucket referenced by Cloudfront, 

320 and verify whether owner is different from Cloudfront account owner. 

321 

322 :example: 

323 

324 .. code-block:: yaml 

325 

326 policies: 

327 - name: mismatch-s3-origin 

328 resource: distribution 

329 filters: 

330 - type: mismatch-s3-origin 

331 check_custom_origins: true 

332 """ 

333 

334 s3_prefix = re.compile(r'.*(?=\.s3(-.*)?(\..*-\d)?\.amazonaws.com)') 

335 s3_suffix = re.compile(r'^([^.]+\.)?s3(-.*)?(\..*-\d)?\.amazonaws.com') 

336 

337 schema = type_schema( 

338 'mismatch-s3-origin', 

339 check_custom_origins={'type': 'boolean'}) 

340 

341 permissions = ('s3:ListAllMyBuckets',) 

342 retry = staticmethod(get_retry(('Throttling',))) 

343 

344 def is_s3_domain(self, x): 

345 bucket_match = self.s3_prefix.match(x['DomainName']) 

346 

347 if bucket_match: 

348 return bucket_match.group() 

349 

350 domain_match = self.s3_suffix.match(x['DomainName']) 

351 

352 if domain_match: 

353 value = x['OriginPath'] 

354 

355 if value.startswith('/'): 

356 value = value.replace("/", "", 1) 

357 

358 return value 

359 

360 return None 

361 

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

363 results = [] 

364 

365 s3_client = local_session(self.manager.session_factory).client( 

366 's3', region_name=self.manager.config.region) 

367 

368 buckets = {b['Name'] for b in s3_client.list_buckets()['Buckets']} 

369 

370 for r in resources: 

371 r['c7n:mismatched-s3-origin'] = [] 

372 for x in r['Origins']['Items']: 

373 if 'S3OriginConfig' in x: 

374 bucket_match = self.s3_prefix.match(x['DomainName']) 

375 if bucket_match: 

376 target_bucket = self.s3_prefix.match(x['DomainName']).group() 

377 elif 'CustomOriginConfig' in x and self.data.get('check_custom_origins'): 

378 target_bucket = self.is_s3_domain(x) 

379 

380 if target_bucket is not None and target_bucket not in buckets: 

381 self.log.debug("Bucket %s not found in distribution %s hosting account." 

382 % (target_bucket, r['Id'])) 

383 r['c7n:mismatched-s3-origin'].append(target_bucket) 

384 results.append(r) 

385 

386 return results 

387 

388 

389@Distribution.action_registry.register('post-finding') 

390class DistributionPostFinding(PostFinding): 

391 

392 resource_type = 'AwsCloudFrontDistribution' 

393 

394 def format_resource(self, r): 

395 envelope, payload = self.format_envelope(r) 

396 origins = r['Origins'] 

397 

398 payload.update(self.filter_empty({ 

399 'DomainName': r['DomainName'], 

400 "WebAclId": r.get('WebACLId'), 

401 'LastModifiedTime': r['LastModifiedTime'].isoformat(), 

402 'Status': r['Status'], 

403 'Logging': self.filter_empty(r.get('Logging', {})), 

404 'Origins': { 

405 'Items': [ 

406 { 

407 # Extract a subset of origin item keys, 

408 # only if they're non-empty. 

409 # 

410 # The full item can be large and noisy, and 

411 # empty string values (notably for OriginPath) 

412 # will fail validation. 

413 k: o[k] 

414 for k in self.filter_empty(o) 

415 if k in ('Id', 'OriginPath', 'DomainName') 

416 } 

417 for o in origins['Items'] 

418 ] 

419 } 

420 })) 

421 

422 return envelope 

423 

424 

425@Distribution.action_registry.register('set-waf') 

426class SetWaf(BaseAction): 

427 """Enable waf protection on CloudFront distribution. 

428 

429 :example: 

430 

431 .. code-block:: yaml 

432 

433 policies: 

434 - name: set-waf-for-cloudfront 

435 resource: distribution 

436 filters: 

437 - type: waf-enabled 

438 state: false 

439 web-acl: test 

440 actions: 

441 - type: set-waf 

442 state: true 

443 force: true 

444 web-acl: test 

445 

446 - name: disassociate-waf-associate-wafv2-cf 

447 resource: distribution 

448 filters: 

449 - type: waf-enabled 

450 state: true 

451 actions: 

452 - type: set-wafv2 

453 state: true 

454 force: true 

455 web-acl: testv2 

456 

457 """ 

458 permissions = ('cloudfront:UpdateDistribution', 'waf:ListWebACLs') 

459 schema = type_schema( 

460 'set-waf', required=['web-acl'], **{ 

461 'web-acl': {'type': 'string'}, 

462 'force': {'type': 'boolean'}, 

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

464 

465 retry = staticmethod(get_retry(('Throttling',))) 

466 

467 def process(self, resources): 

468 wafs = self.manager.get_resource_manager('waf').resources() 

469 waf_name_id_map = {w['Name']: w['WebACLId'] for w in wafs} 

470 target_acl = self.data.get('web-acl') 

471 target_acl_id = waf_name_id_map.get(target_acl, target_acl) 

472 

473 if target_acl_id not in waf_name_id_map.values(): 

474 raise ValueError("invalid web acl: %s" % (target_acl_id)) 

475 

476 client = local_session(self.manager.session_factory).client( 

477 'cloudfront') 

478 force = self.data.get('force', False) 

479 

480 for r in resources: 

481 if r.get('WebACLId') and not force: 

482 continue 

483 if r.get('WebACLId') == target_acl_id: 

484 continue 

485 result = client.get_distribution_config(Id=r['Id']) 

486 config = result['DistributionConfig'] 

487 config['WebACLId'] = target_acl_id 

488 self.retry( 

489 client.update_distribution, 

490 Id=r['Id'], DistributionConfig=config, IfMatch=result['ETag']) 

491 

492 

493@Distribution.action_registry.register('set-wafv2') 

494class SetWafv2(BaseAction): 

495 """Enable wafv2 protection on CloudFront distribution. 

496 

497 :example: 

498 

499 .. code-block:: yaml 

500 

501 policies: 

502 - name: set-wafv2-for-cloudfront 

503 resource: distribution 

504 filters: 

505 - type: wafv2-enabled 

506 state: false 

507 web-acl: testv2 

508 actions: 

509 - type: set-wafv2 

510 state: true 

511 force: true 

512 web-acl: testv2 

513 

514 - name: disassociate-wafv2-associate-waf-cf 

515 resource: distribution 

516 filters: 

517 - type: wafv2-enabled 

518 state: true 

519 actions: 

520 - type: set-waf 

521 state: true 

522 force: true 

523 web-acl: test 

524 

525 policies: 

526 - name: set-wafv2-for-cloudfront-regex 

527 resource: distribution 

528 filters: 

529 - type: wafv2-enabled 

530 state: false 

531 web-acl: .*FMManagedWebACLV2-?FMS-.* 

532 actions: 

533 - type: set-wafv2 

534 state: true 

535 web-acl: FMManagedWebACLV2-?FMS-TestWebACL 

536 """ 

537 permissions = ('cloudfront:UpdateDistribution', 'wafv2:ListWebACLs') 

538 schema = type_schema( 

539 'set-wafv2', **{ 

540 'web-acl': {'type': 'string'}, 

541 'force': {'type': 'boolean'}, 

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

543 

544 retry = staticmethod(get_retry(('Throttling',))) 

545 

546 def process(self, resources): 

547 query = {'Scope': 'CLOUDFRONT'} 

548 wafs = self.manager.get_resource_manager('wafv2').resources(query, augment=False) 

549 waf_name_id_map = {w['Name']: w['ARN'] for w in wafs} 

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

551 

552 target_acl_id = '' 

553 if state: 

554 target_acl = self.data.get('web-acl', '') 

555 target_acl_ids = [v for k, v in waf_name_id_map.items() if 

556 re.match(target_acl, k)] 

557 if len(target_acl_ids) != 1: 

558 raise ValueError(f'{target_acl} matching to none or ' 

559 f'multiple webacls') 

560 target_acl_id = target_acl_ids[0] 

561 

562 client = local_session(self.manager.session_factory).client('cloudfront') 

563 force = self.data.get('force', False) 

564 

565 for r in resources: 

566 if r.get('WebACLId') and not force: 

567 continue 

568 if r.get('WebACLId') == target_acl_id: 

569 continue 

570 result = client.get_distribution_config(Id=r['Id']) 

571 config = result['DistributionConfig'] 

572 config['WebACLId'] = target_acl_id 

573 self.retry( 

574 client.update_distribution, 

575 Id=r['Id'], DistributionConfig=config, IfMatch=result['ETag']) 

576 

577 

578@Distribution.action_registry.register('disable') 

579class DistributionDisableAction(BaseAction): 

580 """Action to disable a Distribution 

581 

582 :example: 

583 

584 .. code-block:: yaml 

585 

586 policies: 

587 - name: distribution-delete 

588 resource: distribution 

589 filters: 

590 - type: value 

591 key: CacheBehaviors.Items[].ViewerProtocolPolicy 

592 value: allow-all 

593 op: contains 

594 actions: 

595 - type: disable 

596 """ 

597 schema = type_schema('disable') 

598 permissions = ("cloudfront:GetDistributionConfig", 

599 "cloudfront:UpdateDistribution",) 

600 

601 def process(self, distributions): 

602 client = local_session( 

603 self.manager.session_factory).client(self.manager.get_model().service) 

604 

605 for d in distributions: 

606 self.process_distribution(client, d) 

607 

608 def process_distribution(self, client, distribution): 

609 try: 

610 res = client.get_distribution_config( 

611 Id=distribution[self.manager.get_model().id]) 

612 res['DistributionConfig']['Enabled'] = False 

613 res = client.update_distribution( 

614 Id=distribution[self.manager.get_model().id], 

615 IfMatch=res['ETag'], 

616 DistributionConfig=res['DistributionConfig'] 

617 ) 

618 except Exception as e: 

619 self.log.warning( 

620 "Exception trying to disable Distribution: %s error: %s", 

621 distribution['ARN'], e) 

622 return 

623 

624 

625@StreamingDistribution.action_registry.register('disable') 

626class StreamingDistributionDisableAction(BaseAction): 

627 """Action to disable a Streaming Distribution 

628 

629 :example: 

630 

631 .. code-block:: yaml 

632 

633 policies: 

634 - name: streaming-distribution-delete 

635 resource: streaming-distribution 

636 filters: 

637 - type: value 

638 key: S3Origin.OriginAccessIdentity 

639 value: '' 

640 actions: 

641 - type: disable 

642 """ 

643 schema = type_schema('disable') 

644 

645 permissions = ("cloudfront:GetStreamingDistributionConfig", 

646 "cloudfront:UpdateStreamingDistribution",) 

647 

648 def process(self, distributions): 

649 client = local_session( 

650 self.manager.session_factory).client(self.manager.get_model().service) 

651 for d in distributions: 

652 self.process_distribution(client, d) 

653 

654 def process_distribution(self, client, distribution): 

655 try: 

656 res = client.get_streaming_distribution_config( 

657 Id=distribution[self.manager.get_model().id]) 

658 res['StreamingDistributionConfig']['Enabled'] = False 

659 res = client.update_streaming_distribution( 

660 Id=distribution[self.manager.get_model().id], 

661 IfMatch=res['ETag'], 

662 StreamingDistributionConfig=res['StreamingDistributionConfig'] 

663 ) 

664 except Exception as e: 

665 self.log.warning( 

666 "Exception trying to disable Distribution: %s error: %s", 

667 distribution['ARN'], e) 

668 return 

669 

670 

671@Distribution.action_registry.register('set-protocols') 

672class DistributionSSLAction(BaseAction): 

673 """Action to set mandatory https-only on a Distribution 

674 

675 :example: 

676 

677 .. code-block:: yaml 

678 

679 policies: 

680 - name: distribution-set-ssl 

681 resource: distribution 

682 filters: 

683 - type: value 

684 key: CacheBehaviors.Items[].ViewerProtocolPolicy 

685 value: allow-all 

686 op: contains 

687 actions: 

688 - type: set-protocols 

689 ViewerProtocolPolicy: https-only 

690 """ 

691 schema = { 

692 'type': 'object', 

693 'additionalProperties': False, 

694 'properties': { 

695 'type': {'enum': ['set-protocols']}, 

696 'OriginProtocolPolicy': { 

697 'enum': ['http-only', 'match-viewer', 'https-only'] 

698 }, 

699 'OriginSslProtocols': { 

700 'type': 'array', 

701 'items': {'enum': ['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2']} 

702 }, 

703 'ViewerProtocolPolicy': { 

704 'enum': ['allow-all', 'https-only', 'redirect-to-https'] 

705 } 

706 } 

707 } 

708 

709 permissions = ("cloudfront:GetDistributionConfig", 

710 "cloudfront:UpdateDistribution",) 

711 

712 def process(self, distributions): 

713 client = local_session(self.manager.session_factory).client( 

714 self.manager.get_model().service) 

715 for d in distributions: 

716 self.process_distribution(client, d) 

717 

718 def process_distribution(self, client, distribution): 

719 try: 

720 res = client.get_distribution_config( 

721 Id=distribution[self.manager.get_model().id]) 

722 etag = res['ETag'] 

723 dc = res['DistributionConfig'] 

724 

725 for item in dc['CacheBehaviors'].get('Items', []): 

726 item['ViewerProtocolPolicy'] = self.data.get( 

727 'ViewerProtocolPolicy', 

728 item['ViewerProtocolPolicy']) 

729 dc['DefaultCacheBehavior']['ViewerProtocolPolicy'] = self.data.get( 

730 'ViewerProtocolPolicy', 

731 dc['DefaultCacheBehavior']['ViewerProtocolPolicy']) 

732 

733 for item in dc['Origins'].get('Items', []): 

734 if item.get('CustomOriginConfig', False): 

735 item['CustomOriginConfig']['OriginProtocolPolicy'] = self.data.get( 

736 'OriginProtocolPolicy', 

737 item['CustomOriginConfig']['OriginProtocolPolicy']) 

738 

739 item['CustomOriginConfig']['OriginSslProtocols']['Items'] = self.data.get( 

740 'OriginSslProtocols', 

741 item['CustomOriginConfig']['OriginSslProtocols']['Items']) 

742 

743 item['CustomOriginConfig']['OriginSslProtocols']['Quantity'] = len( 

744 item['CustomOriginConfig']['OriginSslProtocols']['Items']) 

745 

746 res = client.update_distribution( 

747 Id=distribution[self.manager.get_model().id], 

748 IfMatch=etag, 

749 DistributionConfig=dc 

750 ) 

751 except Exception as e: 

752 self.log.warning( 

753 "Exception trying to force ssl on Distribution: %s error: %s", 

754 distribution['ARN'], e) 

755 return 

756 

757 

758class BaseUpdateAction(BaseAction): 

759 schema = type_schema('set-attributes', 

760 attributes={"type": "object"}, 

761 required=('attributes',)) 

762 schema_alias = False 

763 

764 def validate(self, config_name, shape): 

765 attrs = dict(self.data.get('attributes')) 

766 if attrs.get('CallerReference'): 

767 raise PolicyValidationError('CallerReference field cannot be updated') 

768 

769 # Set default values for required fields if they are not present 

770 attrs["CallerReference"] = "" 

771 config = self.validation_config 

772 updatedConfig = {**config, **attrs} 

773 

774 request = { 

775 config_name: updatedConfig, 

776 "Id": "sample_id", 

777 "IfMatch": "sample_string", 

778 } 

779 return shape_validate(request, shape, 'cloudfront') 

780 

781 def process(self, distributions): 

782 client = local_session(self.manager.session_factory).client( 

783 self.manager.get_model().service) 

784 for d in distributions: 

785 self.process_distribution(client, d) 

786 

787 

788@Distribution.action_registry.register('set-attributes') 

789class DistributionUpdateAction(BaseUpdateAction): 

790 """Action to update the attributes of a distribution 

791 

792 :example: 

793 

794 .. code-block:: yaml 

795 

796 policies: 

797 - name: enforce-distribution-logging 

798 resource: distribution 

799 filters: 

800 - type: value 

801 key: "Logging.Enabled" 

802 value: null 

803 actions: 

804 - type: set-attributes 

805 attributes: 

806 Comment: "" 

807 Enabled: true 

808 Logging: 

809 Enabled: true 

810 IncludeCookies: false 

811 Bucket: 'test-enable-logging-c7n.s3.amazonaws.com' 

812 Prefix: '' 

813 """ 

814 permissions = ("cloudfront:UpdateDistribution", 

815 "cloudfront:GetDistributionConfig",) 

816 shape = 'UpdateDistributionRequest' 

817 validation_config = { 

818 'Origins': { 

819 'Quantity': 0, 

820 'Items': [{ 

821 'Id': '', 

822 'DomainName': '' 

823 }] 

824 }, 

825 'DefaultCacheBehavior': { 

826 'TargetOriginId': '', 

827 'ForwardedValues': { 

828 'QueryString': True, 

829 'Cookies': { 

830 'Forward': '' 

831 } 

832 }, 

833 'TrustedSigners': { 

834 'Enabled': True, 

835 'Quantity': 0 

836 }, 

837 'ViewerProtocolPolicy': '', 

838 'MinTTL': 0 

839 }, 

840 'Comment': '', 

841 'Enabled': False 

842 } 

843 

844 def validate(self): 

845 return super().validate('DistributionConfig', self.shape) 

846 

847 def process_distribution(self, client, distribution): 

848 try: 

849 res = client.get_distribution_config( 

850 Id=distribution[self.manager.get_model().id]) 

851 default_config = self.validation_config 

852 config = {**default_config, **res['DistributionConfig']} 

853 

854 # Recursively merge config to allow piecemeal updates of 

855 # nested structures 

856 updatedConfig = merge_dict(config, self.data['attributes']) 

857 if config == updatedConfig: 

858 return 

859 res = client.update_distribution( 

860 Id=distribution[self.manager.get_model().id], 

861 IfMatch=res['ETag'], 

862 DistributionConfig=updatedConfig 

863 ) 

864 except (client.exceptions.NoSuchDistribution): 

865 pass 

866 except Exception as e: 

867 self.log.warning( 

868 "Exception trying to update Distribution: %s error: %s", 

869 distribution['ARN'], e) 

870 raise e 

871 

872 

873StreamingDistribution.filter_registry.register('shield-enabled', IsShieldProtected) 

874StreamingDistribution.action_registry.register('set-shield', SetShieldProtection) 

875 

876 

877@StreamingDistribution.action_registry.register('set-attributes') 

878class StreamingDistributionUpdateAction(BaseUpdateAction): 

879 """Action to update the attributes of a distribution 

880 

881 :example: 

882 

883 .. code-block:: yaml 

884 

885 policies: 

886 - name: enforce-streaming-distribution-logging 

887 resource: streaming-distribution 

888 filters: 

889 - type: value 

890 key: "Logging.Enabled" 

891 value: false 

892 actions: 

893 - type: set-attributes 

894 attributes: 

895 Logging: 

896 Enabled: true 

897 Bucket: 'test-enable-logging-c7n.s3.amazonaws.com' 

898 Prefix: '' 

899 """ 

900 permissions = ("cloudfront:UpdateStreamingDistribution", 

901 "cloudfront:GetStreamingDistributionConfig",) 

902 shape = 'UpdateStreamingDistributionRequest' 

903 validation_config = { 

904 'S3Origin': { 

905 'DomainName': 'domain_name', 

906 'OriginAccessIdentity': 'origin_access_identity' 

907 }, 

908 'TrustedSigners': { 

909 'Enabled': False, 

910 'Quantity': 0 

911 }, 

912 'Comment': '', 

913 'Enabled': False 

914 } 

915 

916 def validate(self): 

917 return super().validate('StreamingDistributionConfig', self.shape) 

918 

919 def process_distribution(self, client, streaming_distribution): 

920 try: 

921 res = client.get_streaming_distribution_config( 

922 Id=streaming_distribution[self.manager.get_model().id]) 

923 default_config = self.validation_config 

924 config = {**default_config, **res['StreamingDistributionConfig']} 

925 updatedConfig = {**config, **self.data['attributes']} 

926 if config == updatedConfig: 

927 return 

928 res = client.update_streaming_distribution( 

929 Id=streaming_distribution[self.manager.get_model().id], 

930 IfMatch=res['ETag'], 

931 StreamingDistributionConfig=updatedConfig 

932 ) 

933 except (client.exceptions.NoSuchStreamingDistribution): 

934 pass 

935 except Exception as e: 

936 self.log.warning( 

937 "Exception trying to update Streaming Distribution: %s error: %s", 

938 streaming_distribution['ARN'], e) 

939 raise e