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