Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/resources/cw.py: 57%
459 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« 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 itertools
4from collections import defaultdict
5from concurrent.futures import as_completed
6from datetime import datetime, timedelta
8import botocore.exceptions
10from c7n.actions import BaseAction
11from c7n.exceptions import PolicyValidationError
12from c7n.filters import Filter, MetricsFilter
13from c7n.filters.core import parse_date, ValueFilter
14from c7n.filters.iamaccess import CrossAccountAccessFilter
15from c7n.filters.related import ChildResourceFilter
16from c7n.filters.kms import KmsRelatedFilter
17from c7n.query import (
18 QueryResourceManager, ChildResourceManager,
19 TypeInfo, DescribeSource, ConfigSource, DescribeWithResourceTags)
20from c7n.manager import resources
21from c7n.resolver import ValuesFrom
22from c7n.resources import load_resources
23from c7n.resources.aws import ArnResolver
24from c7n.tags import universal_augment
25from c7n.utils import type_schema, local_session, chunks, get_retry
26from botocore.config import Config
29class DescribeAlarm(DescribeSource):
30 def augment(self, resources):
31 return universal_augment(self.manager, super().augment(resources))
34@resources.register('alarm')
35class Alarm(QueryResourceManager):
36 class resource_type(TypeInfo):
37 service = 'cloudwatch'
38 arn_type = 'alarm'
39 enum_spec = ('describe_alarms', 'MetricAlarms', None)
40 id = 'AlarmName'
41 arn = 'AlarmArn'
42 filter_name = 'AlarmNames'
43 filter_type = 'list'
44 name = 'AlarmName'
45 date = 'AlarmConfigurationUpdatedTimestamp'
46 cfn_type = config_type = 'AWS::CloudWatch::Alarm'
47 universal_taggable = object()
49 source_mapping = {
50 'describe': DescribeAlarm,
51 'config': ConfigSource
52 }
54 retry = staticmethod(get_retry(('Throttled',)))
57@Alarm.action_registry.register('delete')
58class AlarmDelete(BaseAction):
59 """Delete a cloudwatch alarm.
61 :example:
63 .. code-block:: yaml
65 policies:
66 - name: cloudwatch-delete-stale-alarms
67 resource: alarm
68 filters:
69 - type: value
70 value_type: age
71 key: StateUpdatedTimestamp
72 value: 30
73 op: ge
74 - StateValue: INSUFFICIENT_DATA
75 actions:
76 - delete
77 """
79 schema = type_schema('delete')
80 permissions = ('cloudwatch:DeleteAlarms',)
82 def process(self, resources):
83 client = local_session(
84 self.manager.session_factory).client('cloudwatch')
86 for resource_set in chunks(resources, size=100):
87 self.manager.retry(
88 client.delete_alarms,
89 AlarmNames=[r['AlarmName'] for r in resource_set])
92@resources.register('composite-alarm')
93class CompositeAlarm(QueryResourceManager):
95 class resource_type(TypeInfo):
96 service = 'cloudwatch'
97 arn_type = 'alarm'
98 enum_spec = ('describe_alarms', 'CompositeAlarms', {'AlarmTypes': ['CompositeAlarm']})
99 id = name = 'AlarmName'
100 arn = 'AlarmArn'
101 date = 'AlarmConfigurationUpdatedTimestamp'
102 cfn_type = 'AWS::CloudWatch::CompositeAlarm'
103 universal_taggable = object()
105 augment = universal_augment
107 retry = staticmethod(get_retry(('Throttled',)))
110@CompositeAlarm.action_registry.register('delete')
111class CompositeAlarmDelete(BaseAction):
112 """Delete a cloudwatch composite alarm.
114 :example:
116 .. code-block:: yaml
118 policies:
119 - name: cloudwatch-delete-composite-alarms
120 resource: aws.composite-alarm
121 filters:
122 - type: value
123 value_type: age
124 key: StateUpdatedTimestamp
125 value: 30
126 op: ge
127 - StateValue: INSUFFICIENT_DATA
128 actions:
129 - delete
130 """
132 schema = type_schema('delete')
133 permissions = ('cloudwatch:DeleteAlarms',)
135 def process(self, resources):
136 client = local_session(
137 self.manager.session_factory).client('cloudwatch')
139 for resource_set in chunks(resources, size=100):
140 self.manager.retry(
141 client.delete_alarms,
142 AlarmNames=[r['AlarmName'] for r in resource_set])
145@resources.register('event-bus')
146class EventBus(QueryResourceManager):
147 class resource_type(TypeInfo):
148 service = 'events'
149 arn_type = 'event-bus'
150 arn = 'Arn'
151 enum_spec = ('list_event_buses', 'EventBuses', None)
152 config_type = cfn_type = 'AWS::Events::EventBus'
153 id = name = 'Name'
154 universal_taggable = object()
156 source_mapping = {'describe': DescribeWithResourceTags,
157 'config': ConfigSource}
160@EventBus.filter_registry.register('cross-account')
161class EventBusCrossAccountFilter(CrossAccountAccessFilter):
162 # dummy permission
163 permissions = ('events:ListEventBuses',)
166@EventBus.action_registry.register('delete')
167class EventBusDelete(BaseAction):
168 """Delete an event bus.
170 :example:
172 .. code-block:: yaml
174 policies:
175 - name: cloudwatch-delete-event-bus
176 resource: aws.event-bus
177 filters:
178 - Name: test-event-bus
179 actions:
180 - delete
181 """
183 schema = type_schema('delete')
184 permissions = ('events:DeleteEventBus',)
186 def process(self, resources):
187 client = local_session(
188 self.manager.session_factory).client('events')
190 for resource_set in chunks(resources, size=100):
191 for r in resource_set:
192 self.manager.retry(
193 client.delete_event_bus,
194 Name=r['Name'])
197@resources.register('event-rule')
198class EventRule(QueryResourceManager):
199 class resource_type(TypeInfo):
200 service = 'events'
201 arn_type = 'rule'
202 enum_spec = ('list_rules', 'Rules', None)
203 name = "Name"
204 id = "Name"
205 filter_name = "NamePrefix"
206 filter_type = "scalar"
207 cfn_type = 'AWS::Events::Rule'
208 universal_taggable = object()
210 augment = universal_augment
213@EventRule.filter_registry.register('metrics')
214class EventRuleMetrics(MetricsFilter):
216 def get_dimensions(self, resource):
217 return [{'Name': 'RuleName', 'Value': resource['Name']}]
220@EventRule.filter_registry.register('event-rule-target')
221class EventRuleTargetFilter(ChildResourceFilter):
223 """
224 Filter event rules by their targets
226 :example:
228 .. code-block:: yaml
230 policies:
231 - name: find-event-rules-with-no-targets
232 resource: aws.event-rule
233 filters:
234 - type: event-rule-target
235 key: Arn
236 value: absent
237 """
239 RelatedResource = "c7n.resources.cw.EventRuleTarget"
240 RelatedIdsExpression = 'Name'
241 AnnotationKey = "EventRuleTargets"
243 schema = type_schema('event-rule-target', rinherit=ValueFilter.schema)
244 permissions = ('events:ListTargetsByRule',)
247@EventRule.filter_registry.register('invalid-targets')
248class ValidEventRuleTargetFilter(ChildResourceFilter):
249 """
250 Filter event rules for invalid targets, Use the `all` option to
251 find any event rules that have all invalid targets, otherwise
252 defaults to filtering any event rule with at least one invalid
253 target.
255 :example:
257 .. code-block:: yaml
259 policies:
260 - name: find-event-rules-with-invalid-targets
261 resource: aws.event-rule
262 filters:
263 - type: invalid-targets
264 all: true # defaults to false
265 """
267 RelatedResource = "c7n.resources.cw.EventRuleTarget"
268 RelatedIdsExpression = 'Name'
269 AnnotationKey = "EventRuleTargets"
271 schema = type_schema(
272 'invalid-targets',
273 **{
274 'all': {
275 'type': 'boolean',
276 'default': False
277 }
278 }
279 )
281 permissions = ('events:ListTargetsByRule',)
282 supported_resources = (
283 "aws.sqs",
284 "aws.event-bus",
285 "aws.lambda",
286 "aws.ecs",
287 "aws.ecs-task",
288 "aws.kinesis",
289 "aws.sns",
290 "aws.ssm-parameter",
291 "aws.batch-compute",
292 "aws.codepipeline",
293 "aws.step-machine",
294 )
296 def validate(self):
297 """
298 Empty validate here to bypass the validation found in the base value filter
299 as we're inheriting from the ChildResourceFilter/RelatedResourceFilter
300 """
301 return self
303 def get_rules_with_children(self, resources):
304 """
305 Augments resources by adding the c7n:ChildArns to the resource dict
306 """
308 results = []
310 # returns a map of {parent_reosurce_id: [{child_resource}, {child_resource2}, etc.]}
311 child_resources = self.get_related(resources)
313 # maps resources by their name to their data
314 for r in resources:
315 if child_resources.get(r['Name']):
316 for c in child_resources[r['Name']]:
317 r.setdefault('c7n:ChildArns', []).append(c['Arn'])
318 results.append(r)
319 return results
321 def process(self, resources, event=None):
322 # Due to lazy loading of resources, we need to explicilty load the following
323 # potential targets for a event rule target:
325 load_resources(list(self.supported_resources))
326 arn_resolver = ArnResolver(self.manager)
327 resources = self.get_rules_with_children(resources)
328 resources = [r for r in resources if self.filter_unsupported_resources(r)]
329 results = []
331 if self.data.get('all'):
332 op = any
333 else:
334 op = all
336 for r in resources:
337 resolved = arn_resolver.resolve(r['c7n:ChildArns'])
338 if not op(resolved.values()):
339 for i, j in resolved.items():
340 if not j:
341 r.setdefault('c7n:InvalidTargets', []).append(i)
342 results.append(r)
343 return results
345 def filter_unsupported_resources(self, r):
346 for carn in r.get('c7n:ChildArns'):
347 if 'aws.' + str(ArnResolver.resolve_type(carn)) not in self.supported_resources:
348 self.log.info(
349 f"Skipping resource {r.get('Arn')}, target type {carn} is not supported")
350 return False
351 return True
354@EventRule.action_registry.register('delete')
355class EventRuleDelete(BaseAction):
356 """
357 Delete an event rule, force target removal with the `force` option
359 :example:
361 .. code-block:: yaml
363 policies:
364 - name: force-delete-rules
365 resource: aws.event-rule
366 filters:
367 - Name: my-event-rule
368 actions:
369 - type: delete
370 force: true
371 """
373 schema = type_schema('delete', force={'type': 'boolean'})
374 permissions = ('events:DeleteRule', 'events:RemoveTargets', 'events:ListTargetsByRule',)
376 def process(self, resources):
377 client = local_session(self.manager.session_factory).client('events')
378 children = {}
379 target_error_msg = "Rule can't be deleted since it has targets."
380 for r in resources:
381 try:
382 client.delete_rule(Name=r['Name'])
383 except botocore.exceptions.ClientError as e:
384 if e.response['Error']['Message'] != target_error_msg:
385 raise
386 if not self.data.get('force'):
387 self.log.warning(
388 'Unable to delete %s event rule due to attached rule targets,'
389 'set force to true to remove targets' % r['Name'])
390 raise
391 child_manager = self.manager.get_resource_manager('aws.event-rule-target')
392 if not children:
393 children = EventRuleTargetFilter({}, child_manager).get_related(resources)
394 targets = list(set([t['Id'] for t in children.get(r['Name'])]))
395 client.remove_targets(Rule=r['Name'], Ids=targets)
396 client.delete_rule(Name=r['Name'])
399@EventRule.action_registry.register('set-rule-state')
400class SetRuleState(BaseAction):
401 """
402 This action allows to enable/disable a rule
404 :example:
406 .. code-block:: yaml
408 policies:
409 - name: test-rule
410 resource: aws.event-rule
411 filters:
412 - Name: my-event-rule
413 actions:
414 - type: set-rule-state
415 enabled: true
416 """
418 schema = type_schema(
419 'set-rule-state',
420 **{'enabled': {'default': True, 'type': 'boolean'}}
421 )
422 permissions = ('events:EnableRule', 'events:DisableRule',)
424 def process(self, resources):
425 config = Config(
426 retries={
427 'max_attempts': 8,
428 'mode': 'standard'
429 }
430 )
431 client = local_session(self.manager.session_factory).client('events', config=config)
432 retry = get_retry(('TooManyRequestsException', 'ResourceConflictException'))
433 enabled = self.data.get('enabled')
434 for resource in resources:
435 try:
436 if enabled:
437 retry(
438 client.enable_rule,
439 Name=resource['Name']
440 )
441 else:
442 retry(
443 client.disable_rule,
444 Name=resource['Name']
445 )
446 except (client.exceptions.ResourceNotFoundException,
447 client.exceptions.ManagedRuleException):
448 continue
451@resources.register('event-rule-target')
452class EventRuleTarget(ChildResourceManager):
453 class resource_type(TypeInfo):
454 service = 'events'
455 arn = False
456 arn_type = 'event-rule-target'
457 enum_spec = ('list_targets_by_rule', 'Targets', None)
458 parent_spec = ('event-rule', 'Rule', True)
459 name = id = 'Id'
462@EventRuleTarget.filter_registry.register('cross-account')
463class CrossAccountFilter(CrossAccountAccessFilter):
464 schema = type_schema(
465 'cross-account',
466 # white list accounts
467 whitelist_from=ValuesFrom.schema,
468 whitelist={'type': 'array', 'items': {'type': 'string'}})
470 # dummy permission
471 permissions = ('events:ListTargetsByRule',)
473 def __call__(self, r):
474 account_id = r['Arn'].split(':', 5)[4]
475 return account_id not in self.accounts
478@EventRuleTarget.action_registry.register('delete')
479class DeleteTarget(BaseAction):
480 schema = type_schema('delete')
481 permissions = ('events:RemoveTargets',)
483 def process(self, resources):
484 client = local_session(self.manager.session_factory).client('events')
485 rule_targets = {}
486 for r in resources:
487 rule_targets.setdefault(r['c7n:parent-id'], []).append(r['Id'])
489 for rule_id, target_ids in rule_targets.items():
490 client.remove_targets(
491 Ids=target_ids,
492 Rule=rule_id)
495@resources.register('log-group')
496class LogGroup(QueryResourceManager):
497 class resource_type(TypeInfo):
498 service = 'logs'
499 arn_type = 'log-group'
500 enum_spec = ('describe_log_groups', 'logGroups', None)
501 id = name = 'logGroupName'
502 arn = 'arn' # see get-arns override re attribute usage
503 filter_name = 'logGroupNamePrefix'
504 filter_type = 'scalar'
505 dimension = 'LogGroupName'
506 date = 'creationTime'
507 universal_taggable = True
508 cfn_type = 'AWS::Logs::LogGroup'
510 augment = universal_augment
512 def get_arns(self, resources):
513 # log group arn in resource describe has ':*' suffix, not all
514 # apis can use that form, so normalize to standard arn.
515 return [r['arn'][:-2] for r in resources]
518@resources.register('insight-rule')
519class InsightRule(QueryResourceManager):
520 class resource_type(TypeInfo):
521 service = 'cloudwatch'
522 arn_type = 'insight-rule'
523 enum_spec = ('describe_insight_rules', 'InsightRules', None)
524 name = id = 'Name'
525 universal_taggable = object()
526 permission_augment = ('cloudWatch::ListTagsForResource',)
527 cfn_type = 'AWS::CloudWatch::InsightRule'
529 def augment(self, rules):
530 client = local_session(self.session_factory).client('cloudwatch')
532 def _add_tags(r):
533 arn = self.generate_arn(r['Name'])
534 r['Tags'] = client.list_tags_for_resource(
535 ResourceARN=arn).get('Tags', [])
536 return r
538 return list(map(_add_tags, rules))
541@InsightRule.action_registry.register('disable')
542class InsightRuleDisable(BaseAction):
543 """Disable a cloudwatch contributor insight rule.
545 :example:
547 .. code-block:: yaml
549 policies:
550 - name: cloudwatch-disable-insight-rule
551 resource: insight-rule
552 filters:
553 - type: value
554 key: State
555 value: ENABLED
556 op: eq
557 actions:
558 - disable
559 """
561 schema = type_schema('disable')
562 permissions = ('cloudwatch:DisableInsightRules',)
564 def process(self, resources):
565 client = local_session(
566 self.manager.session_factory).client('cloudwatch')
568 for resource_set in chunks(resources, size=100):
569 self.manager.retry(
570 client.disable_insight_rules,
571 RuleNames=[r['Name'] for r in resource_set])
574@InsightRule.action_registry.register('delete')
575class InsightRuleDelete(BaseAction):
576 """Delete a cloudwatch contributor insight rule
578 :example:
580 .. code-block:: yaml
582 policies:
583 - name: cloudwatch-delete-insight-rule
584 resource: insight-rule
585 filters:
586 - type: value
587 key: State
588 value: ENABLED
589 op: eq
590 actions:
591 - delete
592 """
594 schema = type_schema('delete')
595 permissions = ('cloudwatch:DeleteInsightRules',)
597 def process(self, resources):
598 client = local_session(
599 self.manager.session_factory).client('cloudwatch')
601 for resource_set in chunks(resources, size=100):
602 self.manager.retry(
603 client.delete_insight_rules,
604 RuleNames=[r['Name'] for r in resource_set])
607@LogGroup.filter_registry.register('metrics')
608class LogGroupMetrics(MetricsFilter):
610 def get_dimensions(self, resource):
611 return [{'Name': 'LogGroupName', 'Value': resource['logGroupName']}]
614@resources.register('log-metric')
615class LogMetric(QueryResourceManager):
616 class resource_type(TypeInfo):
617 service = 'logs'
618 enum_spec = ('describe_metric_filters', 'metricFilters', None)
619 arn = False
620 id = name = 'filterName'
621 date = 'creationTime'
622 cfn_type = 'AWS::Logs::MetricFilter'
625@LogMetric.filter_registry.register('alarm')
626class LogMetricAlarmFilter(ValueFilter):
627 """
628 Filter log metric filters based on associated alarms.
630 :example:
632 .. code-block:: yaml
634 policies:
635 - name: log-metrics-with-alarms
636 resource: aws.log-metric
637 filters:
638 - type: alarm
639 key: AlarmName
640 value: present
641 """
643 schema = type_schema('alarm', rinherit=ValueFilter.schema)
644 annotation_key = 'c7n:MetricAlarms'
645 FetchThreshold = 10 # below this number of resources, fetch alarms individually
647 def augment(self, resources):
648 """Add alarm details to log metric filter resources
650 This includes all alarms where the metric name and namespace match
651 a log metric filter's metric transformation.
652 """
654 if len(resources) < self.FetchThreshold:
655 client = local_session(self.manager.session_factory).client('cloudwatch')
656 for r in resources:
657 r[self.annotation_key] = list(itertools.chain(*(
658 self.manager.retry(
659 client.describe_alarms_for_metric,
660 Namespace=t['metricNamespace'],
661 MetricName=t['metricName'])['MetricAlarms']
662 for t in r.get('metricTransformations', ())
663 )))
664 else:
665 alarms = self.manager.get_resource_manager('aws.alarm').resources()
667 # We'll be matching resources to alarms based on namespace and
668 # metric name - this lookup table makes that smoother
669 alarms_by_metric = defaultdict(list)
670 for alarm in alarms:
671 alarms_by_metric[(alarm['Namespace'], alarm['MetricName'])].append(alarm)
673 for r in resources:
674 r[self.annotation_key] = list(itertools.chain(*(
675 alarms_by_metric.get((t['metricNamespace'], t['metricName']), [])
676 for t in r.get('metricTransformations', ())
677 )))
679 def get_permissions(self):
680 return [
681 *self.manager.get_resource_manager('aws.alarm').get_permissions(),
682 'cloudwatch:DescribeAlarmsForMetric'
683 ]
685 def process(self, resources, event=None):
686 self.augment(resources)
688 matched = []
689 for r in resources:
690 if any((self.match(alarm) for alarm in r[self.annotation_key])):
691 matched.append(r)
692 return matched
695@LogGroup.action_registry.register('retention')
696class Retention(BaseAction):
697 """Action to set the retention period (in days) for CloudWatch log groups
699 :example:
701 .. code-block:: yaml
703 policies:
704 - name: cloudwatch-set-log-group-retention
705 resource: log-group
706 actions:
707 - type: retention
708 days: 200
709 """
711 schema = type_schema('retention', days={'type': 'integer'})
712 permissions = ('logs:PutRetentionPolicy',)
714 def process(self, resources):
715 client = local_session(self.manager.session_factory).client('logs')
716 days = self.data['days']
717 for r in resources:
718 self.manager.retry(
719 client.put_retention_policy,
720 logGroupName=r['logGroupName'],
721 retentionInDays=days)
724@LogGroup.action_registry.register('delete')
725class Delete(BaseAction):
726 """
728 :example:
730 .. code-block:: yaml
732 policies:
733 - name: cloudwatch-delete-stale-log-group
734 resource: log-group
735 filters:
736 - type: last-write
737 days: 182.5
738 actions:
739 - delete
740 """
742 schema = type_schema('delete')
743 permissions = ('logs:DeleteLogGroup',)
745 def process(self, resources):
746 client = local_session(self.manager.session_factory).client('logs')
747 for r in resources:
748 try:
749 self.manager.retry(
750 client.delete_log_group, logGroupName=r['logGroupName'])
751 except client.exceptions.ResourceNotFoundException:
752 continue
755@LogGroup.filter_registry.register('last-write')
756class LastWriteDays(Filter):
757 """Filters CloudWatch log groups by last write
759 :example:
761 .. code-block:: yaml
763 policies:
764 - name: cloudwatch-stale-groups
765 resource: log-group
766 filters:
767 - type: last-write
768 days: 60
769 """
771 schema = type_schema(
772 'last-write', days={'type': 'number'})
773 permissions = ('logs:DescribeLogStreams',)
775 def process(self, resources, event=None):
776 client = local_session(self.manager.session_factory).client('logs')
777 self.date_threshold = parse_date(datetime.utcnow()) - timedelta(
778 days=self.data['days'])
779 return [r for r in resources if self.check_group(client, r)]
781 def check_group(self, client, group):
782 streams = self.manager.retry(
783 client.describe_log_streams,
784 logGroupName=group['logGroupName'],
785 orderBy='LastEventTime',
786 descending=True,
787 limit=3).get('logStreams')
788 group['streams'] = streams
789 if not streams:
790 last_timestamp = group['creationTime']
791 elif 'lastIngestionTime' in streams[0]:
792 last_timestamp = streams[0]['lastIngestionTime']
793 else:
794 last_timestamp = streams[0]['creationTime']
796 last_write = parse_date(last_timestamp)
797 group['lastWrite'] = last_write
798 return self.date_threshold > last_write
801@LogGroup.filter_registry.register('cross-account')
802class LogCrossAccountFilter(CrossAccountAccessFilter):
803 schema = type_schema(
804 'cross-account',
805 # white list accounts
806 whitelist_from=ValuesFrom.schema,
807 whitelist={'type': 'array', 'items': {'type': 'string'}})
809 permissions = ('logs:DescribeSubscriptionFilters',)
811 def process(self, resources, event=None):
812 client = local_session(self.manager.session_factory).client('logs')
813 accounts = self.get_accounts()
814 results = []
815 with self.executor_factory(max_workers=1) as w:
816 futures = []
817 for rset in chunks(resources, 50):
818 futures.append(
819 w.submit(
820 self.process_resource_set, client, accounts, rset))
821 for f in as_completed(futures):
822 if f.exception():
823 self.log.error(
824 "Error checking log groups cross-account %s",
825 f.exception())
826 continue
827 results.extend(f.result())
828 return results
830 def process_resource_set(self, client, accounts, resources):
831 results = []
832 for r in resources:
833 found = False
834 filters = self.manager.retry(
835 client.describe_subscription_filters,
836 logGroupName=r['logGroupName']).get('subscriptionFilters', ())
837 for f in filters:
838 if 'destinationArn' not in f:
839 continue
840 account_id = f['destinationArn'].split(':', 5)[4]
841 if account_id not in accounts:
842 r.setdefault('c7n:CrossAccountViolations', []).append(
843 account_id)
844 found = True
845 if found:
846 results.append(r)
847 return results
850@LogGroup.filter_registry.register('subscription-filter')
851class LogSubscriptionFilter(ValueFilter):
852 """Filters CloudWatch log groups by subscriptions
854 :example:
856 .. code-block:: yaml
858 policies:
859 - name: cloudwatch-groups-with-subscriptions
860 resource: log-group
861 filters:
862 - type: subscription-filter
863 key: destinationArn
864 value: arn:aws:lambda:us-east-1:123456789876:function:forwarder
865 """
866 schema = type_schema('subscription-filter', rinherit=ValueFilter.schema)
867 annotation_key = 'c7n:SubscriptionFilters'
868 permissions = ('logs:DescribeSubscriptionFilters',)
870 def process(self, resources, event=None):
871 client = local_session(self.manager.session_factory).client('logs')
872 results = []
873 for r in resources:
874 filters = self.manager.retry(
875 client.describe_subscription_filters,
876 logGroupName=r['logGroupName']).get('subscriptionFilters', ())
877 if not any(filters):
878 continue
879 for f in filters:
880 r.setdefault(self.annotation_key, []).append(f)
881 if (len(self.data) == 1) or any((self.match(sub) for sub in r[self.annotation_key])):
882 results.append(r)
883 return results
886@LogGroup.filter_registry.register('kms-key')
887class KmsFilter(KmsRelatedFilter):
888 RelatedIdsExpression = 'kmsKeyId'
891@LogGroup.action_registry.register('set-encryption')
892class EncryptLogGroup(BaseAction):
893 """Encrypt/Decrypt a log group
895 :example:
897 .. code-block:: yaml
899 policies:
900 - name: encrypt-log-group
901 resource: log-group
902 filters:
903 - kmsKeyId: absent
904 actions:
905 - type: set-encryption
906 kms-key: alias/mylogkey
907 state: True
909 - name: decrypt-log-group
910 resource: log-group
911 filters:
912 - kmsKeyId: kms:key:arn
913 actions:
914 - type: set-encryption
915 state: False
916 """
917 schema = type_schema(
918 'set-encryption',
919 **{'kms-key': {'type': 'string'},
920 'state': {'type': 'boolean'}})
921 permissions = (
922 'logs:AssociateKmsKey', 'logs:DisassociateKmsKey', 'kms:DescribeKey')
924 def validate(self):
925 if not self.data.get('state', True):
926 return self
927 key = self.data.get('kms-key', '')
928 if not key:
929 raise ValueError('Must specify either a KMS key ARN or Alias')
930 if 'alias/' not in key and ':key/' not in key:
931 raise PolicyValidationError(
932 "Invalid kms key format %s" % key)
933 return self
935 def resolve_key(self, key):
936 if not key:
937 return
939 # Qualified arn for key
940 if key.startswith('arn:') and ':key/' in key:
941 return key
943 # Alias
944 key = local_session(
945 self.manager.session_factory).client(
946 'kms').describe_key(
947 KeyId=key)['KeyMetadata']['Arn']
948 return key
950 def process(self, resources):
951 session = local_session(self.manager.session_factory)
952 client = session.client('logs')
954 state = self.data.get('state', True)
955 key = self.resolve_key(self.data.get('kms-key'))
957 for r in resources:
958 try:
959 if state:
960 client.associate_kms_key(
961 logGroupName=r['logGroupName'], kmsKeyId=key)
962 else:
963 client.disassociate_kms_key(logGroupName=r['logGroupName'])
964 except client.exceptions.ResourceNotFoundException:
965 continue
968@LogGroup.action_registry.register('put-subscription-filter')
969class SubscriptionFilter(BaseAction):
970 """Create/Update a subscription filter and associate with a log group
972 :example:
974 .. code-block:: yaml
976 policies:
977 - name: cloudwatch-put-subscription-filter
978 resource: log-group
979 actions:
980 - type: put-subscription-filter
981 filter_name: AllLambda
982 filter_pattern: ip
983 destination_arn: arn:aws:logs:us-east-1:1234567890:destination:lambda
984 distribution: Random
985 role_arn: "arn:aws:iam::{account_id}:role/testCrossAccountRole"
986 """
987 schema = type_schema(
988 'put-subscription-filter',
989 filter_name={'type': 'string'},
990 filter_pattern={'type': 'string'},
991 destination_arn={'type': 'string'},
992 distribution={'enum': ['Random', 'ByLogStream']},
993 role_arn={'type': 'string'},
994 required=['filter_name', 'destination_arn'])
995 permissions = ('logs:PutSubscriptionFilter',)
997 def process(self, resources):
998 session = local_session(self.manager.session_factory)
999 client = session.client('logs')
1000 params = dict(
1001 filterName=self.data.get('filter_name'),
1002 filterPattern=self.data.get('filter_pattern', ''),
1003 destinationArn=self.data.get('destination_arn'),
1004 distribution=self.data.get('distribution', 'ByLogStream'))
1006 if self.data.get('role_arn'):
1007 params['roleArn'] = self.data.get('role_arn')
1009 for r in resources:
1010 client.put_subscription_filter(
1011 logGroupName=r['logGroupName'], **params)
1015@resources.register("cloudwatch-dashboard")
1016class CloudWatchDashboard(QueryResourceManager):
1017 class resource_type(TypeInfo):
1018 service = "cloudwatch"
1019 enum_spec = ('list_dashboards', 'DashboardEntries', None)
1020 arn_type = "dashboard"
1021 arn = "DashboardArn"
1022 id = "DashboardName"
1023 name = "DashboardName"
1024 cfn_type = "AWS::CloudWatch::Dashboard"
1025 universal_taggable = object()
1027 source_mapping = {
1028 "describe": DescribeWithResourceTags,
1029 }