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

342 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 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 

56 source_mapping = { 

57 'describe': DescribeDistribution, 

58 'config': ConfigSource 

59 } 

60 

61 

62class DescribeStreamingDistribution(DescribeSource): 

63 

64 def augment(self, resources): 

65 return universal_augment(self.manager, resources) 

66 

67 

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

69class StreamingDistribution(QueryResourceManager): 

70 

71 class resource_type(TypeInfo): 

72 service = 'cloudfront' 

73 arn_type = 'streaming-distribution' 

74 enum_spec = ('list_streaming_distributions', 

75 'StreamingDistributionList.Items', 

76 None) 

77 id = 'Id' 

78 arn = 'ARN' 

79 name = 'DomainName' 

80 date = 'LastModifiedTime' 

81 dimension = "DistributionId" 

82 universal_taggable = True 

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

84 

85 source_mapping = { 

86 'describe': DescribeStreamingDistribution, 

87 'config': ConfigSource 

88 } 

89 

90 

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

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

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

94 

95 

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

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

98class DistributionMetrics(MetricsFilter): 

99 """Filter cloudfront distributions based on metric values 

100 

101 :example: 

102 

103 .. code-block:: yaml 

104 

105 policies: 

106 - name: cloudfront-distribution-errors 

107 resource: distribution 

108 filters: 

109 - type: metrics 

110 name: Requests 

111 value: 3 

112 op: ge 

113 """ 

114 

115 def get_dimensions(self, resource): 

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

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

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

119 

120 

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

122class IsWafEnabled(Filter): 

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

124 

125 :example: 

126 

127 .. code-block:: yaml 

128 

129 policies: 

130 - name: filter-distribution-waf 

131 resource: distribution 

132 filters: 

133 - type: waf-enabled 

134 state: false 

135 web-acl: test 

136 """ 

137 schema = type_schema( 

138 'waf-enabled', **{ 

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

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

141 

142 permissions = ('waf:ListWebACLs',) 

143 

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

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

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

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

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

149 target_acl_id = waf_name_id_map.get(target_acl, target_acl) 

150 

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

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

153 

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

155 results = [] 

156 for r in resources: 

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

158 results.append(r) 

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

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

161 results.append(r) 

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

163 results.append(r) 

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

165 results.append(r) 

166 return results 

167 

168 

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

170class IsWafV2Enabled(WafV2FilterBase): 

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

172 

173 :example: 

174 

175 .. code-block:: yaml 

176 

177 policies: 

178 - name: filter-distribution-wafv2 

179 description: | 

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

181 resource: distribution 

182 filters: 

183 - type: wafv2-enabled 

184 state: false 

185 

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

187 description: | 

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

189 resource: distribution 

190 filters: 

191 - type: wafv2-enabled 

192 state: false 

193 web-acl: testv2 

194 

195 - name: filter-distribution-wafv2-regex 

196 description: | 

197 match resources that are NOT associated with specified 

198 wafV2 web-acl regex 

199 resource: distribution 

200 filters: 

201 - type: wafv2-enabled 

202 state: false 

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

204 """ 

205 

206 def get_associated_web_acl(self, resource): 

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

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

209 

210 

211class BaseDistributionConfig(ValueFilter): 

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

213 schema_alias = False 

214 annotation_key = 'c7n:distribution-config' 

215 annotate = False 

216 

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

218 

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

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

221 

222 def __call__(self, r): 

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

224 

225 

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

227class DistributionConfig(BaseDistributionConfig): 

228 """Check for Cloudfront distribution config values 

229 

230 :example: 

231 

232 .. code-block:: yaml 

233 

234 policies: 

235 - name: logging-enabled 

236 resource: distribution 

237 filters: 

238 - type: distribution-config 

239 key: Logging.Enabled 

240 value: False 

241 """ 

242 permissions = ('cloudfront:GetDistributionConfig',) 

243 

244 def augment(self, resources): 

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

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

247 

248 for r in resources: 

249 try: 

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

251 .get('DistributionConfig') 

252 except (client.exceptions.NoSuchDistribution): 

253 r[self.annotation_key] = {} 

254 except Exception as e: 

255 self.log.warning( 

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

257 r['ARN'], e) 

258 raise e 

259 

260 

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

262class StreamingDistributionConfig(BaseDistributionConfig): 

263 """Check for Cloudfront streaming distribution config values 

264 

265 :example: 

266 

267 .. code-block:: yaml 

268 

269 policies: 

270 - name: streaming-distribution-logging-enabled 

271 resource: streaming-distribution 

272 filters: 

273 - type: distribution-config 

274 key: Logging.Enabled 

275 value: true 

276 """ 

277 permissions = ('cloudfront:GetStreamingDistributionConfig',) 

278 

279 def augment(self, resources): 

