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

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 

7 

8import botocore.exceptions 

9 

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 

27 

28 

29class DescribeAlarm(DescribeSource): 

30 def augment(self, resources): 

31 return universal_augment(self.manager, super().augment(resources)) 

32 

33 

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() 

48 

49 source_mapping = { 

50 'describe': DescribeAlarm, 

51 'config': ConfigSource 

52 } 

53 

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

55 

56 

57@Alarm.action_registry.register('delete') 

58class AlarmDelete(BaseAction): 

59 """Delete a cloudwatch alarm. 

60 

61 :example: 

62 

63 .. code-block:: yaml 

64 

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

78 

79 schema = type_schema('delete') 

80 permissions = ('cloudwatch:DeleteAlarms',) 

81 

82 def process(self, resources): 

83 client = local_session( 

84 self.manager.session_factory).client('cloudwatch') 

85 

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]) 

90 

91 

92@resources.register('composite-alarm') 

93class CompositeAlarm(QueryResourceManager): 

94 

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() 

104 

105 augment = universal_augment 

106 

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

108 

109 

110@CompositeAlarm.action_registry.register('delete') 

111class CompositeAlarmDelete(BaseAction): 

112 """Delete a cloudwatch composite alarm. 

113 

114 :example: 

115 

116 .. code-block:: yaml 

117 

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

131 

132 schema = type_schema('delete') 

133 permissions = ('cloudwatch:DeleteAlarms',) 

134 

135 def process(self, resources): 

136 client = local_session( 

137 self.manager.session_factory).client('cloudwatch') 

138 

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]) 

143 

144 

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() 

155 

156 source_mapping = {'describe': DescribeWithResourceTags, 

157 'config': ConfigSource} 

158 

159 

160@EventBus.filter_registry.register('cross-account') 

161class EventBusCrossAccountFilter(CrossAccountAccessFilter): 

162 # dummy permission 

163 permissions = ('events:ListEventBuses',) 

164 

165 

166@EventBus.action_registry.register('delete') 

167class EventBusDelete(BaseAction): 

168 """Delete an event bus. 

169 

170 :example: 

171 

172 .. code-block:: yaml 

173 

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

182 

183 schema = type_schema('delete') 

184 permissions = ('events:DeleteEventBus',) 

185 

186 def process(self, resources): 

187 client = local_session( 

188 self.manager.session_factory).client('events') 

189 

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']) 

195 

196 

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() 

209 

210 augment = universal_augment 

211 

212 

213@EventRule.filter_registry.register('metrics') 

214class EventRuleMetrics(MetricsFilter): 

215 

216 def get_dimensions(self, resource): 

217 return [{'Name': 'RuleName', 'Value': resource['Name']}] 

218 

219 

220@EventRule.filter_registry.register('event-rule-target') 

221class EventRuleTargetFilter(ChildResourceFilter): 

222 

223 """ 

224 Filter event rules by their targets 

225 

226 :example: 

227 

228 .. code-block:: yaml 

229 

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

238 

239 RelatedResource = "c7n.resources.cw.EventRuleTarget" 

240 RelatedIdsExpression = 'Name' 

241 AnnotationKey = "EventRuleTargets" 

242 

243 schema = type_schema('event-rule-target', rinherit=ValueFilter.schema) 

244 permissions = ('events:ListTargetsByRule',) 

245 

246 

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. 

254 

255 :example: 

256 

257 .. code-block:: yaml 

258 

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

266 

267 RelatedResource = "c7n.resources.cw.EventRuleTarget" 

268 RelatedIdsExpression = 'Name' 

269 AnnotationKey = "EventRuleTargets" 

270 

271 schema = type_schema( 

272 'invalid-targets', 

273 **{ 

274 'all': { 

275 'type': 'boolean', 

276 'default': False 

277 } 

278 } 

279 ) 

280 

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 ) 

295 

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 

302 

303 def get_rules_with_children(self, resources): 

304 """ 

305 Augments resources by adding the c7n:ChildArns to the resource dict 

306 """ 

307 

308 results = [] 

309 

310 # returns a map of {parent_reosurce_id: [{child_resource}, {child_resource2}, etc.]} 

