Repository: cloud-custodian/cloud-custodian

Issue:
SecurityHub finding doesn't work for rds-cluster-snapshot resource in config-rule mode
**Describe the bug**
SecurityHub post-finding doesn't work on rds-cluster-snapshot resource in config-rule policy mode. This is because the id/name/arn for rds-cluster-snapshot is slightly different when coming from AWS Config vs when doing Describe

**To Reproduce**
Steps to reproduce the behavior:
* Set up a policy to run in config-rule mode for rds-cluster-snapshot resource, filter on StorageEncrypted=false with action = post-finding to security hub.
* For example:
```
policies:
  - name: ddao-rds-cluster-snapshot
    resource: rds-cluster-snapshot
    mode:
       type: config-rule
       timeout: 300
       role: arn:aws:iam::123456789012:role/CustodianRole
    filters:
      - type: value
        key: "StorageEncrypted"
        value: false
    action:
      - type: post-finding
        severity_normalized: 30 
        types:
          - "Software and Configuration Checks/AWS Security Best Practices"
```
* Create a snapshot for an encrypted RDS DB cluster

**Expected behavior**
A finding should be sent to security hub regarding the unencrypted RDS cluster snapshot

**Actual behavior**
Cloud Custodian threw the following error:
```
{
  "errorMessage": "'DBClusterSnapshotIdentifier'",
  "errorType": "KeyError",
  "stackTrace": [
    "  File \"/var/task/custodian_policy.py\", line 6, in run\n    return handler.dispatch_event(event, context)\n",
    "  File \"/var/task/c7n/handler.py\", line 176, in dispatch_event\n    p.push(event, context)\n",
    "  File \"/var/task/c7n/policy.py\", line 1130, in push\n    return mode.run(event, lambda_ctx)\n",
    "  File \"/var/task/c7n/policy.py\", line 854, in run\n    resources = super(ConfigRuleMode, self).run(event, lambda_context)\n",
    "  File \"/var/task/c7n/policy.py\", line 461, in run\n    return self.run_resource_set(event, resources)\n",
    "  File \"/var/task/c7n/policy.py\", line 491, in run_resource_set\n    results = action.process(resources)\n",
    "  File \"/var/task/c7n/resources/securityhub.py\", line 419, in process\n    finding = self.get_finding(\n",
    "  File \"/var/task/c7n/resources/securityhub.py\", line 467, in get_finding\n    [r[model.id] for r in resources]))).encode(\n",
    "  File \"/var/task/c7n/resources/securityhub.py\", line 467, in <listcomp>\n    [r[model.id] for r in resources]))).encode(\n"
  ]
}
```

**Additional context**
I did some digging into the code and here's the problem: For rds-cluster-snapshot, we have its name and id set to `DBClusterSnapshotIdentifier ` (https://github.com/cloud-custodian/cloud-custodian/blob/05b7d2b95e9f879b2d94b644160630f0c04e923e/c7n/resources/rdscluster.py#L375), but when the policy is running in config-rule mode, AWS Config has the resource name/id as `dbclusterSnapshotIdentifier `, which then gets turned into `DbclusterSnapshotIdentifier` when we do `resolve_resource`. Then, in the get_finding method of securityhub, we expect the dictionary to have `DBClusterSnapshotIdentifier` as the key, but it's not there so things blow up. https://github.com/cloud-custodian/cloud-custodian/blob/05b7d2b95e9f879b2d94b644160630f0c04e923e/c7n/resources/securityhub.py#L466

I was thinking of submitting a PR to do a quick fix like this
```
@resources.register('rds-cluster-snapshot')
class RDSClusterSnapshot(QueryResourceManager):
    """Resource manager for RDS cluster snapshots.
    """

    class resource_type(TypeInfo):
        service = 'rds'
        arn_type = 'cluster-snapshot'
        arn_separator = ':'
        arn = 'DBClusterSnapshotArn'
        enum_spec = (
            'describe_db_cluster_snapshots', 'DBClusterSnapshots', None)
        name = id = 'DBClusterSnapshotIdentifier'
        date = 'SnapshotCreateTime'
        universal_tagging = object()
        config_type = 'AWS::RDS::DBClusterSnapshot'
        permissions_enum = ('rds:DescribeDBClusterSnapshots',)

    source_mapping = {
        'describe': DescribeClusterSnapshot,
        'config': ConfigSource
    }

    def get_source(self, source_type):
        # Fixing id, name and arn of the resource for when it comes from AWS Config
        if source_type == 'config':
            self.resource_type.id = 'DbclusterSnapshotIdentifier'
            self.resource_type.name = 'DbclusterSnapshotIdentifier'
            self.resource_type.arn = 'DbclusterSnapshotArn'
        return super().get_source(source_type)
```