280 

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

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

283 

284 for r in resources: 

285 try: 

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

287 .get('StreamingDistributionConfig') 

288 except (client.exceptions.NoSuchStreamingDistribution): 

289 r[self.annotation_key] = {} 

290 except Exception as e: 

291 self.log.warning( 

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

293 r['ARN'], e) 

294 raise e 

295 

296 

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

298class MismatchS3Origin(Filter): 

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

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

301 

302 :example: 

303 

304 .. code-block:: yaml 

305 

306 policies: 

307 - name: mismatch-s3-origin 

308 resource: distribution 

309 filters: 

310 - type: mismatch-s3-origin 

311 check_custom_origins: true 

312 """ 

313 

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

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

316 

317 schema = type_schema( 

318 'mismatch-s3-origin', 

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

320 

321 permissions = ('s3:ListAllMyBuckets',) 

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

323 

324 def is_s3_domain(self, x): 

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

326 

327 if bucket_match: 

328 return bucket_match.group() 

329 

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

331 

332 if domain_match: 

333 value = x['OriginPath'] 

334 

335 if value.startswith('/'): 

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

337 

338 return value 

339 

340 return None 

341 

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

343 results = [] 

344 

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

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

347 

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

349 

350 for r in resources: 

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

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

353 if 'S3OriginConfig' in x: 

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

355 if bucket_match: 

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

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

358 target_bucket = self.is_s3_domain(x) 

359 

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

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

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

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

364 results.append(r) 

365 

366 return results 

367 

368 

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

370class DistributionPostFinding(PostFinding): 

371 

372 resource_type = 'AwsCloudFrontDistribution' 

373 

374 def format_resource(self, r): 

375 envelope, payload = self.format_envelope(r) 

376 origins = r['Origins'] 

377 

378 payload.update(self.filter_empty({ 

379 'DomainName': r['DomainName'], 

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

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

382 'Status': r['Status'], 

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

384 'Origins': { 

385 'Items': [ 

386 { 

387 # Extract a subset of origin item keys, 

388 # only if they're non-empty. 

389 # 

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

391 # empty string values (notably for OriginPath) 

392 # will fail validation. 

393 k: o[k] 

394 for k in self.filter_empty(o) 

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

396 } 

397 for o in origins['Items'] 

398 ] 

399 } 

400 })) 

401 

402 return envelope 

403 

404 

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

406class SetWaf(BaseAction): 

407 """Enable waf protection on CloudFront distribution. 

408 

409 :example: 

410 

411 .. code-block:: yaml 

412 

413 policies: 

414 - name: set-waf-for-cloudfront 

415 resource: distribution 

416 filters: 

417 - type: waf-enabled 

418 state: false 

419 web-acl: test 

420 actions: 

421 - type: set-waf 

422 state: true 

423 force: true 

424 web-acl: test 

425 

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

427 resource: distribution 

428 filters: 

429 - type: waf-enabled 

430 state: true 

431 actions: 

432 - type: set-wafv2 

433 state: true 

434 force: true 

435 web-acl: testv2 

436 

437 """ 

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

439 schema = type_schema( 

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

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

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

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

444 

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

446 

447 def process(self, resources): 

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

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

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

451 target_acl_id = waf_name_id_map.get(target_acl, target_acl) 

452 

453 if target_acl_id not in waf_name_id_map.values(): 

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

455 

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

457 'cloudfront') 

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

459 

460 for r in resources: 

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

462 continue 

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

464 continue 

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

466 config = result['DistributionConfig'] 

467 config['WebACLId'] = target_acl_id 

468 self.retry( 

469 client.update_distribution, 

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

471 

472 

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

474class SetWafv2(BaseAction): 

475 """Enable wafv2 protection on CloudFront distribution. 

476 

477 :example: 

478 

479 .. code-block:: yaml 

480 

481 policies: 

482 - name: set-wafv2-for-cloudfront 

483 resource: distribution 

484 filters: 

485 - type: wafv2-enabled 

486 state: false 

487 web-acl: testv2 

488 actions: 

489 - type: set-wafv2 

490 state: true 

491 force: true 

492 web-acl: testv2 

493 

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

495 resource: distribution 

496 filters: 

497 - type: wafv2-enabled 

498 state: true 

499 actions: 

500 - type: set-waf 

501 state: true 

502 force: true 

503 web-acl: test 

504 

505 policies: 

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

507 resource: distribution 

508 filters: 

509 - type: wafv2-enabled 

510 state: false 

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

512 actions: 

513 - type: set-wafv2 

514 state: true 

515 web-acl: FMManagedWebACLV2-?FMS-TestWebACL 

