Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n/resources/ssm.py: 48%
371 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 json
4import hashlib
5import operator
7from concurrent.futures import as_completed
8from c7n.actions import Action
9from c7n.exceptions import PolicyValidationError
10from c7n.filters import Filter, CrossAccountAccessFilter
11from c7n.filters.kms import KmsRelatedFilter
12from c7n.query import QueryResourceManager, TypeInfo
13from c7n.manager import resources
14from c7n.tags import universal_augment
15from c7n.utils import chunks, get_retry, local_session, type_schema, filter_empty
16from c7n.version import version
18from .aws import shape_validate
19from .ec2 import EC2
22@resources.register('ssm-parameter')
23class SSMParameter(QueryResourceManager):
25 class resource_type(TypeInfo):
26 service = 'ssm'
27 enum_spec = ('describe_parameters', 'Parameters', None)
28 name = "Name"
29 id = "Name"
30 universal_taggable = True
31 arn_type = "parameter"
32 cfn_type = 'AWS::SSM::Parameter'
34 retry = staticmethod(get_retry(('Throttled',)))
35 permissions = ('ssm:GetParameters',
36 'ssm:DescribeParameters')
38 augment = universal_augment
41@SSMParameter.action_registry.register('delete')
42class DeleteParameter(Action):
44 schema = type_schema('delete')
45 permissions = ("ssm:DeleteParameter",)
47 def process(self, resources):
48 client = local_session(self.manager.session_factory).client('ssm')
49 for r in resources:
50 self.manager.retry(
51 client.delete_parameter, Name=r['Name'],
52 ignore_err_codes=('ParameterNotFound',))
55@resources.register('ssm-managed-instance')
56class ManagedInstance(QueryResourceManager):
58 class resource_type(TypeInfo):
59 service = 'ssm'
60 enum_spec = ('describe_instance_information', 'InstanceInformationList', None)
61 id = 'InstanceId'
62 name = 'Name'
63 date = 'RegistrationDate'
64 arn_type = "managed-instance"
66 permissions = ('ssm:DescribeInstanceInformation',)
69@EC2.action_registry.register('send-command')
70@ManagedInstance.action_registry.register('send-command')
71class SendCommand(Action):
72 """Run an SSM Automation Document on an instance.
74 :Example:
76 Find ubuntu 18.04 instances are active with ssm.
78 .. code-block:: yaml
80 policies:
81 - name: ec2-osquery-install
82 resource: ec2
83 filters:
84 - type: ssm
85 key: PingStatus
86 value: Online
87 - type: ssm
88 key: PlatformName
89 value: Ubuntu
90 - type: ssm
91 key: PlatformVersion
92 value: 18.04
93 actions:
94 - type: send-command
95 command:
96 DocumentName: AWS-RunShellScript
97 Parameters:
98 commands:
99 - wget https://pkg.osquery.io/deb/osquery_3.3.0_1.linux.amd64.deb
100 - dpkg -i osquery_3.3.0_1.linux.amd64.deb
101 """
103 schema = type_schema(
104 'send-command',
105 command={'type': 'object'},
106 required=('command',))
108 permissions = ('ssm:SendCommand',)
109 shape = "SendCommandRequest"
110 annotation = 'c7n:SendCommand'
112 def validate(self):
113 shape_validate(self.data['command'], self.shape, 'ssm')
114 # If used against an ec2 resource, require an ssm status filter
115 # to ensure that we're not trying to send commands to instances
116 # that aren't in ssm.
117 if self.manager.type != 'ec2':
118 return
120 found = False
121 for f in self.manager.iter_filters():
122 if f.type == 'ssm':
123 found = True
124 break
125 if not found:
126 raise PolicyValidationError(
127 "send-command requires use of ssm filter on ec2 resources")
129 def process(self, resources):
130 client = local_session(self.manager.session_factory).client('ssm')
131 for resource_set in chunks(resources, 50):
132 self.process_resource_set(client, resource_set)
134 def process_resource_set(self, client, resources):
135 command = dict(self.data['command'])
136 command['InstanceIds'] = [
137 r['InstanceId'] for r in resources]
138 result = client.send_command(**command).get('Command')
139 for r in resources:
140 r.setdefault('c7n:SendCommand', []).append(result['CommandId'])
143@resources.register('ssm-activation')
144class SSMActivation(QueryResourceManager):
146 class resource_type(TypeInfo):
147 service = 'ssm'
148 enum_spec = ('describe_activations', 'ActivationList', None)
149 id = 'ActivationId'
150 name = 'Description'
151 date = 'CreatedDate'
152 arn = False
154 permissions = ('ssm:DescribeActivations',)
157@SSMActivation.action_registry.register('delete')
158class DeleteSSMActivation(Action):
159 schema = type_schema('delete')
160 permissions = ('ssm:DeleteActivation',)
162 def process(self, resources):
163 client = local_session(self.manager.session_factory).client('ssm')
164 for a in resources:
165 client.delete_activation(ActivationId=a["ActivationId"])
168@resources.register('ops-item')
169class OpsItem(QueryResourceManager):
170 """Resource for OpsItems in SSM OpsCenter
171 https://docs.aws.amazon.com/systems-manager/latest/userguide/OpsCenter.html
172 """
173 class resource_type(TypeInfo):
175 enum_spec = ('describe_ops_items', 'OpsItemSummaries', None)
176 service = 'ssm'
177 arn_type = 'opsitem'
178 id = 'OpsItemId'
179 name = 'Title'
181 default_report_fields = (
182 'Status', 'Title', 'LastModifiedTime',
183 'CreatedBy', 'CreatedTime')
185 QueryKeys = {
186 'Status',
187 'CreatedBy',
188 'Source',
189 'Priority',
190 'Title',
191 'OpsItemId',
192 'CreatedTime',
193 'LastModifiedTime',
194 'OperationalData',
195 'OperationalDataKey',
196 'OperationalDataValue',
197 'ResourceId',
198 'AutomationId'}
199 QueryOperators = {'Equal', 'LessThan', 'GreaterThan', 'Contains'}
201 def validate(self):
202 self.query = self.resource_query()
203 return super(OpsItem, self).validate()
205 def get_resources(self, ids, cache=True, augment=True):
206 if isinstance(ids, str):
207 ids = [ids]
208 return self.resources({
209 'OpsItemFilters': [{
210 'Key': 'OpsItemId',
211 'Values': [i],
212 'Operator': 'Equal'} for i in ids]})
214 def resources(self, query=None):
215 q = self.resource_query()
216 if q and query and 'OpsItemFilters' in query:
217 q['OpsItemFilters'].extend(query['OpsItemFilters'])
218 return super(OpsItem, self).resources(query=q)
220 def resource_query(self):
221 filters = []
222 for q in self.data.get('query', ()):
223 if (not isinstance(q, dict) or
224 not set(q.keys()) == {'Key', 'Values', 'Operator'} or
225 q['Key'] not in self.QueryKeys or
226 q['Operator'] not in self.QueryOperators):
227 raise PolicyValidationError(
228 "invalid ops-item query %s" % self.data['query'])
229 filters.append(q)
230 return {'OpsItemFilters': filters}
233@OpsItem.action_registry.register('update')
234class UpdateOpsItem(Action):
235 """Update an ops item.
237 : example :
239 Close out open ops items older than 30 days for a given issue.
241 .. code-block:: yaml
243 policies:
244 - name: issue-items
245 resource: aws.ops-item
246 filters:
247 - Status: Open
248 - Title: checking-lambdas
249 - type: value
250 key: CreatedTime
251 value_type: age
252 op: greater-than
253 value: 30
254 actions:
255 - type: update
256 status: Resolved
257 """
259 schema = type_schema(
260 'update',
261 description={'type': 'string'},
262 priority={'enum': list(range(1, 6))},
263 title={'type': 'string'},
264 topics={'type': 'array', 'items': {'type': 'string'}},
265 status={'enum': ['Open', 'In Progress', 'Resolved']},
266 )
267 permissions = ('ssm:UpdateOpsItem',)
269 def process(self, resources):
270 attrs = dict(self.data)
271 attrs = filter_empty({
272 'Description': attrs.get('description'),
273 'Title': attrs.get('title'),
274 'Priority': attrs.get('priority'),
275 'Status': attrs.get('status'),
276 'Notifications': [{'Arn': a} for a in attrs.get('topics', ())]})
278 modified = []
279 for r in resources:
280 for k, v in attrs.items():
281 if k not in r or r[k] != v:
282 modified.append(r)
284 self.log.debug("Updating %d of %d ops items", len(modified), len(resources))
285 client = local_session(self.manager.session_factory).client('ssm')
286 for m in modified:
287 client.update_ops_item(OpsItemId=m['OpsItemId'], **attrs)
290class OpsItemFilter(Filter):
291 """Filter resources associated to extant OpsCenter operational items.
293 :example:
295 Find ec2 instances with open ops items.
297 .. code-block:: yaml
299 policies:
300 - name: ec2-instances-ops-items
301 resource: ec2
302 filters:
303 - type: ops-item
304 # we can filter on source, title, priority
305 priority: [1, 2]
306 """
308 schema = type_schema(
309 'ops-item',
310 status={'type': 'array',
311 'default': ['Open'],
312 'items': {'enum': ['Open', 'In progress', 'Resolved']}},
313 priority={'type': 'array', 'items': {'enum': list(range(1, 6))}},
314 title={'type': 'string'},
315 source={'type': 'string'})
316 schema_alias = True
317 permissions = ('ssm:DescribeOpsItems',)
319 def process(self, resources, event=None):
320 client = local_session(self.manager.session_factory).client('ssm')
321 results = []
323 for resource_set in chunks(resources, 10):
324 qf = self.get_query_filter(resource_set)
325 items = client.describe_ops_items(**qf).get('OpsItemSummaries')
327 arn_item_map = {}
328 for i in items:
329 for arn in json.loads(
330 i['OperationalData']['/aws/resources']['Value']):
331 arn_item_map.setdefault(arn['arn'], []).append(i['OpsItemId'])
333 for arn, r in zip(self.manager.get_arns(resource_set), resource_set):
334 if arn in arn_item_map:
335 r['c7n:opsitems'] = arn_item_map[arn]
336 results.append(r)
337 return results
339 def get_query_filter(self, resources):
340 q = []
341 q.append({'Key': 'Status', 'Operator': 'Equal',
342 'Values': self.data.get('status', ('Open',))})
343 if self.data.get('priority'):
344 q.append({'Key': 'Priority', 'Operator': 'Equal',
345 'Values': list(map(str, self.data['priority']))})
346 if self.data.get('title'):
347 q.append({'Key': 'Title', 'Operator': 'Contains',
348 'Values': [self.data['title']]})
349 if self.data.get('source'):
350 q.append({'Key': 'Source', 'Operator': 'Equal',
351 'Values': [self.data['source']]})
352 q.append({'Key': 'ResourceId', 'Operator': 'Contains',
353 'Values': [r[self.manager.resource_type.id] for r in resources]})
354 return {'OpsItemFilters': q}
356 @classmethod
357 def register_resource(cls, registry, resource_class):
358 if 'ops-item' not in resource_class.filter_registry:
359 resource_class.filter_registry.register('ops-item', cls)
362resources.subscribe(OpsItemFilter.register_resource)
365class PostItem(Action):
366 """Post an OpsItem to AWS Systems Manager OpsCenter Dashboard.
368 https://docs.aws.amazon.com/systems-manager/latest/userguide/OpsCenter.html
370 Each ops item supports up to a 100 associated resources. This
371 action supports the builtin OpsCenter dedup logic with additional
372 support for associating new resources to existing Open ops items.
374 : Example :
376 Create an ops item for ec2 instances with Create User permissions
378 .. code-block:: yaml
380 policies:
381 - name: over-privileged-ec2
382 resource: aws.ec2
383 filters:
384 - type: check-permissions
385 match: allowed
386 actions:
387 - iam:CreateUser
388 actions:
389 - type: post-item
390 priority: 3
392 The builtin OpsCenter dedup logic will kick in if the same
393 resource set (ec2 instances in this case) is posted for the same
394 policy.
396 : Example :
398 Create an ops item for sqs queues with cross account access as ops items.
400 .. code-block:: yaml
402 policies:
403 - name: sqs-cross-account-access
404 resource: aws.sqs
405 filters:
406 - type: cross-account
407 actions:
408 - type: mark-for-op
409 days: 5
410 op: delete
411 - type: post-item
412 title: SQS Cross Account Access
413 description: |
414 Cross Account Access detected in SQS resource IAM Policy.
415 tags:
416 Topic: Security
417 """
419 schema = type_schema(
420 'post-item',
421 description={'type': 'string'},
422 tags={'type': 'object'},
423 priority={'enum': list(range(1, 6))},
424 title={'type': 'string'},
425 topics={'type': 'string'},
426 )
427 schema_alias = True
428 permissions = ('ssm:CreateOpsItem',)
430 def process(self, resources, event=None):
431 client = local_session(self.manager.session_factory).client('ssm')
432 item_template = self.get_item_template()
433 resources = list(sorted(resources, key=operator.itemgetter(
434 self.manager.resource_type.id)))
435 items = self.get_items(client, item_template)
436 if items:
437 # - Use a copy of the template as we'll be passing in status changes on updates.
438 # - The return resources will be those that we couldn't fit into updates
439 # to existing resources.
440 resources = self.update_items(client, items, dict(item_template), resources)
442 item_ids = [i['OpsItemId'] for i in items[:5]]
444 for resource_set in chunks(resources, 100):
445 resource_arns = json.dumps(
446 [{'arn': arn} for arn in sorted(self.manager.get_arns(resource_set))])
447 item_template['OperationalData']['/aws/resources'] = {
448 'Type': 'SearchableString', 'Value': resource_arns}
449 if items:
450 item_template['RelatedOpsItems'] = [
451 {'OpsItemId': item_ids[:5]}]
452 try:
453 oid = client.create_ops_item(**item_template).get('OpsItemId')
454 item_ids.insert(0, oid)
455 except client.exceptions.OpsItemAlreadyExistsException:
456 pass
458 for r in resource_set:
459 r['c7n:opsitem'] = oid
461 def get_items(self, client, item_template):
462 qf = [
463 {'Key': 'OperationalDataValue',
464 'Operator': 'Contains',
465 'Values': [item_template['OperationalData'][
466 '/custodian/dedup']['Value']]},
467 {'Key': 'OperationalDataKey',
468 'Operator': 'Equal',
469 'Values': ['/custodian/dedup']},
470 {'Key': 'Status',
471 'Operator': 'Equal',
472 # In progress could imply activity/executions underway, we don't want to update
473 # the resource set out from underneath that so only look at Open state.
474 'Values': ['Open']},
475 {'Key': 'Source',
476 'Operator': 'Equal',
477 'Values': ['Cloud Custodian']}]
478 items = client.describe_ops_items(OpsItemFilters=qf)['OpsItemSummaries']
479 return list(sorted(items, key=operator.itemgetter('CreatedTime'), reverse=True))
481 def update_items(self, client, items, item_template, resources):
482 """Update existing Open OpsItems with new resources.
484 Originally this tried to support attribute updates as well, but
485 the reasoning around that is a bit complex due to partial state
486 evaluation around any given execution, so its restricted atm
487 to just updating associated resources.
489 For management of ops items, use a policy on the
490 ops-item resource.
492 Rationale: Typically a custodian policy will be evaluating
493 some partial set of resources at any given execution (ie think
494 a lambda looking at newly created resources), where as a
495 collection of ops center items will represent the total
496 set. Custodian can multiplex the partial set of resource over
497 a set of ops items (100 resources per item) which minimizes
498 the item count. When updating the state of an ops item though,
499 we have to contend with the possibility that we're doing so
500 with only a partial state. Which could be confusing if we
501 tried to set the Status to Resolved even if we're only evaluating
502 a handful of resources associated to an ops item.
503 """
504 arn_item_map = {}
505 item_arn_map = {}
506 for i in items:
507 item_arn_map[i['OpsItemId']] = arns = json.loads(
508 i['OperationalData']['/aws/resources']['Value'])
509 for arn in arns:
510 arn_item_map[arn['arn']] = i['OpsItemId']
512 arn_resource_map = dict(zip(self.manager.get_arns(resources), resources))
513 added = set(arn_resource_map).difference(arn_item_map)
515 updated = set()
516 remainder = []
518 # Check for resource additions
519 for a in added:
520 handled = False
521 for i in items:
522 if len(item_arn_map[i['OpsItemId']]) >= 100:
523 continue
524 item_arn_map[i['OpsItemId']].append({'arn': a})
525 updated.add(i['OpsItemId'])
526 arn_resource_map[a]['c7n:opsitem'] = i['OpsItemId']
527 handled = True
528 break
529 if not handled:
530 remainder.append(a)
532 for i in items:
533 if i['OpsItemId'] not in updated:
534 continue
535 i = dict(i)
536 for k in ('CreatedBy', 'CreatedTime', 'Source', 'LastModifiedBy',
537 'LastModifiedTime'):
538 i.pop(k, None)
539 i['OperationalData']['/aws/resources']['Value'] = json.dumps(
540 item_arn_map[i['OpsItemId']])
541 i['OperationalData'].pop('/aws/dedup', None)
542 client.update_ops_item(**i)
543 return remainder
545 def get_item_template(self):
546 title = self.data.get('title', self.manager.data['name']).strip()
547 dedup = ("%s %s %s %s" % (
548 title,
549 self.manager.type,
550 self.manager.config.region,
551 self.manager.config.account_id)).encode('utf8')
552 # size restrictions on this value is 4-20, digest is 32
553 dedup = hashlib.md5(dedup).hexdigest()[:20] # nosec nosemgrep
555 i = dict(
556 Title=title,
557 Description=self.data.get(
558 'description',
559 self.manager.data.get(
560 'description',
561 self.manager.data.get('name'))),
562 Priority=self.data.get('priority'),
563 Source="Cloud Custodian",
564 Tags=[{'Key': k, 'Value': v} for k, v in self.data.get(
565 'tags', self.manager.data.get('tags', {})).items()],
566 Notifications=[{'Arn': a} for a in self.data.get('topics', ())],
567 OperationalData={
568 '/aws/dedup': {
569 'Type': 'SearchableString',
570 'Value': json.dumps({'dedupString': dedup})},
571 '/custodian/execution-id': {
572 'Type': 'String',
573 'Value': self.manager.ctx.execution_id},
574 # We need our own dedup string to be able to filter
575 # search on it.
576 '/custodian/dedup': {
577 'Type': 'SearchableString',
578 'Value': dedup},
579 '/custodian/policy': {
580 'Type': 'String',
581 'Value': json.dumps(self.manager.data)},
582 '/custodian/version': {
583 'Type': 'String',
584 'Value': version},
585 '/custodian/policy-name': {
586 'Type': 'SearchableString',
587 'Value': self.manager.data['name']},
588 '/custodian/resource': {
589 'Type': 'SearchableString',
590 'Value': self.manager.type},
591 }
592 )
593 return filter_empty(i)
595 @classmethod
596 def register_resource(cls, registry, resource_class):
597 if 'post-item' not in resource_class.action_registry:
598 resource_class.action_registry.register('post-item', cls)
601resources.subscribe(PostItem.register_resource)
604@resources.register('ssm-document')
605class SSMDocument(QueryResourceManager):
607 class resource_type(TypeInfo):
608 service = 'ssm'
609 enum_spec = ('list_documents', 'DocumentIdentifiers', {'Filters': [
610 {
611 'Key': 'Owner',
612 'Values': ['Self']}]})
613 name = id = 'Name'
614 date = 'RegistrationDate'
615 arn_type = 'document'
617 permissions = ('ssm:ListDocuments',)
620@SSMDocument.filter_registry.register('cross-account')
621class SSMDocumentCrossAccount(CrossAccountAccessFilter):
622 """Filter SSM documents which have cross account permissions
624 :example:
626 .. code-block:: yaml
628 policies:
629 - name: ssm-cross-account
630 resource: ssm-document
631 filters:
632 - type: cross-account
633 whitelist: [xxxxxxxxxxxx]
634 """
636 permissions = ('ssm:DescribeDocumentPermission',)
638 def process(self, resources, event=None):
639 self.accounts = self.get_accounts()
640 results = []
641 client = local_session(self.manager.session_factory).client('ssm')
642 with self.executor_factory(max_workers=3) as w:
643 futures = []
644 for resource_set in chunks(resources, 10):
645 futures.append(w.submit(
646 self.process_resource_set, client, resource_set))
647 for f in as_completed(futures):
648 if f.exception():
649 self.log.error(
650 "Exception checking cross account access \n %s" % (
651 f.exception()))
652 continue
653 results.extend(f.result())
654 return results
656 def process_resource_set(self, client, resource_set):
657 results = []
658 for r in resource_set:
659 attrs = self.manager.retry(
660 client.describe_document_permission,
661 Name=r['Name'],
662 PermissionType='Share',
663 ignore_err_codes=('InvalidDocument',))['AccountSharingInfoList']
664 shared_accounts = {
665 g.get('AccountId') for g in attrs}
666 delta_accounts = shared_accounts.difference(self.accounts)
667 if delta_accounts:
668 r['c7n:CrossAccountViolations'] = list(delta_accounts)
669 results.append(r)
670 return results
673@SSMDocument.action_registry.register('set-sharing')
674class RemoveSharingSSMDocument(Action):
675 """Edit list of accounts that share permissions on an SSM document. Pass in a list of account
676 IDs to the 'add' or 'remove' fields to edit document sharing permissions.
677 Set 'remove' to 'matched' to automatically remove any external accounts on a
678 document (use in conjunction with the cross-account filter).
680 :example:
682 .. code-block:: yaml
684 policies:
685 - name: ssm-set-sharing
686 resource: ssm-document
687 filters:
688 - type: cross-account
689 whitelist: [xxxxxxxxxxxx]
690 actions:
691 - type: set-sharing
692 add: [yyyyyyyyyy]
693 remove: matched
694 """
696 schema = type_schema('set-sharing',
697 remove={
698 'oneOf': [
699 {'enum': ['matched']},
700 {'type': 'array', 'items': {
701 'type': 'string'}},
702 ]},
703 add={
704 'type': 'array', 'items': {
705 'type': 'string'}})
706 permissions = ('ssm:ModifyDocumentPermission',)
708 def process(self, resources):
709 client = local_session(self.manager.session_factory).client('ssm')
710 add_accounts = self.data.get('add', [])
711 remove_accounts = self.data.get('remove', [])
712 if self.data.get('remove') == 'matched':
713 for r in resources:
714 try:
715 client.modify_document_permission(
716 Name=r['Name'],
717 PermissionType='Share',
718 AccountIdsToAdd=add_accounts,
719 AccountIdsToRemove=r['c7n:CrossAccountViolations']
720 )
721 except client.exceptions.InvalidDocumentOperation as e:
722 raise e
723 else:
724 for r in resources:
725 try:
726 client.modify_document_permission(
727 Name=r['Name'],
728 PermissionType='Share',
729 AccountIdsToAdd=add_accounts,
730 AccountIdsToRemove=remove_accounts
731 )
732 except client.exceptions.InvalidDocumentOperation as e:
733 raise e
736@SSMDocument.action_registry.register('delete')
737class DeleteSSMDocument(Action):
738 """Delete SSM documents. Set force flag to True to force delete on documents that are
739 shared across accounts. This will remove those shared accounts, and then delete the document.
740 Otherwise, delete will fail and raise InvalidDocumentOperation exception
741 if a document is shared with other accounts. Default value for force is False.
743 :example:
745 .. code-block:: yaml
747 policies:
748 - name: ssm-delete-documents
749 resource: ssm-document
750 filters:
751 - type: cross-account
752 whitelist: [xxxxxxxxxxxx]
753 actions:
754 - type: delete
755 force: True
756 """
758 schema = type_schema(
759 'delete',
760 force={'type': 'boolean'}
761 )
763 permissions = ('ssm:DeleteDocument', 'ssm:ModifyDocumentPermission',)
765 def process(self, resources):
766 client = local_session(self.manager.session_factory).client('ssm')
767 for r in resources:
768 try:
769 client.delete_document(Name=r['Name'], Force=True)
770 except client.exceptions.InvalidDocumentOperation as e:
771 if self.data.get('force', False):
772 response = client.describe_document_permission(
773 Name=r['Name'],
774 PermissionType='Share'
775 )
776 client.modify_document_permission(
777 Name=r['Name'],
778 PermissionType='Share',
779 AccountIdsToRemove=response.get('AccountIds', [])
780 )
781 client.delete_document(
782 Name=r['Name'],
783 Force=True
784 )
785 else:
786 raise e
789@resources.register('ssm-data-sync')
790class SSMDataSync(QueryResourceManager):
791 """Resource for AWS DataSync
792 https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-datasync.html
793 """
794 class resource_type(TypeInfo):
796 enum_spec = ('list_resource_data_sync', 'ResourceDataSyncItems', None)
797 service = 'ssm'
798 arn_type = 'resource-data-sync'
799 id = name = 'SyncName'
801 permissions = ('ssm:ListResourceDataSync',)
804@SSMDataSync.filter_registry.register('kms-key')
805class KmsFilter(KmsRelatedFilter):
806 RelatedIdsExpression = 'S3Destination.AWSKMSKeyARN'
809@SSMDataSync.action_registry.register('delete')
810class DeleteDataSync(Action):
811 """Delete SSM data sync resources.
813 :example:
815 .. code-block:: yaml
817 policies:
818 - name: delete-resource-data-sync
819 resource: ssm-data-sync
820 actions:
821 - type: delete
822 """
823 permissions = ('ssm:DeleteResourceDataSync',)
824 schema = type_schema('delete')
826 def process(self, resources):
827 client = local_session(self.manager.session_factory).client('ssm')
828 for r in resources:
829 try:
830 client.delete_resource_data_sync(SyncName=r['SyncName'])
831 except client.exceptions.ResourceDataSyncNotFoundException:
832 continue
836@resources.register("ssm-patch-group")
837class SsmPatchGroup(QueryResourceManager):
838 class resource_type(TypeInfo):
839 service = "ssm"
840 enum_spec = ('describe_patch_groups', 'Mappings', None)
841 arn = False
842 id = "PatchGroup"
843 name = "PatchGroup"
846@resources.register('ssm-session-manager')
847class SSMSessionManager(QueryResourceManager):
849 class resource_type(TypeInfo):
850 service = 'ssm'
851 enum_spec = ('describe_sessions', 'Sessions', None)
852 name = "SessionId"
853 id = "SessionId"
854 arn_type = 'session'
856 retry = staticmethod(get_retry(('Throttled',)))
857 permissions = ('ssm:DescribeSessions', 'ssm:TerminateSession', )
859 augment = universal_augment
861 def resources(self, query=None):
862 if query is None:
863 query = {}
864 if 'State' not in query:
865 # Default to Active if not given
866 query['State'] = 'Active'
867 return super(SSMSessionManager, self).resources(query=query)
869@SSMSessionManager.action_registry.register('terminate')
870class TerminateSession(Action):
871 """ Terminate a session.
873 This call will permanently end a session's connection to an instance.
875 :Example:
877 .. code-block:: yaml
879 policies:
880 - name: ssm-session-termination
881 resource: ssm-session-manager
882 actions:
883 - terminate
884 """
885 schema = type_schema('terminate')
887 def get_permissions(self):
888 return ('ssm:TerminateSession',)
890 def process(self, resources):
891 client = local_session(self.manager.session_factory).client('ssm')
892 for r in resources:
893 client.terminate_session(SessionId=r['SessionId'])