but I think the problem might be there in other resources as well and was thinking if we need to have a more generic or standard fix. Ultimately, the problem is with AWS Config having different conventions for naming things. I wonder if the proper fix is to do something like what you have started doing with config_type vs cfn_type, meaning we now have config_id, config_name, config_arn vs the regular id, name and arn.

**Background (please complete the following information):**
 - OS: [e.g. OSX 10.15]
 - Python Version: [e.g. python 3.8.1]
 - Custodian Version: latest from master
 - Cloud Provider: aws
 - Policy: See above
 - Traceback: [if applicable, please exclude sensitive/account information]
 - `custodian version --debug` output
```
$ custodian version --debug
Custodian:   0.8.46.1
Python:      3.7.6 (default, Dec 30 2019, 19:38:36) 
             [Clang 10.0.0 (clang-1000.11.45.5)]
Platform:    posix.uname_result(sysname='Darwin', nodename='SDGL1607c2b92.local', release='17.7.0', version='Darwin Kernel Version 17.7.0: Sun Dec  1 19:19:56 PST 2019; root:xnu-4570.71.63~1/RELEASE_X86_64', machine='x86_64')
Using venv:  False
Docker: False
PYTHONPATH: 
[            '/Users/ddao/sandbox/c7n/custodian/bin',
             '/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
             '/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
             '/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
             '/Users/ddao/sandbox/c7n/custodian/lib/python3.7/site-packages']
```




Discussion:
we use a config source to normalize the bespoke idiosyncratic formatting of config to describe format. ie the goal is that policies are isomorphic wrt to execution mode without filter/action changes. The extant automated conversion for config is likely turning this to Db instead of DB, which needs customization for this and other DB prefixed fields.
@kapilt Maybe update the camelResource() method in utils.py to something like this?
```
def camelResource(obj):
    """Some sources from apis return lowerCased where as describe calls

    always return TitleCase, this function turns the former to the later
    """
    if not isinstance(obj, dict):
        return obj
    for k in list(obj.keys()):
        v = obj.pop(k)
        if len(k) > 3 and k.startswith("db"):
            k = "%s%s" % (k[0:3].upper(), k[3:])
        else:
            k = "%s%s" % (k[0].upper(), k[1:])
        obj[k] = v
        if isinstance(v, dict):
            camelResource(v)
        elif isinstance(v, list):
            list(map(camelResource, v))
    return obj
```
Might have some undesire side effects though...
@darrendao we don't put resource specific hacks/workarounds in generic utilities, the right place for this normalization is a config source subclass for rds snapshot.