516 """ 

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

518 schema = type_schema( 

519 'set-wafv2', **{ 

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

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

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

523 

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

525 

526 def process(self, resources): 

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

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

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

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

531 

532 target_acl_id = '' 

533 if state: 

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

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

536 re.match(target_acl, k)] 

537 if len(target_acl_ids) != 1: 

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

539 f'multiple webacls') 

540 target_acl_id = target_acl_ids[0] 

541 

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

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

544 

545 for r in resources: 

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

547 continue 

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

549 continue 

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

551 config = result['DistributionConfig'] 

552 config['WebACLId'] = target_acl_id 

553 self.retry( 

554 client.update_distribution, 

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

556 

557 

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

559class DistributionDisableAction(BaseAction): 

560 """Action to disable a Distribution 

561 

562 :example: 

563 

564 .. code-block:: yaml 

565 

566 policies: 

567 - name: distribution-delete 

568 resource: distribution 

569 filters: 

570 - type: value 

571 key: CacheBehaviors.Items[].ViewerProtocolPolicy 

572 value: allow-all 

573 op: contains 

574 actions: 

575 - type: disable 

576 """ 

577 schema = type_schema('disable') 

578 permissions = ("cloudfront:GetDistributionConfig", 

579 "cloudfront:UpdateDistribution",) 

580 

581 def process(self, distributions): 

582 client = local_session( 

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

584 

585 for d in distributions: 

586 self.process_distribution(client, d) 

587 

588 def process_distribution(self, client, distribution): 

589 try: 

590 res = client.get_distribution_config( 

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

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

593 res = client.update_distribution( 

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

595 IfMatch=res['ETag'], 

596 DistributionConfig=res['DistributionConfig'] 

597 ) 

598 except Exception as e: 

599 self.log.warning( 

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

601 distribution['ARN'], e) 

602 return 

603 

604 

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

606class StreamingDistributionDisableAction(BaseAction): 

607 """Action to disable a Streaming Distribution 

608 

609 :example: 

610 

611 .. code-block:: yaml 

612 

613 policies: 

614 - name: streaming-distribution-delete 

615 resource: streaming-distribution 

616 filters: 

617 - type: value 

618 key: S3Origin.OriginAccessIdentity 

619 value: '' 

620 actions: 

621 - type: disable 

622 """ 

623 schema = type_schema('disable') 

624 

625 permissions = ("cloudfront:GetStreamingDistributionConfig", 

626 "cloudfront:UpdateStreamingDistribution",) 

627 

628 def process(self, distributions): 

629 client = local_session( 

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

631 for d in distributions: 

632 self.process_distribution(client, d) 

633 

634 def process_distribution(self, client, distribution): 

635 try: 

636 res = client.get_streaming_distribution_config( 

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

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

639 res = client.update_streaming_distribution( 

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

641 IfMatch=res['ETag'], 

642 StreamingDistributionConfig=res['StreamingDistributionConfig'] 

643 ) 

644 except Exception as e: 

645 self.log.warning( 

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

647 distribution['ARN'], e) 

648 return 

649 

650 

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

652class DistributionSSLAction(BaseAction): 

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

654 

655 :example: 

656 

657 .. code-block:: yaml 

658 

659 policies: 

660 - name: distribution-set-ssl 

661 resource: distribution 

662 filters: 

663 - type: value 

664 key: CacheBehaviors.Items[].ViewerProtocolPolicy 

665 value: allow-all 

666 op: contains 

667 actions: 

668 - type: set-protocols 

669 ViewerProtocolPolicy: https-only 

670 """ 

671 schema = { 

672 'type': 'object', 

673 'additionalProperties': False, 

674 'properties': { 

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

676 'OriginProtocolPolicy': { 

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

678 }, 

679 'OriginSslProtocols': { 

680 'type': 'array', 

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

682 }, 

683 'ViewerProtocolPolicy': { 

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

685 } 

686 } 

687 } 

688 

689 permissions = ("cloudfront:GetDistributionConfig", 

690 "cloudfront:UpdateDistribution",) 

691 

692 def process(self, distributions): 

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

694 self.manager.get_model().service) 

695 for d in distributions: 

696 self.process_distribution(client, d) 

697 

698 def process_distribution(self, client, distribution): 

699 try: 

700 res = client.get_distribution_config( 

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

702 etag = res['ETag'] 

703 dc = res['DistributionConfig'] 

704 

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

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

707 'ViewerProtocolPolicy', 

708 item['ViewerProtocolPolicy']) 

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

710 'ViewerProtocolPolicy', 

711 dc['DefaultCacheBehavior']['ViewerProtocolPolicy']) 

712 

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

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

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

716 'OriginProtocolPolicy', 

717 item['CustomOriginConfig']['OriginProtocolPolicy']) 

718 

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

720 'OriginSslProtocols', 

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

722 

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

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

725 