311 child_resources = self.get_related(resources) 

312 

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 

320 

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: 

324 

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 = [] 

330 

331 if self.data.get('all'): 

332 op = any 

333 else: 

334 op = all 

335 

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 

344 

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 

352 

353 

354@EventRule.action_registry.register('delete') 

355class EventRuleDelete(BaseAction): 

356 """ 

357 Delete an event rule, force target removal with the `force` option 

358 

359 :example: 

360 

361 .. code-block:: yaml 

362 

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

372 

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

374 permissions = ('events:DeleteRule', 'events:RemoveTargets', 'events:ListTargetsByRule',) 

375 

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']) 

397 

398 

399@EventRule.action_registry.register('set-rule-state') 

400class SetRuleState(BaseAction): 

401 """ 

402 This action allows to enable/disable a rule 

403 

404 :example: 

405 

406 .. code-block:: yaml 

407 

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

417 

418 schema = type_schema( 

419 'set-rule-state', 

420 **{'enabled': {'default': True, 'type': 'boolean'}} 

421 ) 

422 permissions = ('events:EnableRule', 'events:DisableRule',) 

423 

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 

449 

450 

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' 

460 

461 

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'}}) 

469 

470 # dummy permission 

471 permissions = ('events:ListTargetsByRule',) 

472 

473 def __call__(self, r): 

474 account_id = r['Arn'].split(':', 5)[4] 

475 return account_id not in self.accounts 

476 

477 

478@EventRuleTarget.action_registry.register('delete') 

479class DeleteTarget(BaseAction): 

480 schema = type_schema('delete') 

481 permissions = ('events:RemoveTargets',) 

482 

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']) 

488 

489 for rule_id, target_ids in rule_targets.items(): 

490 client.remove_targets( 

491 Ids=target_ids, 

492 Rule=rule_id) 

493 

494 

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' 

509 

510 augment = universal_augment 

511 

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] 

516 

517 

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' 

528 

529 def augment(self, rules): 

530 client = local_session(self.session_factory).client('cloudwatch') 

531 

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 

537 

538 return list(map(_add_tags, rules)) 

539 

540 

541@InsightRule.action_registry.register('disable') 

542class InsightRuleDisable(BaseAction): 

543 """Disable a cloudwatch contributor insight rule. 

544 

545 :example: 

546 

547 .. code-block:: yaml 

548 

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

560 

561 schema = type_schema('disable') 

562 permissions = ('cloudwatch:DisableInsightRules',) 

563 

564 def process(self, resources): 

565 client = local_session( 

566 self.manager.session_factory).client('cloudwatch') 

567 

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]) 

572 

573 

574@InsightRule.action_registry.register('delete') 

575class InsightRuleDelete(BaseAction): 

576 """Delete a cloudwatch contributor insight rule 

577 

578 :example: 

579 

580 .. code-block:: yaml 

581 

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

593 

594 schema = type_schema('delete') 

595 permissions = ('cloudwatch:DeleteInsightRules',) 

596 

597 def process(self, resources): 

598 client = local_session( 

599 self.manager.session_factory).client('cloudwatch') 

600 

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]) 

605 

606 

607@LogGroup.filter_registry.register('metrics') 

608class LogGroupMetrics(MetricsFilter): 

609 

610 def get_dimensions(self, resource): 

611 return [{'Name': 'LogGroupName', 'Value': resource['logGroupName']}] 

612 

613 

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' 

623 

624 

625@LogMetric.filter_registry.register('alarm') 

626class LogMetricAlarmFilter(ValueFilter): 

627 """ 

628 Filter log metric filters based on associated alarms. 

629 

630 :example: 

631 

632 .. code-block:: yaml 

633 

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

642 

643 schema = type_schema('alarm', rinherit=ValueFilter.schema) 

644 annotation_key = 'c7n:MetricAlarms' 

645 FetchThreshold = 10 # below this number of resources, fetch alarms individually 

646 

647 def augment(self, resources): 

648 """Add alarm details to log metric filter resources 

649 

650 This includes all alarms where the metric name and namespace match 

651 a log metric filter's metric transformation. 