Resolving patch:
diff --git a/c7n/query.py b/c7n/query.py
--- a/c7n/query.py
+++ b/c7n/query.py
@@ -16,6 +16,7 @@
 
 tags_spec -> s3, elb, rds
 """
+from concurrent.futures import as_completed
 import functools
 import itertools
 import json
@@ -353,6 +354,35 @@ def load_resource(self, item):
             item_config = item['configuration']
         return camelResource(item_config)
 
+    def get_listed_resources(self, client):
+        # fallback for when config decides to arbitrarily break select
+        # resource for a given resource type.
+        paginator = client.get_paginator('list_discovered_resources')
+        paginator.PAGE_ITERATOR_CLS = RetryPageIterator
+        pages = paginator.paginate(
+            resourceType=self.manager.get_model().config_type)
+        results = []
+
+        with self.manager.executor_factory(max_workers=2) as w:
+            ridents = pages.build_full_result()
+            resource_ids = [
+                r['resourceId'] for r in ridents.get('resourceIdentifiers', ())]
+            self.manager.log.debug(
+                "querying %d %s resources",
+                len(resource_ids),
+                self.manager.__class__.__name__.lower())
+
+            for resource_set in chunks(resource_ids, 50):
+                futures = []
+                futures.append(w.submit(self.get_resources, resource_set))
+                for f in as_completed(futures):
+                    if f.exception():
+                        self.manager.log.error(
+                            "Exception getting resources from config \n %s" % (
+                                f.exception()))
+                    results.extend(f.result())
+        return results
+
     def resources(self, query=None):
         client = local_session(self.manager.session_factory).client('config')
         query = self.get_query_params(query)
@@ -367,6 +397,12 @@ def resources(self, query=None):
         for page in pager.paginate(Expression=query['expr']):
             results.extend([
                 self.load_resource(json.loads(r)) for r in page['Results']])
+
+        # Config arbitrarily breaks which resource types its supports for query/select
+        # on any given day, if we don't have a user defined query, then fallback
+        # to iteration mode.
+        if not results and query == self.get_query_params({}):
+            results = self.get_listed_resources(client)
         return results
 
     def augment(self, resources):
diff --git a/c7n/resources/rdscluster.py b/c7n/resources/rdscluster.py
--- a/c7n/resources/rdscluster.py
+++ b/c7n/resources/rdscluster.py
@@ -14,6 +14,8 @@
 import logging
 
 from concurrent.futures import as_completed
+from datetime import datetime
+from dateutil.tz import tzutc
 
 from c7n.actions import BaseAction
 from c7n.filters import AgeFilter, CrossAccountAccessFilter
@@ -360,6 +362,31 @@ def augment(self, resources):
         return tags.universal_augment(self.manager, resources)
 
 
+class ConfigClusterSnapshot(ConfigSource):
+
+    def load_resource(self, item):
+
+        resource = super(ConfigClusterSnapshot, self).load_resource(item)
+        # db cluster snapshots are particularly mangled on keys
+        for k, v in list(resource.items()):
+            if k.startswith('Dbcl'):
+                resource.pop(k)
+                k = 'DBCl%s' % k[4:]
+                resource[k] = v
+            elif k.startswith('Iamd'):
+                resource.pop(k)
+                k = 'IAMD%s' % k[4:]
+                resource[k] = v
+        resource['Tags'] = [{'Key': k, 'Value': v} for k, v in item['tags'].items()]
+
+        utc = tzutc()
+        resource['SnapshotCreateTime'] = datetime.fromtimestamp(
+            resource['SnapshotCreateTime'] / 1000, tz=utc)
+        resource['ClusterCreateTime'] = datetime.fromtimestamp(
+            resource['ClusterCreateTime'] / 1000, tz=utc)
+        return resource
+
+
 @resources.register('rds-cluster-snapshot')
 class RDSClusterSnapshot(QueryResourceManager):
     """Resource manager for RDS cluster snapshots.
@@ -380,7 +407,7 @@ class resource_type(TypeInfo):
 
     source_mapping = {
         'describe': DescribeClusterSnapshot,
-        'config': ConfigSource
+        'config': ConfigClusterSnapshot
     }
 
 


