Repository: cloud-custodian/cloud-custodian

Issue:
AWS.EC2 - SSM Compliance Support via Config-Filter
**Description**
Currently, you cannot leverage config-filter to query SSM Patch Compliance or SSM Association Compliance.  This is because Custodian queries config for `AWS::EC2::Instance` type which does not expose SSM compliance status. The two types for querying SSM Compliance status of an EC2 are `AWS::Config::ResourceCompliance` and `AWS::SSM::ManagedInstanceInventory`.

Amazon pre-packages two great config rules:
  * [Patch Compliance](https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-patch-compliance-status-check.html)
  * [Association Compliance](https://docs.aws.amazon.com/config/latest/developerguide/ec2-managedinstance-association-compliance-status-check.html)

The objective is to filter EC2 instances which are not compliant with these config rules and take appropriate action (notify resource owner, tag for remediation, etc.)

Ultimately, I am looking to push these two via Organization Config in addition to EC2_INSTANCE_MANAGED_BY_SSM.  By implementing all three of these together, we can effectively evaluate and enforce policy compliance on the EC2 fleet at the organization level, feed this information to Security Hub, and hook into Config & SecHub for remediation via Custodian.

**Solution**
A. Enhance config-filter to support overriding the resource type to query
B. Add a new filter type (maybe a superclass of config-filter) specific to EC2 to achieve the same.

**Alternatives**
If I am not able to do this, I will write a lambda which will tag instances with their compliance status whenever the SSM Patch Compliance or SSM Association Compliance status changes.


**Thoughts**

https://github.com/cloud-custodian/cloud-custodian/blob/cc7a6dc2927ea94e71c313627cab24284bb31b16/c7n/resources/ec2.py#L66


Maybe we can query Config for a specific instance via resource ID or if gathering an array in the return, check for where resourceId ends with the instance-id instead of resourceId == instance-id

#### Example Resource ID:
```
AWS::SSM::PatchCompliance/AWS::SSM::ManagedInstanceInventory/i-0050852ea15a9ea0f
```

#### Example Query:
```
SELECT
  configuration,
  *
WHERE
  resourceType = 'AWS::Config::ResourceCompliance'
  AND configuration.targetResourceType = 'AWS::SSM::PatchCompliance'
  AND configuration.complianceType = 'NON_COMPLIANT'

# Can also filter for an instance id
AND resourceId LIKE 'AWS::SSM::PatchCompliance%i-0012345ab23a2da0f'
```


Discussion:
i think we need to do B because we need to match on the ids specific to the idiosyncracies of this specific checks (patch-compliance). ec2 ssm association filter is already available and can check status, last ping date etc.
Agreed.  I'd call it "ssm-compliance" as their are a bunch of other pre-canned AWS Config rules pertaining pertaining to SSM-Managed instances.

* ec2-managedinstance-applications-blacklisted
* ec2-managedinstance-applications-required
* ec2-managedinstance-association-compliance-status-check
* ec2-managedinstance-inventory-blacklisted
* ec2-managedinstance-patch-compliance-status-check
* ec2-managedinstance-platform-check

I don't know that we would bother to query config, the canned config rules are pretty thin and security via obscurity by nature of closed source, we can just go to ssm and query the info directly, perhaps optionally if config is enabled.
hmm.. ic, so having an ssm-config-compliance across those set of checks as distinct from a checking ssm directly.

Resolving patch:
diff --git a/c7n/resources/ec2.py b/c7n/resources/ec2.py
--- a/c7n/resources/ec2.py
+++ b/c7n/resources/ec2.py
@@ -920,6 +920,112 @@ def process_resource_set(self, client, resources):
             r[self.annotation] = info_map.get(r['InstanceId'], {})
 
 
+@EC2.filter_registry.register('ssm-compliance')
+class SsmCompliance(Filter):
+    """Filter ec2 instances by their ssm compliance status.
+
+    :Example:
+
+    Find non-compliant ec2 instances.
+
+    .. code-block:: yaml
+
+        policies:
+          - name: ec2-ssm-compliance
+            resource: ec2
+            filters:
+              - type: ssm-compliance
+                compliance_types:
+                  - Association
+                  - Patch
+                severity:
+                  - CRITICAL
+                  - HIGH
+                  - MEDIUM
+                  - LOW
+                  - UNSPECIFIED
+                states:
+                  - NON_COMPLIANT
+                eval_filters:
+                 - type: value
+                   key: ExecutionSummary.ExecutionTime
+                   value_type: age
+                   value: 30
+                   op: less-than
+    """
+    schema = type_schema(
+        'ssm-compliance',
+        **{'required': ['compliance_types'],
+           'compliance_types': {'type': 'array', 'items': {'type': 'string'}},
+           'severity': {'type': 'array', 'items': {'type': 'string'}},
+           'op': {'enum': ['or', 'and']},
+           'eval_filters': {'type': 'array', 'items': {
+                            'oneOf': [
+                                {'$ref': '#/definitions/filters/valuekv'},
+                                {'$ref': '#/definitions/filters/value'}]}},
+           'states': {'type': 'array',
+                      'default': ['NON_COMPLIANT'],
+                      'items': {
+                          'enum': [
+                              'COMPLIANT',
+                              'NON_COMPLIANT'
+                          ]}}})
+    permissions = ('ssm:ListResourceComplianceSummaries',)
+    annotation = 'c7n:ssm-compliance'
+
+    def process(self, resources, event=None):
+        op = self.data.get('op', 'or') == 'or' and any or all
+        eval_filters = []
+        for f in self.data.get('eval_filters', ()):
+            vf = ValueFilter(f)
+            vf.annotate = False
+            eval_filters.append(vf)
+
+        client = utils.local_session(self.manager.session_factory).client('ssm')
+        filters = [
+            {
+                'Key': 'Status',
+                'Values': self.data['states'],
+                'Type': 'EQUAL'
+            },
+            {
+                'Key': 'ComplianceType',
+                'Values': self.data['compliance_types'],
+                'Type': 'EQUAL'
+            }
+        ]
+        severity = self.data.get('severity')
+        if severity:
+            filters.append(
+                {
+                    'Key': 'OverallSeverity',
+                    'Values': severity,
+                    'Type': 'EQUAL'
+                })
+
+        resource_map = {}
+        pager = client.get_paginator('list_resource_compliance_summaries')
+        for page in pager.paginate(Filters=filters):
+            items = page['ResourceComplianceSummaryItems']
+            for i in items:
+                if not eval_filters:
+                    resource_map.setdefault(
+                        i['ResourceId'], []).append(i)
+                    continue
+                if op([f.match(i) for f in eval_filters]):
+                    resource_map.setdefault(
+                        i['ResourceId'], []).append(i)
+
+        results = []
+        for r in resources:
+            result = resource_map.get(r['InstanceId'])
+            if result:
+                r[self.annotation] = result
+                results.append(r)
+
+        return results
+
+
 @actions.register('set-monitoring')
 class MonitorInstances(BaseAction, StateTransitionFilter):
     """Action on EC2 Instances to enable/disable detailed monitoring


Test patch:
diff --git a/tests/data/placebo/test_ec2_ssm_compliance_filter/ec2.DescribeInstances_1.json b/tests/data/placebo/test_ec2_ssm_compliance_filter/ec2.DescribeInstances_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ec2_ssm_compliance_filter/ec2.DescribeInstances_1.json
@@ -0,0 +1,326 @@
+{
+    "status_code": 200,
+    "data": {
+        "Reservations": [
+            {
+                "Groups": [],
+                "Instances": [
+                    {
+                        "AmiLaunchIndex": 0,
+                        "ImageId": "ami-0f65671a86f061fcd",
+                        "InstanceId": "i-0dea82d960d56dc1d",
+                        "InstanceType": "t2.medium",
+                        "KeyName": "ssm-dev",
+                        "LaunchTime": {
+                            "__class__": "datetime",
+                            "year": 2019,
+                            "month": 1,
+                            "day": 30,
+                            "hour": 13,
+                            "minute": 14,
+                            "second": 36,
+                            "microsecond": 0
+                        },
+                        "Monitoring": {
+                            "State": "disabled"
+                        },
+                        "Placement": {
+                            "AvailabilityZone": "us-east-2a",
+                            "GroupName": "",
+                            "Tenancy": "default"
+                        },
+                        "PrivateDnsName": "ip-172-31-11-50.us-east-2.compute.internal",
+                        "PrivateIpAddress": "172.31.11.50",
+                        "ProductCodes": [],
+                        "PublicDnsName": "ec2-18-191-215-59.us-east-2.compute.amazonaws.com",
+                        "PublicIpAddress": "18.191.215.59",
+                        "State": {
+                            "Code": 64,
+                            "Name": "stopping"
+                        },
+                        "StateTransitionReason": "User initiated (2019-01-30 13:33:45 GMT)",
+                        "SubnetId": "subnet-db9f62b2",
+                        "VpcId": "vpc-5f6c9936",
+                        "Architecture": "x86_64",
+                        "BlockDeviceMappings": [
+                            {
+                                "DeviceName": "/dev/sda1",
+                                "Ebs": {
+                                    "AttachTime": {
+                                        "__class__": "datetime",
+                                        "year": 2019,
+                                        "month": 1,
+                                        "day": 30,
+                                        "hour": 13,
+                                        "minute": 14,
+                                        "second": 36,
+                                        "microsecond": 0
+                                    },
+                                    "DeleteOnTermination": true,
+                                    "Status": "attached",
+                                    "VolumeId": "vol-08d1573a7cd3fd1f2"
+                                }
+                            }
+                        ],
+                        "ClientToken": "",
+                        "EbsOptimized": false,
+                        "EnaSupport": true,
+                        "Hypervisor": "xen",
+                        "IamInstanceProfile": {
+                            "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
+                            "Id": "AIPAJO5EMJ3YEMNUF5ZWM"
+                        },
+                        "NetworkInterfaces": [
+                            {
+                                "Association": {
+                                    "IpOwnerId": "amazon",
+                                    "PublicDnsName": "ec2-18-191-215-59.us-east-2.compute.amazonaws.com",
+                                    "PublicIp": "18.191.215.59"
+                                },
+                                "Attachment": {
+                                    "AttachTime": {
+                                        "__class__": "datetime",
+                                        "year": 2019,
+                                        "month": 1,
+                                        "day": 30,
+                                        "hour": 13,
+                                        "minute": 14,
+                                        "second": 36,
+                                        "microsecond": 0
+                                    },
+                                    "AttachmentId": "eni-attach-0bc8d36721d13f0a6",
+                                    "DeleteOnTermination": true,
+                                    "DeviceIndex": 0,
+                                    "Status": "attached"
+                                },
+                                "Description": "Primary network interface",
+                                "Groups": [
+                                    {
+                                        "GroupName": "launch-wizard-1",
+                                        "GroupId": "sg-bebafdd5"
+                                    }
+                                ],
+                                "Ipv6Addresses": [],
+                                "MacAddress": "02:27:4e:43:cb:e6",
+                                "NetworkInterfaceId": "eni-01075318d08f25bf5",
+                                "OwnerId": "644160558196",
+                                "PrivateDnsName": "ip-172-31-11-50.us-east-2.compute.internal",
+                                "PrivateIpAddress": "172.31.11.50",
+                                "PrivateIpAddresses": [
+                                    {
+                                        "Association": {
+                                            "IpOwnerId": "amazon",
+                                            "PublicDnsName": "ec2-18-191-215-59.us-east-2.compute.amazonaws.com",
+                                            "PublicIp": "18.191.215.59"
+                                        },
+                                        "Primary": true,
+                                        "PrivateDnsName": "ip-172-31-11-50.us-east-2.compute.internal",
+                                        "PrivateIpAddress": "172.31.11.50"
+                                    }
+                                ],
+                                "SourceDestCheck": true,
+                                "Status": "in-use",
+                                "SubnetId": "subnet-db9f62b2",
+                                "VpcId": "vpc-5f6c9936"
+                            }
+                        ],
+                        "RootDeviceName": "/dev/sda1",
+                        "RootDeviceType": "ebs",
+                        "SecurityGroups": [
+                            {
+                                "GroupName": "launch-wizard-1",
+                                "GroupId": "sg-bebafdd5"
+                            }
+                        ],
+                        "SourceDestCheck": true,
+                        "StateReason": {
+                            "Code": "Client.UserInitiatedShutdown",
+                            "Message": "Client.UserInitiatedShutdown: User initiated shutdown"
+                        },
+                        "Tags": [
+                            {
+                                "Key": "Name",
+                                "Value": "kapil-ssm-dev"
+                            }
+                        ],
+                        "VirtualizationType": "hvm",
+                        "CpuOptions": {
+                            "CoreCount": 2,
+                            "ThreadsPerCore": 1
+                        },
+                        "HibernationOptions": {
+                            "Configured": false
+                        }
+                    }
+                ],
+                "OwnerId": "644160558196",
+                "ReservationId": "r-07103c7504d70d128"
+            },
+            {
+                "Groups": [],
+                "Instances": [
+                    {
+                        "AmiLaunchIndex": 0,
+                        "ImageId": "ami-0f65671a86f061fcd",
+                        "InstanceId": "i-0ba3874e85bb97244",
+                        "InstanceType": "t2.medium",
+                        "KeyName": "ssm-dev",
+                        "LaunchTime": {
+                            "__class__": "datetime",
+                            "year": 2019,
+                            "month": 1,
+                            "day": 30,
+                            "hour": 11,
+                            "minute": 58,
+                            "second": 37,
+                            "microsecond": 0
+                        },
+                        "Monitoring": {
+                            "State": "disabled"
+                        },
+                        "Placement": {
+                            "AvailabilityZone": "us-east-2a",
+                            "GroupName": "",
+                            "Tenancy": "default"
+                        },
+                        "PrivateDnsName": "ip-172-31-10-93.us-east-2.compute.internal",
+                        "PrivateIpAddress": "172.31.10.93",
+                        "ProductCodes": [],
+                        "PublicDnsName": "ec2-18-222-166-169.us-east-2.compute.amazonaws.com",
+                        "PublicIpAddress": "18.222.166.169",
+                        "State": {
+                            "Code": 16,
+                            "Name": "running"
+                        },
+                        "StateTransitionReason": "",
+                        "SubnetId": "subnet-db9f62b2",
+                        "VpcId": "vpc-5f6c9936",
+                        "Architecture": "x86_64",
+                        "BlockDeviceMappings": [
+                            {
+                                "DeviceName": "/dev/sda1",
+                                "Ebs": {
+                                    "AttachTime": {
+                                        "__class__": "datetime",
+                                        "year": 2019,
+                                        "month": 1,
+                                        "day": 30,
+                                        "hour": 11,
+                                        "minute": 58,
+                                        "second": 37,
+                                        "microsecond": 0
+                                    },
+                                    "DeleteOnTermination": true,
+                                    "Status": "attached",
+                                    "VolumeId": "vol-0fcfccae49c73e29a"
+                                }
+                            }
+                        ],
+                        "ClientToken": "",
+                        "EbsOptimized": false,
+                        "EnaSupport": true,
+                        "Hypervisor": "xen",
+                        "IamInstanceProfile": {
+                            "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
+                            "Id": "AIPAJO5EMJ3YEMNUF5ZWM"
+                        },
+                        "NetworkInterfaces": [
+                            {
+                                "Association": {
+                                    "IpOwnerId": "amazon",
+                                    "PublicDnsName": "ec2-18-222-166-169.us-east-2.compute.amazonaws.com",
+                                    "PublicIp": "18.222.166.169"
+                                },
+                                "Attachment": {
+                                    "AttachTime": {
+                                        "__class__": "datetime",
+                                        "year": 2019,
+                                        "month": 1,
+                                        "day": 30,
+                                        "hour": 11,
+                                        "minute": 58,
+                                        "second": 37,
+                                        "microsecond": 0
+                                    },
+                                    "AttachmentId": "eni-attach-0dbd4eeabb5c3ffa0",
+                                    "DeleteOnTermination": true,
+                                    "DeviceIndex": 0,
+                                    "Status": "attached"
+                                },
+                                "Description": "",
+                                "Groups": [
+                                    {
+                                        "GroupName": "launch-wizard-1",
+                                        "GroupId": "sg-bebafdd5"
+                                    }
+                                ],
+                                "Ipv6Addresses": [],
+                                "MacAddress": "02:ac:dd:b6:54:8a",
+                                "NetworkInterfaceId": "eni-0fe02396b03961784",
+                                "OwnerId": "644160558196",
+                                "PrivateDnsName": "ip-172-31-10-93.us-east-2.compute.internal",
+                                "PrivateIpAddress": "172.31.10.93",
+                                "PrivateIpAddresses": [
+                                    {
+                                        "Association": {
+                                            "IpOwnerId": "amazon",
+                                            "PublicDnsName": "ec2-18-222-166-169.us-east-2.compute.amazonaws.com",
+                                            "PublicIp": "18.222.166.169"
+                                        },
+                                        "Primary": true,
+                                        "PrivateDnsName": "ip-172-31-10-93.us-east-2.compute.internal",
+                                        "PrivateIpAddress": "172.31.10.93"
+                                    }
+                                ],
+                                "SourceDestCheck": true,
+                                "Status": "in-use",
+                                "SubnetId": "subnet-db9f62b2",
+                                "VpcId": "vpc-5f6c9936"
+                            }
+                        ],
+                        "RootDeviceName": "/dev/sda1",
+                        "RootDeviceType": "ebs",
+                        "SecurityGroups": [
+                            {
+                                "GroupName": "launch-wizard-1",
+                                "GroupId": "sg-bebafdd5"
+                            }
+                        ],
+                        "SourceDestCheck": true,
+                        "Tags": [
+                            {
+                                "Key": "Name",
+                                "Value": "kapil-ssm-dev"
+                            }
+                        ],
+                        "VirtualizationType": "hvm",
+                        "CpuOptions": {
+                            "CoreCount": 2,
+                            "ThreadsPerCore": 1
+                        },
+                        "CapacityReservationSpecification": {
+                            "CapacityReservationPreference": "open"
+                        },
+                        "HibernationOptions": {
+                            "Configured": false
+                        }
+                    }
+                ],
+                "OwnerId": "644160558196",
+                "ReservationId": "r-074f65542bb72fd56"
+            }
+        ],
+        "ResponseMetadata": {
+            "RequestId": "8151c8c3-afeb-4a62-b10d-b801d72a7458",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "content-type": "text/xml;charset=UTF-8",
+                "transfer-encoding": "chunked",
+                "vary": "Accept-Encoding",
+                "date": "Wed, 30 Jan 2019 13:34:16 GMT",
+                "server": "AmazonEC2"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
diff --git a/tests/data/placebo/test_ec2_ssm_compliance_filter/ssm.ListResourceComplianceSummaries_1.json b/tests/data/placebo/test_ec2_ssm_compliance_filter/ssm.ListResourceComplianceSummaries_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ec2_ssm_compliance_filter/ssm.ListResourceComplianceSummaries_1.json
@@ -0,0 +1,150 @@
+{
+  "status_code": 200,
+  "data": {
+    "ResourceComplianceSummaryItems": [
+      {
+        "ComplianceType": "Association",
+        "CompliantSummary": {
+          "CompliantCount": 1,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 1
+          }
+        },
+        "ExecutionSummary": {
+          "ExecutionTime": "2020-03-20 10:52:50-04:00"
+        },
+        "NonCompliantSummary": {
+          "NonCompliantCount": 3,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 3
+          }
+        },
+        "OverallSeverity": "UNSPECIFIED",
+        "ResourceId": "i-0dea82d960d56dc1d",
+        "ResourceType": "ManagedInstance",
+        "Status": "NON_COMPLIANT"
+      },
+      {
+        "ComplianceType": "Patch",
+        "CompliantSummary": {
+          "CompliantCount": 310,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 310
+          }
+        },
+        "ExecutionSummary": {
+          "ExecutionId": "e44bffd7-4017-44ec-80f9-a6fbf932672f",
+          "ExecutionTime": "2020-03-19 18:50:59-04:00",
+          "ExecutionType": "Command"
+        },
+        "NonCompliantSummary": {
+          "NonCompliantCount": 36,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 36
+          }
+        },
+        "OverallSeverity": "UNSPECIFIED",
+        "ResourceId": "i-0dea82d960d56dc1d",
+        "ResourceType": "ManagedInstance",
+        "Status": "NON_COMPLIANT"
+      },
+      {
+        "ComplianceType": "Association",
+        "CompliantSummary": {
+          "CompliantCount": 1,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 1
+          }
+        },
+        "ExecutionSummary": {
+          "ExecutionTime": "2020-03-20 10:52:50-04:00"
+        },
+        "NonCompliantSummary": {
+          "NonCompliantCount": 3,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 3
+          }
+        },
+        "OverallSeverity": "UNSPECIFIED",
+        "ResourceId": "i-0ba3874e85bb97244",
+        "ResourceType": "ManagedInstance",
+        "Status": "NON_COMPLIANT"
+      },
+      {
+        "ComplianceType": "Patch",
+        "CompliantSummary": {
+          "CompliantCount": 310,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 310
+          }
+        },
+        "ExecutionSummary": {
+          "ExecutionId": "e44bffd7-4017-44ec-80f9-a6fbf932672f",
+          "ExecutionTime": "2020-03-19 18:50:59-04:00",
+          "ExecutionType": "Command"
+        },
+        "NonCompliantSummary": {
+          "NonCompliantCount": 36,
+          "SeveritySummary": {
+            "CriticalCount": 0,
+            "HighCount": 0,
+            "InformationalCount": 0,
+            "LowCount": 0,
+            "MediumCount": 0,
+            "UnspecifiedCount": 36
+          }
+        },
+        "OverallSeverity": "UNSPECIFIED",
+        "ResourceId": "i-0ba3874e85bb97244",
+        "ResourceType": "ManagedInstance",
+        "Status": "NON_COMPLIANT"
+      }
+    ],
+    "ResponseMetadata": {
+      "HTTPHeaders": {
+        "content-length": "12345",
+        "content-type": "application/x-amz-json-1.1",
+        "date": "Fri, 20 Mar 2020 15:09:20 GMT",
+        "x-amzn-requestid": "88383b45-36f4-44bb-b8a6-942dd5ffca3b"
+      },
+      "HTTPStatusCode": 200,
+      "RequestId": "88383b45-36f4-44bb-b8a6-942dd5ffca3b",
+      "RetryAttempts": 0
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/test_ec2.py b/tests/test_ec2.py
--- a/tests/test_ec2.py
+++ b/tests/test_ec2.py
@@ -263,6 +263,34 @@ def test_ssm_status(self):
             [r['InstanceId'] for r in resources],
             ['i-0dea82d960d56dc1d', 'i-0ba3874e85bb97244'])
 
+    def test_ssm_compliance(self):
+        session_factory = self.replay_flight_data('test_ec2_ssm_compliance_filter')
+        policy = self.load_policy({
+            'name': 'ec2-ssm-compliance',
+            'resource': 'aws.ec2',
+            'filters': [
+                {'type': 'ssm-compliance',
+                 'compliance_types': [
+                     'Association',
+                     'Patch'
+                 ],
+                 'severity': [
+                     'CRITICAL',
+                     'HIGH',
+                     'MEDIUM',
+                     'LOW',
+                     'UNSPECIFIED'
+                 ],
+                 'states': ['NON_COMPLIANT']}]},
+            session_factory=session_factory,
+            config={'region': 'us-east-2'})
+        resources = policy.run()
+        self.assertEqual(len(resources), 2)
+        self.assertTrue('c7n:ssm-compliance' in resources[0])
+        self.assertEqual(
+            [r['InstanceId'] for r in resources],
+            ['i-0dea82d960d56dc1d', 'i-0ba3874e85bb97244'])
+
 
 class TestHealthEventsFilter(BaseTest):