652 """ 

653 

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() 

666 

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) 

672 

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

678 

679 def get_permissions(self): 

680 return [ 

681 *self.manager.get_resource_manager('aws.alarm').get_permissions(), 

682 'cloudwatch:DescribeAlarmsForMetric' 

683 ] 

684 

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

686 self.augment(resources) 

687 

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 

693 

694 

695@LogGroup.action_registry.register('retention') 

696class Retention(BaseAction): 

697 """Action to set the retention period (in days) for CloudWatch log groups 

698 

699 :example: 

700 

701 .. code-block:: yaml 

702 

703 policies: 

704 - name: cloudwatch-set-log-group-retention 

705 resource: log-group 

706 actions: 

707 - type: retention 

708 days: 200 

709 """ 

710 

711 schema = type_schema('retention', days={'type': 'integer'}) 

712 permissions = ('logs:PutRetentionPolicy',) 

713 

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) 

722 

723 

724@LogGroup.action_registry.register('delete') 

725class Delete(BaseAction): 

726 """ 

727 

728 :example: 

729 

730 .. code-block:: yaml 

731 

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

741 

742 schema = type_schema('delete') 

743 permissions = ('logs:DeleteLogGroup',) 

744 

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 

753 

754 

755@LogGroup.filter_registry.register('last-write') 

756class LastWriteDays(Filter): 

757 """Filters CloudWatch log groups by last write 

758 

759 :example: 

760 

761 .. code-block:: yaml 

762 

763 policies: 

764 - name: cloudwatch-stale-groups 

765 resource: log-group 

766 filters: 

767 - type: last-write 

768 days: 60 

769 """ 

770 

771 schema = type_schema( 

772 'last-write', days={'type': 'number'}) 

773 permissions = ('logs:DescribeLogStreams',) 

774 

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)] 

780 

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'] 

795 

796 last_write = parse_date(last_timestamp) 

797 group['lastWrite'] = last_write 

798 return self.date_threshold > last_write 

799 

800 

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'}}) 

808 

809 permissions = ('logs:DescribeSubscriptionFilters',) 

810 

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 

829 

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 

848 

849 

850@LogGroup.filter_registry.register('subscription-filter') 

851class LogSubscriptionFilter(ValueFilter): 

852 """Filters CloudWatch log groups by subscriptions 

853 

854 :example: 

855 

856 .. code-block:: yaml 

857 

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',) 

869 

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 

884 

885 

886@LogGroup.filter_registry.register('kms-key') 

887class KmsFilter(KmsRelatedFilter): 

888 RelatedIdsExpression = 'kmsKeyId' 

889 

890 

891@LogGroup.action_registry.register('set-encryption') 

892class EncryptLogGroup(BaseAction): 

893 """Encrypt/Decrypt a log group 

894 

895 :example: 

896 

897 .. code-block:: yaml 

898 

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 

908 

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') 

923 

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 

934 

935 def resolve_key(self, key): 

936 if not key: 

937 return 

938 

939 # Qualified arn for key 

940 if key.startswith('arn:') and ':key/' in key: 

941 return key 

942 

943 # Alias 

944 key = local_session( 

945 self.manager.session_factory).client( 

946 'kms').describe_key( 

947 KeyId=key)['KeyMetadata']['Arn'] 

948 return key 

949 

950 def process(self, resources): 

951 session = local_session(self.manager.session_factory) 

952 client = session.client('logs') 

953 

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

955 key = self.resolve_key(self.data.get('kms-key')) 

956 

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 

966 

967 

968@LogGroup.action_registry.register('put-subscription-filter') 

969class SubscriptionFilter(BaseAction): 

970 """Create/Update a subscription filter and associate with a log group 

971 

972 :example: 

973 

974 .. code-block:: yaml 

975 

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',) 

996 

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')) 

1005 

1006 if self.data.get('role_arn'): 

1007 params['roleArn'] = self.data.get('role_arn') 

1008 

1009 for r in resources: 

1010 client.put_subscription_filter( 

1011 logGroupName=r['logGroupName'], **params) 

1012 

1013 

1014 

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() 

1026 

1027 source_mapping = { 

1028 "describe": DescribeWithResourceTags, 

1029 }