Test patch:
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_1.json b/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_1.json
@@ -0,0 +1,63 @@
+{
+    "status_code": 200,
+    "data": {
+        "configurationItems": [
+            {
+                "version": "1.3",
+                "accountId": "644160558196",
+                "configurationItemCaptureTime": {
+                    "__class__": "datetime",
+                    "year": 2020,
+                    "month": 5,
+                    "day": 19,
+                    "hour": 8,
+                    "minute": 28,
+                    "second": 14,
+                    "microsecond": 760000
+                },
+                "configurationItemStatus": "ResourceDiscovered",
+                "configurationStateId": "6441605581960",
+                "configurationItemMD5Hash": "",
+                "arn": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-19-05-58",
+                "resourceType": "AWS::RDS::DBClusterSnapshot",
+                "resourceId": "rds:database-1-2020-05-19-05-58",
+                "resourceName": "rds:database-1-2020-05-19-05-58",
+                "awsRegion": "us-east-1",
+                "availabilityZone": "Multiple Availability Zones",
+                "resourceCreationTime": {
+                    "__class__": "datetime",
+                    "year": 2020,
+                    "month": 5,
+                    "day": 19,
+                    "hour": 1,
+                    "minute": 58,
+                    "second": 37,
+                    "microsecond": 785000
+                },
+                "tags": {
+                    "Owner": "kapil"
+                },
+                "relatedEvents": [],
+                "relationships": [
+                    {
+                        "resourceType": "AWS::EC2::VPC",
+                        "resourceId": "vpc-d2d616b5",
+                        "relationshipName": "Is associated with "
+                    },
+                    {
+                        "resourceType": "AWS::RDS::DBCluster",
+                        "resourceName": "database-1",
+                        "relationshipName": "Is associated with "
+                    }
+                ],
+                "configuration": "{\"availabilityZones\":[\"us-east-1a\",\"us-east-1b\",\"us-east-1d\"],\"snapshotCreateTime\":6441605581965,\"engine\":\"aurora-postgresql\",\"allocatedStorage\":0,\"status\":\"available\",\"port\":0,\"vpcId\":\"vpc-d2d616b5\",\"clusterCreateTime\":6441605581960,\"masterUsername\":\"postgres\",\"engineVersion\":\"10.serverless_7\",\"licenseModel\":\"postgresql-license\",\"snapshotType\":\"automated\",\"percentProgress\":100,\"storageEncrypted\":true,\"kmsKeyId\":\"arn:aws:kms:us-east-1:644160558196:key/b10f842a-feb7-4318-92d5-0640a75b7688\",\"dbclusterIdentifier\":\"database-1\",\"dbclusterSnapshotIdentifier\":\"rds:database-1-2020-05-19-05-58\",\"iamdatabaseAuthenticationEnabled\":false,\"dbclusterSnapshotArn\":\"arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-19-05-58\"}",
+                "supplementaryConfiguration": {
+                    "DBClusterSnapshotAttributes": "[{\"attributeName\":\"restore\",\"attributeValues\":[]}]",
+                    "Tags": "[{\"key\":\"Owner\",\"value\":\"kapil\"}]"
+                }
+            }
+        ],
+        "nextToken": "eyJlbmNyeXB0ZWREYXRhIjpbMTIwLDYzLDg2LDk2LC0xMjAsNzMsMTE2LC0xMjAsLTcwLDEzLC05NiwtMjIsMTI3LC0zMCwxMjQsLTYxLDExOSwxMTUsMTI3LC03MywtNTMsLTUwLC0yMSwxMjYsNDQsNTYsNDMsLTQ0LC0xMTcsNjMsLTg2LC05OSw2Nyw5OSw2OSwxNiw0OCw2MSwxMDksMTE5LC03NiwtNTAsLTExNCwtMTI1LC0xMjMsLTU3LDg0LC0xMDcsMTEyLDE4LDUxLC01LC05NiwtMTI0LDMxLDY5LC0xMjEsNDEsNDYsLTEyLC00MywtMTAxLC04LC00MywtOTYsODgsLTI4LDgwLC0xMjMsLTI1LC0xMSw4NSwtOCwtNTcsMzgsMjUsLTExNywtNTUsLTEwMywyNyw4LDU2LDU0LC02MywtODcsMTI2LDUsLTYxLDEyMCwtODUsNjEsLTEyNiwtMTIwLC0xMjEsODgsLTQ2LC05Myw4MCw5NCwwLDYyLDUsODgsNzMsLTk2LC03NiwtNDUsLTM0LDk2LDcxLC02LC03OSw4NiwtMTE2LC0xMSwtNjgsMzcsLTU1LC0xNSwtNDIsLTEwOCwtMTI3LDUwLC0xMjgsLTE1LDgyLDc5LC0yOSw3NSwtMTEzLC0zMiw4MywtOTEsLTc3LDY2LDMsMjMsMjksODIsNDMsLTk3LC03MSwyMiwtNTYsMjAsMTIwLC04MCwtMzgsLTI2LC0xNywyLC00OCw0Nyw3OCwtMTEsLTkzLDQzLDEwMywzNCwzMCwtMTI2LDIxLDExNCwxMjMsNzgsMzRdLCJtYXRlcmlhbFNldFNlcmlhbE51bWJlciI6MSwiaXZQYXJhbWV0ZXJTcGVjIjp7Iml2IjpbMTE0LDMsLTQ1LDQ4LDYyLDM1LDExMSwxMSwtNzUsLTM3LC03LC0yMSwyMSwtNzAsMTIsLTEwMF19fQ==",
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_2.json b/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/config.GetResourceConfigHistory_2.json
@@ -0,0 +1,63 @@
+{
+    "status_code": 200,
+    "data": {
+        "configurationItems": [
+            {
+                "version": "1.3",
+                "accountId": "644160558196",
+                "configurationItemCaptureTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 10,
+                    "day": 23,
+                    "hour": 12,
+                    "minute": 46,
+                    "second": 53,
+                    "microsecond": 279000
+                },
+                "configurationItemStatus": "ResourceDiscovered",
+                "configurationStateId": "6441605581969",
+                "configurationItemMD5Hash": "",
+                "arn": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:verify",
+                "resourceType": "AWS::RDS::DBClusterSnapshot",
+                "resourceId": "verify",
+                "resourceName": "verify",
+                "awsRegion": "us-east-1",
+                "availabilityZone": "Multiple Availability Zones",
+                "resourceCreationTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 10,
+                    "day": 23,
+                    "hour": 12,
+                    "minute": 44,
+                    "second": 39,
+                    "microsecond": 790000
+                },
+                "tags": {
+                    "Owner": "kapil"
+                },
+                "relatedEvents": [],
+                "relationships": [
+                    {
+                        "resourceType": "AWS::RDS::DBCluster",
+                        "resourceName": "database-1",
+                        "relationshipName": "Is associated with "
+                    },
+                    {
+                        "resourceType": "AWS::EC2::VPC",
+                        "resourceId": "vpc-d2d616b5",
+                        "relationshipName": "Is associated with "
+                    }
+                ],
+                "configuration": "{\"availabilityZones\":[\"us-east-1a\",\"us-east-1b\",\"us-east-1d\"],\"snapshotCreateTime\":6441605581960,\"engine\":\"aurora-postgresql\",\"allocatedStorage\":0,\"status\":\"available\",\"port\":0,\"vpcId\":\"vpc-d2d616b5\",\"clusterCreateTime\":6441605581960,\"masterUsername\":\"postgres\",\"engineVersion\":\"10.serverless_7\",\"licenseModel\":\"postgresql-license\",\"snapshotType\":\"manual\",\"percentProgress\":100,\"storageEncrypted\":true,\"kmsKeyId\":\"arn:aws:kms:us-east-1:644160558196:key/b10f842a-feb7-4318-92d5-0640a75b7688\",\"dbclusterSnapshotIdentifier\":\"verify\",\"dbclusterIdentifier\":\"database-1\",\"iamdatabaseAuthenticationEnabled\":false,\"dbclusterSnapshotArn\":\"arn:aws:rds:us-east-1:644160558196:cluster-snapshot:verify\"}",
+                "supplementaryConfiguration": {
+                    "DBClusterSnapshotAttributes": "[{\"attributeName\":\"restore\",\"attributeValues\":[]}]",
+                    "Tags": "[{\"key\":\"Owner\",\"value\":\"kapil\"}]"
+                }
+            }
+        ],
+        "nextToken": "eyJlbmNyeXB0ZWREYXRhIjpbLTM4LC01NywtMjAsNTIsLTEwNiwxMTksOTEsNzMsODYsLTExLC0zNywxMDYsMzAsMTE1LC0xMTAsMTA1LDc3LC00NywtMjksLTE2LC0xMTAsLTEwOSw0OSwtNDgsLTkxLC00NiwtMTAwLC0xMjcsNzEsLTY5LDEyNywtMTI3LDQ3LDY2LC05MiwtMTE0LDczLDM1LC0xNiwxMjQsMzksLTI1LC04NCw5Miw1MywtOTksLTcyLDkwLC0zLDg5LC01OSwxMTMsMSwtMTEwLDE3LDcxLC0zNywtMjQsLTEwMywxNSwtNiwtOTUsLTM0LDM2LC0xMCwtOSwtNTAsMTE3LC05MCwzMywtODAsNTgsLTEyNywtOTAsLTUyLC0xMiwxMjAsLTUzLDEwNywtMTEzLC01NywtOSwtMTExLC0xMDgsLTEyNCwtMTMsLTY2LC0xMTIsMTksLTEyNSwtNjEsLTExLDEwOCw1Nyw1OCwtNDksNzYsLTEyNCwtOTEsNzYsLTcsLTg2LDQ4LC03OCwtMSwxMTQsOCwtNjYsNDEsLTc0LC0zNCw5Myw2MiwtMTMsNTIsLTksLTExOSw0MCw5Miw5NiwtMTE5LDU1LDg4LDgxLDM1LC04NiwxMTYsLTExNSwyNCwtMTksLTgsNCwtMTExLC03MSwtOTgsLTM0LDY1LC01OCw1NiwtODksOTMsMzksLTk2LDUzLDU4LDEwLC04NCwtOTYsLTMzLC03LC03MiwxMDIsLTQ0LDMzLC0zNywtNjgsMywxMjMsMzAsLTExNiwtNzgsLTYzLDI3LC0zNCwtMTAyLC0zXSwibWF0ZXJpYWxTZXRTZXJpYWxOdW1iZXIiOjEsIml2UGFyYW1ldGVyU3BlYyI6eyJpdiI6WzEwMSwyNCw4MSw5NiwtNDgsLTkwLDU4LDM5LDQyLDU1LC04MSwtMTMsNzUsLTExNiw5NSwtMTddfX0=",
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/config.ListDiscoveredResources_1.json b/tests/data/placebo/test_rdscluster_snapshot_config/config.ListDiscoveredResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/config.ListDiscoveredResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "resourceIdentifiers": [
+            {
+                "resourceType": "AWS::RDS::DBClusterSnapshot",
+                "resourceId": "rds:database-1-2020-05-19-05-58",
+                "resourceName": "rds:database-1-2020-05-19-05-58"
+            },
+            {
+                "resourceType": "AWS::RDS::DBClusterSnapshot",
+                "resourceId": "verify",
+                "resourceName": "verify"
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/config.SelectResourceConfig_1.json b/tests/data/placebo/test_rdscluster_snapshot_config/config.SelectResourceConfig_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/config.SelectResourceConfig_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "Results": [],
+        "QueryInfo": {
+            "SelectFields": [
+                {
+                    "Name": "configuration"
+                },
+                {
+                    "Name": "supplementaryConfiguration"
+                }
+            ]
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/rds.DescribeDBClusterSnapshots_1.json b/tests/data/placebo/test_rdscluster_snapshot_config/rds.DescribeDBClusterSnapshots_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/rds.DescribeDBClusterSnapshots_1.json
@@ -0,0 +1,51 @@
+{
+    "status_code": 200,
+    "data": {
+        "DBClusterSnapshots": [
+            {
+                "AvailabilityZones": [
+                    "us-east-1a",
+                    "us-east-1b",
+                    "us-east-1d"
+                ],
+                "DBClusterSnapshotIdentifier": "verify",
+                "DBClusterIdentifier": "database-1",
+                "SnapshotCreateTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 10,
+                    "day": 23,
+                    "hour": 16,
+                    "minute": 44,
+                    "second": 39,
+                    "microsecond": 790000
+                },
+                "Engine": "aurora-postgresql",
+                "AllocatedStorage": 0,
+                "Status": "available",
+                "Port": 0,
+                "VpcId": "vpc-d2d616b5",
+                "ClusterCreateTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 10,
+                    "day": 20,
+                    "hour": 10,
+                    "minute": 53,
+                    "second": 28,
+                    "microsecond": 740000
+                },
+                "MasterUsername": "postgres",
+                "EngineVersion": "10.serverless_7",
+                "LicenseModel": "postgresql-license",
+                "SnapshotType": "manual",
+                "PercentProgress": 100,
+                "StorageEncrypted": true,
+                "KmsKeyId": "arn:aws:kms:us-east-1:644160558196:key/b10f842a-feb7-4318-92d5-0640a75b7688",
+                "DBClusterSnapshotArn": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:verify",
+                "IAMDatabaseAuthenticationEnabled": false
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rdscluster_snapshot_config/tagging.GetResources_1.json b/tests/data/placebo/test_rdscluster_snapshot_config/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rdscluster_snapshot_config/tagging.GetResources_1.json
@@ -0,0 +1,81 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-26-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-21-05-59",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-25-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-24-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-23-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:verify",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-27-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:rds:us-east-1:644160558196:cluster-snapshot:rds:database-1-2020-05-22-05-58",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "kapil"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_rdscluster.py b/tests/test_rdscluster.py
--- a/tests/test_rdscluster.py
+++ b/tests/test_rdscluster.py
@@ -371,6 +371,30 @@ def test_start(self):
 
 class RDSClusterSnapshotTest(BaseTest):
 
+    def test_rdscluster_snapshot_config(self):
+        session_factory = self.replay_flight_data("test_rdscluster_snapshot_config")
+        p = self.load_policy(
+            {"name": "rdscluster-snapshot-simple",
+             "source": "config",
+             "resource": "rds-cluster-snapshot"},
+            session_factory=session_factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 2)
+        p2 = self.load_policy(
+            {"name": "rdscluster-snapshot-descr",
+             "resource": "rds-cluster-snapshot"},
+            session_factory=session_factory)
+        rm = p2.resource_manager
+        resources2 = rm.get_resources([resources[-1][rm.resource_type.id]])
+        self.maxDiff = None
+        # placebo mangles the utc tz with its own class, also our account rewriter
+        # mangles the timestamp string :-(
+        for k in ('ClusterCreateTime', 'SnapshotCreateTime'):
+            for r in (resources[-1], resources2[0]):
+                r.pop(k)
+        self.assertEqual(resources[-1], resources2[0])
+
     def test_rdscluster_snapshot_simple(self):
         session_factory = self.replay_flight_data("test_rdscluster_snapshot_simple")
         p = self.load_policy(