726 res = client.update_distribution( 

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

728 IfMatch=etag, 

729 DistributionConfig=dc 

730 ) 

731 except Exception as e: 

732 self.log.warning( 

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

734 distribution['ARN'], e) 

735 return 

736 

737 

738class BaseUpdateAction(BaseAction): 

739 schema = type_schema('set-attributes', 

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

741 required=('attributes',)) 

742 schema_alias = False 

743 

744 def validate(self, config_name, shape): 

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

746 if attrs.get('CallerReference'): 

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

748 

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

750 attrs["CallerReference"] = "" 

751 config = self.validation_config 

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

753 

754 request = { 

755 config_name: updatedConfig, 

756 "Id": "sample_id", 

757 "IfMatch": "sample_string", 

758 } 

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

760 

761 def process(self, distributions): 

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

763 self.manager.get_model().service) 

764 for d in distributions: 

765 self.process_distribution(client, d) 

766 

767 

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

769class DistributionUpdateAction(BaseUpdateAction): 

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

771 

772 :example: 

773 

774 .. code-block:: yaml 

775 

776 policies: 

777 - name: enforce-distribution-logging 

778 resource: distribution 

779 filters: 

780 - type: value 

781 key: "Logging.Enabled" 

782 value: null 

783 actions: 

784 - type: set-attributes 

785 attributes: 

786 Comment: "" 

787 Enabled: true 

788 Logging: 

789 Enabled: true 

790 IncludeCookies: false 

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

792 Prefix: '' 

793 """ 

794 permissions = ("cloudfront:UpdateDistribution", 

795 "cloudfront:GetDistributionConfig",) 

796 shape = 'UpdateDistributionRequest' 

797 validation_config = { 

798 'Origins': { 

799 'Quantity': 0, 

800 'Items': [{ 

801 'Id': '', 

802 'DomainName': '' 

803 }] 

804 }, 

805 'DefaultCacheBehavior': { 

806 'TargetOriginId': '', 

807 'ForwardedValues': { 

808 'QueryString': True, 

809 'Cookies': { 

810 'Forward': '' 

811 } 

812 }, 

813 'TrustedSigners': { 

814 'Enabled': True, 

815 'Quantity': 0 

816 }, 

817 'ViewerProtocolPolicy': '', 

818 'MinTTL': 0 

819 }, 

820 'Comment': '', 

821 'Enabled': False 

822 } 

823 

824 def validate(self): 

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

826 

827 def process_distribution(self, client, distribution): 

828 try: 

829 res = client.get_distribution_config( 

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

831 default_config = self.validation_config 

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

833 

834 # Recursively merge config to allow piecemeal updates of 

835 # nested structures 

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

837 if config == updatedConfig: 

838 return 

839 res = client.update_distribution( 

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

841 IfMatch=res['ETag'], 

842 DistributionConfig=updatedConfig 

843 ) 

844 except (client.exceptions.NoSuchDistribution): 

845 pass 

846 except Exception as e: 

847 self.log.warning( 

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

849 distribution['ARN'], e) 

850 raise e 

851 

852 

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

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

855 

856 

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

858class StreamingDistributionUpdateAction(BaseUpdateAction): 

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

860 

861 :example: 

862 

863 .. code-block:: yaml 

864 

865 policies: 

866 - name: enforce-streaming-distribution-logging 

867 resource: streaming-distribution 

868 filters: 

869 - type: value 

870 key: "Logging.Enabled" 

871 value: false 

872 actions: 

873 - type: set-attributes 

874 attributes: 

875 Logging: 

876 Enabled: true 

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

878 Prefix: '' 

879 """ 

880 permissions = ("cloudfront:UpdateStreamingDistribution", 

881 "cloudfront:GetStreamingDistributionConfig",) 

882 shape = 'UpdateStreamingDistributionRequest' 

883 validation_config = { 

884 'S3Origin': { 

885 'DomainName': 'domain_name', 

886 'OriginAccessIdentity': 'origin_access_identity' 

887 }, 

888 'TrustedSigners': { 

889 'Enabled': False, 

890 'Quantity': 0 

891 }, 

892 'Comment': '', 

893 'Enabled': False 

894 } 

895 

896 def validate(self): 

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

898 

899 def process_distribution(self, client, streaming_distribution): 

900 try: 

901 res = client.get_streaming_distribution_config( 

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

903 default_config = self.validation_config 

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

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

906 if config == updatedConfig: 

907 return 

908 res = client.update_streaming_distribution( 

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

910 IfMatch=res['ETag'], 

911 StreamingDistributionConfig=updatedConfig 

912 ) 

913 except (client.exceptions.NoSuchStreamingDistribution): 

914 pass 

915 except Exception as e: 

916 self.log.warning( 

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

918 streaming_distribution['ARN'], e) 

919 raise e