Repository: cloud-custodian/cloud-custodian

Issue:
aws - Allow config-compliance filter on all resource types supported by CloudFormation
**Background**

AWS Config [supports](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#creating-custom-rules-for-additional-resource-types) custom rules that evaluate _Additional Resource Types_.

_Additional Resource Types_ a types of resources that are **not** yet [supported by AWS Config](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html), but are valid [AWS CloudFormation Resource Types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html).

**Use Case**

I'm looking at a use-case where we will be deploying various AWS Config custom rules that are, in some cases, evaluating _Additional Resource Types_. Note that these custom rules are _not managed by Custodian_. However, we will be using Custodian policies that uses [`type: config-compliance`](https://cloudcustodian.io/docs/aws/resources/aws-common-filters.html#config-compliance) filter to find resources marked as non-compliant by these custom rules.

**Feature Request**

Currently, for all resources supported by AWS Config, the Custodian `resource_type` has the appropriate `config_type` defined and therefore has the `config-compliance` filter registered.

In order to support to use-case described above, the appropriate `config_type` will need to be defined for all resources supported by AWS CloudFormation as well.

**Caveats**

* [Config Source](https://cloudcustodian.io/docs/aws/topics/config.html#config-source) would only work for _AWS Config Supported Resource Types_.
* [Config Rule](https://cloudcustodian.io/docs/aws/topics/config.html#config-rule) would only work for _AWS Config Supported Resource Types_.
* [Config Filter](https://cloudcustodian.io/docs/aws/topics/config.html#filter) would work for both _AWS Config Supported Resource Types_ and _Additional Resource Types_.


Discussion:
>In order to support to use-case described above, the appropriate config_type will need to be defined for all resources supported by AWS CloudFormation as well.

On second thought, maybe this requires a new `config_type_partial` (or similar) attribute that distinguishes between a resource that is only _partially supported_.
the use case isn't entirely clear, that sounds more like an implementation aspect your describing.

ie. you saying config doesn't support something, except your planning on doing a user defined resource type.. ie. something arbitrary and custom.. how does that map to custodian? and why woud custodian support something thats only useful only for a single deployment? is your mapping of cfn to config opensource? have you thought about contributing support to custodian to do the same re mapping to custom resources? its also unclear why it would only be supporting config compliance filter, instead of all three of those (source, rule, filter), ie. custodian is going to easier to write the rule then any alternative that i can see, unless your saying that aws config doesn't actually support custom resource types fully even though they advertise such.

>the use case isn't entirely clear

Maybe I can provide a better example:

```yaml
policies:
- name: ecs-check-using-custom-rule
  resource: aws.ecs-service
  filters:
  - type: config-compliance
    rules:
    - my-custom-ecs-config-rule
    states:
    - NON_COMPLIANT
```

In the policy above, I'm using a valid custodian resource (`aws.ecs-service`). However, the policy is not valid because `aws.ecs-service` custodian resource does not support the `config-compliance` filter since an ECS Service is not a _fully supported_ resource type by AWS Config.

However, AWS Config **does support** creating a custom rule that evaluates the resource type [`AWS::ECS::Service`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html).  See [this AWS Config documentation](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#creating-custom-rules-for-additional-resource-types). Particularly, the snippet:

> You can create custom rules to run evaluations for resource types not yet recorded by AWS Config. This is useful if you want to evaluate compliance for additional resource types, such as Amazon S3 Glacier vaults or Amazon SNS topics, that AWS Config doesn't currently record. For a list of additional resource types that you can evaluate with custom rules, see AWS Resource Types Reference.

It looks like custodian is deciding on whether the `config-compliance` filter is available to a resource based on [`resource_class.resource_type.config_type`](https://github.com/cloud-custodian/cloud-custodian/blob/master/c7n/filters/config.py#L126).

The `resource_class.resource_type.config_type` is currently set for each _supported_ resource listed here: https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html

What I'm proposing is that a new attribute `resource_class.resource_type.config_type_partial` (or similar) be introduced for resources that are listed [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) but **not** listed [here](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html). Resources with this attribute should also support the `config-compliance` filter, so that the example policy show above is valid.

Hope this helps clarify.
I think your missing the part for custom resources, where you have to post all state changes, additions, deletions yourself to the config service (afaics).. its not simply a question of provisioning the rule on the custom resource. the reference to cfn, isn't about cfn supporting config, its that its using the cfn schema registry for types, and you can register custom types in there which have could refer to anything (including stuff unrelated to aws).  config needs a data pump for those.
>I think your missing the part for custom resources, where you have to post all state changes, additions, deletions yourself to the config service (afaics).. its not simply a question of provisioning the rule on the custom resource. the reference to cfn, isn't about cfn supporting config, its that its using the cfn schema registry for types, and you can register custom types in there which have could refer to anything (including stuff unrelated to aws). config needs a data pump for those.

What you are describing is [Record Configurations for Third-Party Resources](https://docs.aws.amazon.com/config/latest/developerguide/customresources.html). But that is not the use-case I'm referring to.

The use-case I'm referring to is [Evaluating Additional Resource Types](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#creating-custom-rules-for-additional-resource-types).

> What you are describing is [Record Configurations for Third-Party Resources](https://docs.aws.amazon.com/config/latest/developerguide/customresources.html). But that is not the use-case I'm referring to.
> 
> The use-case I'm referring to is [Evaluating Additional Resource Types](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#creating-custom-rules-for-additional-resource-types).

interesting.. what sort of guarantees on liveness does this offer (ie. eta on change detection), I'm assuming its closer to 15m+ poll.. its a little unclear to be honest from the docs, ie. "not yet recorded by AWS Config." implies you have to record yourself, but in the absence of docs banging on the api to observe seems required.

wrt to support, I've added in cfn_type to resources in #5681
> what sort of guarantees on liveness does this offer (ie. eta on change detection)

There is no liveness guarantee. Rules created with these types of resources are "triggered at a periodic frequency" (https://docs.aws.amazon.com/config/latest/APIReference/API_ConfigRule.html#config-Type-ConfigRule-MaximumExecutionFrequency)




On Apr 30, 2020, at 5:29 PM, Kapil Thangavelu <notifications@github.com> wrote:

﻿
What you are describing is Record Configurations for Third-Party Resources. But that is not the use-case I'm referring to.

The use-case I'm referring to is Evaluating Additional Resource Types.

interesting.. what sort of guarantees on liveness does this offer (ie. eta on change detection), I'm assuming its closer to 15m+ poll.. its a little unclear to be honest from the docs, ie. "not yet recorded by AWS Config." implies you have to record yourself, but in the absence of docs banging on the api to observe seems required.

wrt to support, I've added in cfn_type to resources in #5681

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.
okay so this isn't really different then a periodic policy except now we have to contend with config's janky idiosyncratic bespoke attribute normalization... which frankly is a problem sans verification per resource.  generally speaking custodian attempts to normalize that so that policies are isomorphic on execution mode, instead of having lockin on config. 

afaics the primary benefit for supporting this is groups that want to use config's dashboards which are pretty basic? else a custodian periodic policy using cwe or a cron job is effectively doing the same and provides choice of dashboard re sechub, cloudwatch, grafana, etc and supports centralization much better then config. ie c7n-org on a cron job/container is going to be simpler to operate/update. ie. the value chain seems to be on being able to claim use of config even though its not really the best solution here afaics. could you clarify what you see as the benefit? 

wrt to normal config support and these partially supported resources. does config maintain history timelines on these resources? can they be queried via select resource config? 

the config rule mode would need updating on provisioning for these config fake/partially supported resources, that have nothing to do with the rest of config. at min requiring a periodicity specification on the mode.

you also mentioned using config rules not implemented by custodian, could you clarify what their doing? i'm just curious why not implement them in custodian, as i haven't seen any config rules that weren't easier to express (20x code reduction) when implemented in custodian.

> The use-case I'm referring to is [Evaluating Additional Resource Types](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_nodejs.html#creating-custom-rules-for-additional-resource-types).

its worth pointing how bad the documentation here is, the example resource, a glacier vault..  isn't even supported as resource by CloudFormation 🤷 

as additional example, config says they support aws secrets manager in the docs (https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#awssecretsmanager) , but the aws sdks haven't been updated to denote that as a supported resource type 😭 https://github.com/boto/botocore/blob/develop/botocore/data/config/2014-11-12/service-2.json#L5972


>okay so this isn't really different then a periodic policy

Correct.

>wrt to normal config support and these partially supported resources. does config maintain history timelines on these resources? can they be queried via select resource config?

No. Config does not maintain configuration history, timelines or support querying these resources since they are _partially supported_ and not first-class services in Config.

>the config rule mode would need updating on provisioning for these config fake/partially supported resources, that have nothing to do with the rest of config. at min requiring a periodicity specification on the mode.

Actually, if you look at the _caveats_ listed in the original request, the only thing that would work in this scenario is the `config-compliance` filter. The config-rule mode and config-source would not work for these resource types.

>you also mentioned using config rules not implemented by custodian, could you clarify what they're doing? i'm just curious why not implement them in custodian, as i haven't seen any config rules that weren't easier to express (20x code reduction) when implemented in custodian.

The initial use-cases we are looking at is to evaluate a resource's compliance based on some complex relationship with multiple other resources (_resource hopping_?). For e.g., Determine if a glue-dev-endpoint/rds-instance lives in a private subnet by examining the endpoint->subnet->route-table relationship and configurations.

One other initial use-case is what is listed in #5671, where the configuration of a resource needs to be examined based on some other configuration within the same resource.

Essentially, what this feature request is asking for is to support _more_ number of resources to be able to use the `config-compliance` filter. It also serves as an escape hatch to offload compliance evaluation for complex scenarios that are either (a) not currently supported by Custodian or (b) not really recommended for a Custodian policy implementation since it would serve only a single customer.

>its worth pointing how bad the documentation here is, the example resource, a glacier vault.. isn't even supported as resource by CloudFormation 🤷

I agree. There is a lot left to be clarified in the documentation.
> > its worth pointing how bad the documentation here is, the example resource, a glacier vault.. isn't even supported as resource by CloudFormation 🤷
> 
> I agree. There is a lot left to be clarified in the documentation.

this goes beyond just docs issues.. to broken service guarantees.. ie. what's even worse with secrets manager, even though its documented as **fully** supported you can't query for it using select resource config.. its showing up in the *config* console..  but querying for it via select resources for even a basic query like

```
select
  configuration,
  configurationItemCaptureTime,
  configurationItemStatus
where
  resourceType = 'AWS::SecretsManager::Secret'
```

will turn up no results. so it's really unclear exactly what a fully supported config resource even means, if the basics are broken.

> 
> > the config rule mode would need updating on provisioning for these config fake/partially supported resources, that have nothing to do with the rest of config. at min requiring a periodicity specification on the mode.
> 
> Actually, if you look at the _caveats_ listed in the original request, the only thing that would work in this scenario is the `config-compliance` filter. The config-rule mode and config-source would not work for these resource types.
> 

why wouldn't we also want to support a config rule in custodian here for these partially resources, ie thats the basis for these partially config supported resources.

> > you also mentioned using config rules not implemented by custodian, could you clarify what they're doing? i'm just curious why not implement them in custodian, as i haven't seen any config rules that weren't easier to express (20x code reduction) when implemented in custodian.
> 
> The initial use-cases we are looking at is to evaluate a resource's compliance based on some complex relationship with multiple other resources (_resource hopping_?). For e.g., Determine if a glue-dev-endpoint/rds-instance lives in a private subnet by examining the endpoint->subnet->route-table relationship and configurations.
> 

cool, I think we can probably add a network reachability filter to custodian, we already do one ho evals on all network attached resources to subnets. thats probably best discussed in a separate issue though.

> One other initial use-case is what is listed in #5671, where the configuration of a resource needs to be examined based on some other configuration within the same resource.

could you add some concrete examples to that issue, it sounds like a value filter thats just comparing against a resource attribute instead of a policy value.

> 
> Essentially, what this feature request is asking for is to support _more_ number of resources to be able to use the `config-compliance` filter. It also serves as an escape hatch to offload compliance evaluation for complex scenarios that are either (a) not currently supported by Custodian or (b) not really recommended for a Custodian policy implementation since it would serve only a single customer.


we generally the safety valve support those by using value_from and maintain an external data dictionary in s3 etc.

wrt to this feature request afaics it should be both config-rule and compliance-filter for resources with cfn types defined. ie the later implies the former can be defined, and there's no reason not to enable both afaics.



>why wouldn't we also want to support a config rule in custodian here for these partially resources, ie thats the basis for these partially config supported resources.

We cannot. Partially supported resources are **not** recorded by AWS Config. The [config-rule implementation](https://github.com/cloud-custodian/cloud-custodian/blob/master/c7n/policy.py#L749) relies on the resource being a `configurationItem` - which partially supported resources are **not**.

>we generally the safety valve support those by using value_from and maintain an external data dictionary in s3 etc.

The `value_from` doesn't work really well with dynamic values, or values that change very often. It just puts the responsibility of keeping the data-dictionary up to date on a different process.

>wrt to this feature request afaics it should be both config-rule and compliance-filter for resources with cfn types defined. ie the later implies the former can be defined, and there's no reason not to enable both afaics.

Like I mentioned above. Partially supported resources are not recorded by AWS Config and are not considered as configurationItems. This means we cannot trigger a config-rule execution based on changes to a resource, and we cannot query AWS Config for any information regarding these partially supported resources.
>could you add some concrete examples to that issue, it sounds like a value filter thats just comparing against a resource attribute instead of a policy value

The use-case defined in #5671 wants to be able to use the value filter type where _both_ the `key` and `value` are JMESPath expressions.
> > why wouldn't we also want to support a config rule in custodian here for these partially resources, ie thats the basis for these partially config supported resources.
> 
> We cannot. Partially supported resources are **not** recorded by AWS Config. The [config-rule implementation](https://github.com/cloud-custodian/cloud-custodian/blob/master/c7n/policy.py#L749) relies on the resource being a `configurationItem` - which partially supported resources are **not**.
> 

ic, thanks for clarifying so it really is a different mode, config-poll-rule thats akin to the existing periodic in execution, except also posting to config. 

> > we generally the safety valve support those by using value_from and maintain an external data dictionary in s3 etc.
> 
> The `value_from` doesn't work really well with dynamic values, or values that change very often. It just puts the responsibility of keeping the data-dictionary up to date on a different process.
> 

value_from can pull from a url, ie its no different then hitting a rest endpoint for a data dictionary. 

> > wrt to this feature request afaics it should be both config-rule and compliance-filter for resources with cfn types defined. ie the later implies the former can be defined, and there's no reason not to enable both afaics.
> 
> Like I mentioned above. Partially supported resources are not recorded by AWS Config and are not considered as configurationItems. This means we cannot trigger a config-rule execution based on changes to a resource, and we cannot query AWS Config for any information regarding these partially supported resources.

if your using this config capability to push evaluations in, its trivial to have custodian do the same.


> > could you add some concrete examples to that issue, it sounds like a value filter thats just comparing against a resource attribute instead of a policy value
> 
> The use-case defined in #5671 wants to be able to use the value filter type where _both_ the `key` and `value` are JMESPath expressions.

thats not what I read in #5671 it would be good to file a separate issue with example for that use case.
>so it really is a different mode, config-poll-rule thats akin to the existing periodic in execution, except also posting to config

I agree. But I think this new mode should be a different feature.

>value_from can pull from a url, ie its no different then hitting a rest endpoint for a data dictionary.

Good point.

>thats not what I read in #5671 it would be good to file a separate issue with example for that use case.

You are right. I misread #5671 as well. I'll open a new issue.

Resolving patch:
diff --git a/c7n/filters/config.py b/c7n/filters/config.py
--- a/c7n/filters/config.py
+++ b/c7n/filters/config.py
@@ -83,7 +83,9 @@ def get_resource_map(self, filters, resource_model, resources):
                     # for multi resource type rules, only look at
                     # results for the resource type currently being
                     # processed.
-                    if rident['ResourceType'] != resource_model.config_type:
+                    if rident['ResourceType'] not in (
+                            resource_model.config_type,
+                            resource_model.cfn_type):
                         continue
 
                     if not filters:
@@ -123,7 +125,8 @@ def register_resources(klass, registry, resource_class):
         supported by aws config, automatically, register the
         config-compliance filter.
         """
-        if resource_class.resource_type.config_type is None:
+        if (resource_class.resource_type.cfn_type is None and
+                resource_class.resource_type.config_type is None):
             return
         resource_class.filter_registry.register('config-compliance', klass)
 
diff --git a/c7n/mu.py b/c7n/mu.py
--- a/c7n/mu.py
+++ b/c7n/mu.py
@@ -886,7 +886,8 @@ def packages(self):
 
     def get_events(self, session_factory):
         events = []
-        if self.policy.data['mode']['type'] == 'config-rule':
+        if self.policy.data['mode']['type'] in (
+                'config-rule', 'config-poll-rule'):
             events.append(
                 ConfigRule(self.policy.data['mode'], session_factory))
         elif self.policy.data['mode']['type'] == 'hub-action':
@@ -1605,8 +1606,11 @@ def get_rule_params(self, func):
 
         if isinstance(func, PolicyLambda):
             manager = func.policy.load_resource_manager()
-            if hasattr(manager.get_model(), 'config_type'):
-                config_type = manager.get_model().config_type
+            resource_model = manager.get_model()
+            if resource_model.config_type:
+                config_type = resource_model.config_type
+            elif resource_model.cfn_type and 'schedule' in self.data:
+                config_type = resource_model.cfn_type
             else:
                 raise Exception("You may have attempted to deploy a config "
                                 "based lambda function with an unsupported config type. "
@@ -1618,6 +1622,12 @@ def get_rule_params(self, func):
         else:
             params['Scope']['ComplianceResourceTypes'] = self.data.get(
                 'resource-types', ())
+        if self.data.get('schedule'):
+            params['Source']['SourceDetails'] = [{
+                'EventSource': 'aws.config',
+                'MessageType': 'ScheduledNotification'
+            }]
+            params['MaximumExecutionFrequency'] = self.data['schedule']
         return params
 
     def get(self, rule_name):
@@ -1638,6 +1648,9 @@ def delta(rule, params):
             return True
         if rule['Source'] != params['Source']:
             return True
+        if ('MaximumExecutionFrequency' in params and
+                rule['MaximumExecutionFrequency'] != params['MaximumExecutionFrequency']):
+            return True
         if rule.get('Description', '') != rule.get('Description', ''):
             return True
         return False
diff --git a/c7n/policy.py b/c7n/policy.py
--- a/c7n/policy.py
+++ b/c7n/policy.py
@@ -713,6 +713,99 @@ def provision(self):
         return super(GuardDutyMode, self).provision()
 
 
+@execution.register('config-poll-rule')
+class ConfigPollRuleMode(LambdaMode, PullMode):
+    """This mode represents a periodic/scheduled AWS config evaluation.
+
+    The primary benefit this mode offers is to support additional resources
+    beyond what config supports natively, as it can post evaluations for
+    any resource which has a cloudformation type. If a resource is natively
+    supported by config its highly recommended to use a `config-rule`
+    mode instead.
+
+    This mode effectively receives no data from config, instead its
+    periodically executed by config and polls and evaluates all
+    resources. It is equivalent to a periodic policy, except it also
+    pushes resource evaluations to config.
+    """
+    schema = utils.type_schema(
+        'config-poll-rule',
+        schedule={'enum': [
+            "One_Hour",
+            "Three_Hours",
+            "Six_Hours",
+            "Twelve_Hours",
+            "TwentyFour_Hours"]},
+        rinherit=LambdaMode.schema)
+
+    def validate(self):
+        super().validate()
+        if not self.policy.data['mode'].get('schedule'):
+            raise PolicyValidationError(
+                "policy:%s config-poll-rule schedule required" % (
+                    self.policy.name))
+        if self.policy.resource_manager.resource_type.config_type:
+            raise PolicyValidationError(
+                "resource:%s fully supported by config and should use mode: config-rule" % (
+                    self.policy.resource_type))
+        if self.policy.data['mode'].get('pattern'):
+            raise PolicyValidationError(
+                "policy:%s AWS Config does not support event pattern filtering" % (
+                    self.policy.name))
+        if not self.policy.resource_manager.resource_type.cfn_type:
+            raise PolicyValidationError((
+                'policy:%s resource:%s does not have a cloudformation type'
+                ' and is there-fore not supported by config-poll-rule'))
+
+    def _get_client(self):
+        return utils.local_session(
+            self.policy.session_factory).client('config')
+
+    def run(self, event, lambda_context):
+        cfg_event = json.loads(event['invokingEvent'])
+        resource_type = self.policy.resource_manager.resource_type.cfn_type
+        resource_id = self.policy.resource_manager.resource_type.id
+        client = self._get_client()
+
+        matched_resources = set()
+        for r in PullMode.run(self):
+            matched_resources.add(r[resource_id])
+        unmatched_resources = set()
+        for r in self.policy.resource_manager.get_resource_manager(
+                self.policy.resource_type).resources():
+            if r[resource_id] not in matched_resources:
+                unmatched_resources.add(r[resource_id])
+
+        evaluations = [dict(
+            ComplianceResourceType=resource_type,
+            ComplianceResourceId=r,
+            ComplianceType='NON_COMPLIANT',
+            OrderingTimestamp=cfg_event['notificationCreationTime'],
+            Annotation='The resource is not compliant with policy:%s.' % (
+                self.policy.name))
+            for r in matched_resources]
+        if evaluations:
+            self.policy.resource_manager.retry(
+                client.put_evaluations,
+                Evaluations=evaluations,
+                ResultToken=event.get('resultToken', 'No token found.'))
+
+        evaluations = [dict(
+            ComplianceResourceType=resource_type,
+            ComplianceResourceId=r,
+            ComplianceType='COMPLIANT',
+            OrderingTimestamp=cfg_event['notificationCreationTime'],
+            Annotation='The resource is compliant with policy:%s.' % (
+                self.policy.name))
+            for r in unmatched_resources]
+        if evaluations:
+            self.policy.resource_manager.retry(
+                client.put_evaluations,
+                Evaluations=evaluations,
+                ResultToken=event.get('resultToken', 'No token found.'))
+        return list(matched_resources)
+
+
 @execution.register('config-rule')
 class ConfigRuleMode(LambdaMode):
     """a lambda policy that executes as a config service rule.


Test patch:
diff --git a/tests/data/config/poll-evaluation.json b/tests/data/config/poll-evaluation.json
new file mode 100644
--- /dev/null
+++ b/tests/data/config/poll-evaluation.json
@@ -0,0 +1,12 @@
+{
+  "version": "1.0",
+  "invokingEvent": "{\"awsAccountId\":\"644160558196\",\"notificationCreationTime\":\"2020-05-03T13:55:44.576Z\",\"messageType\":\"ScheduledNotification\",\"recordVersion\":\"1.0\"}",
+  "ruleParameters": "{}",
+  "resultToken": "eyJlbmNyeXB0ZWREYXRhIjpbLTEyNSwtNCwtODgsNDYsLTc3LDE1LC0zMyw0MiwtNjgsMTA2LC01OSwxMCw3NSwxMjIsLTI0LDY3LDM5LDcxLC0xMDYsLTU1LDcyLDg3LDQyLDEyNiwtMjUsODEsNTEsMTEzLDEwNiwtMTIzLC04NCwxMDEsLTYwLC00NSwtOSwxMjIsLTEyOCwtMjMsNTksNjEsMTEzLC0xMTIsLTExNCwxNywtMzUsLTEyNCwzNCw0NCwtMTAzLDUyLC0xMjMsMzEsODUsNzQsLTI4LDU5LC0zLC0xMDAsMjUsNCw5NiwtODIsMTI2LC04Niw2NCwtODAsNDYsNjUsNjYsNywtMjAsMTcsLTI3LC01MSwtODMsLTI3LDI4LDkwLC0yNSw0MywtMTI0LC04NSwtMTE2LDEyNCwtNjcsLTQxLDExMiw5LDEwMyw3MCwtOTYsLTQyLC03Miw2OCw3MCwtMzYsNjQsMTksLTEyNyw3Niw1MCwtODgsLTQ2LDE5LDIwLC03Miw3MSwxOSw5OSw5NCwtMTA3LDE4LC0yMSwtMTEwLC01MiwtMTYsNDMsLTUsMTcsLTI0LC04LC00NiwtNywtOTksMTIxLC05Myw2OCw2Myw2MywyNywtNDUsLTEyNyw5NSwxMjAsLTIyLC02NCw5MCwtNDIsLTUwLDcxLC02NSw2NywtMTEwLC05NCwyNywtNTAsNTEsLTk0LC0xMjYsMTIsLTM1LC0xMjAsLTExNSw0NCwxMjAsLTI5LC05MywxMTQsLTEwMywtODEsMzQsNjcsNDYsNjUsLTE5LDk5LDEwNCwtNjAsMTEsMzksMTI2LC0xMDIsLTgwLDY0LC02MCw2MSwtNDQsMTI2LDk2LC03MCwtODEsLTgwLDc3LC0xMjMsLTk3LDExLDEwOSwxNCwtNTIsLTEwNSwtODQsNCwtMTAzLC0zMyw2OSwtNDcsMTE3LDE5LDEyMSwxMDMsMCwtNzAsMTAzLDEyMiw4NSwtODIsLTEwOSwtMTYsLTExOSw3OCwtMTAzLC0zNiwtMSwtMTAzLC05OCw5MiwtMjUsLTEzLC0xNCwtMTgsLTU4LDM2LC05MSwtMTA5LDY0LDMsLTQ0LC0xMDAsNDUsMSw0OCwtNTYsLTUyLC0xMjQsMzEsLTM2LDE1LDcsNzYsOSwtMTA5LDExNCw0NiwtMTEzLDM5LC02MywtNjksLTYsMTAyLDQsMzgsNiwtNywxMjQsMjgsLTYxLDY0LC0xMDEsLTg0LC05OSwtMTA1LC03NSwtMTgsLTk4LDEwMyw5MSwzLDc0LC04NiwtNzEsLTczLDkyLDEwNSwtMTEzLC05NiwtMjAsNzMsOTAsLTQsLTI2LC05LDE2LDgyLC0xMDksLTM0LDcsMSwxNCw3OSwyOCwyNiwzMywtNjMsLTEyNSw4MCwtMjEsLTM2LDU4LC03MywtMywxMTMsNTcsLTYwLC05MSwtNjYsLTk4LDQwLDI4LC04MiwtNDEsLTEyNSwzNiwtNywtMTE0LC03NywzNSwyNSwtNDZdLCJtYXRlcmlhbFNldFNlcmlhbE51bWJlciI6MSwiaXZQYXJhbWV0ZXJTcGVjIjp7Iml2IjpbMTE3LC00NywtNTMsNzIsNDIsMzUsNTYsNiwzNywtNTUsLTUxLC0zNCwxMjUsNjYsLTYxLDEwNF19fQ==",
+  "eventLeftScope": false,
+  "executionRoleArn": "arn:aws:iam::644160558196:role/service-role/config-role-us-east-1",
+  "configRuleArn": "arn:aws:config:us-east-1:644160558196:config-rule/config-rule-4epkbf",
+  "configRuleName": "dev",
+  "configRuleId": "config-rule-4epkbf",
+  "accountId": "644160558196"
+}
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_1.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_1.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamDescription": {
+            "StreamName": "dev1",
+            "StreamARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev1",
+            "StreamStatus": "ACTIVE",
+            "Shards": [
+                {
+                    "ShardId": "shardId-644160558196",
+                    "HashKeyRange": {
+                        "StartingHashKey": "0",
+                        "EndingHashKey": "644160558196644160558196644160558196455"
+                    },
+                    "SequenceNumberRange": {
+                        "StartingSequenceNumber": "64416055819664416055819664416055819664416055819625978114"
+                    }
+                }
+            ],
+            "HasMoreShards": false,
+            "RetentionPeriodHours": 24,
+            "StreamCreationTimestamp": {
+                "__class__": "datetime",
+                "year": 2020,
+                "month": 5,
+                "day": 3,
+                "hour": 10,
+                "minute": 46,
+                "second": 33,
+                "microsecond": 0
+            },
+            "EnhancedMonitoring": [
+                {
+                    "ShardLevelMetrics": []
+                }
+            ],
+            "EncryptionType": "NONE"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_2.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_2.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamDescription": {
+            "StreamName": "dev2",
+            "StreamARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev2",
+            "StreamStatus": "ACTIVE",
+            "Shards": [
+                {
+                    "ShardId": "shardId-644160558196",
+                    "HashKeyRange": {
+                        "StartingHashKey": "0",
+                        "EndingHashKey": "644160558196644160558196644160558196455"
+                    },
+                    "SequenceNumberRange": {
+                        "StartingSequenceNumber": "64416055819664416055819664416055819664416055819692775426"
+                    }
+                }
+            ],
+            "HasMoreShards": false,
+            "RetentionPeriodHours": 24,
+            "StreamCreationTimestamp": {
+                "__class__": "datetime",
+                "year": 2020,
+                "month": 5,
+                "day": 3,
+                "hour": 10,
+                "minute": 46,
+                "second": 44,
+                "microsecond": 0
+            },
+            "EnhancedMonitoring": [
+                {
+                    "ShardLevelMetrics": []
+                }
+            ],
+            "EncryptionType": "NONE"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_3.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_3.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamDescription": {
+            "StreamName": "dev1",
+            "StreamARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev1",
+            "StreamStatus": "ACTIVE",
+            "Shards": [
+                {
+                    "ShardId": "shardId-644160558196",
+                    "HashKeyRange": {
+                        "StartingHashKey": "0",
+                        "EndingHashKey": "644160558196644160558196644160558196455"
+                    },
+                    "SequenceNumberRange": {
+                        "StartingSequenceNumber": "64416055819664416055819664416055819664416055819625978114"
+                    }
+                }
+            ],
+            "HasMoreShards": false,
+            "RetentionPeriodHours": 24,
+            "StreamCreationTimestamp": {
+                "__class__": "datetime",
+                "year": 2020,
+                "month": 5,
+                "day": 3,
+                "hour": 10,
+                "minute": 46,
+                "second": 33,
+                "microsecond": 0
+            },
+            "EnhancedMonitoring": [
+                {
+                    "ShardLevelMetrics": []
+                }
+            ],
+            "EncryptionType": "NONE"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_4.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_4.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.DescribeStream_4.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamDescription": {
+            "StreamName": "dev2",
+            "StreamARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev2",
+            "StreamStatus": "ACTIVE",
+            "Shards": [
+                {
+                    "ShardId": "shardId-644160558196",
+                    "HashKeyRange": {
+                        "StartingHashKey": "0",
+                        "EndingHashKey": "644160558196644160558196644160558196455"
+                    },
+                    "SequenceNumberRange": {
+                        "StartingSequenceNumber": "64416055819664416055819664416055819664416055819692775426"
+                    }
+                }
+            ],
+            "HasMoreShards": false,
+            "RetentionPeriodHours": 24,
+            "StreamCreationTimestamp": {
+                "__class__": "datetime",
+                "year": 2020,
+                "month": 5,
+                "day": 3,
+                "hour": 10,
+                "minute": 46,
+                "second": 44,
+                "microsecond": 0
+            },
+            "EnhancedMonitoring": [
+                {
+                    "ShardLevelMetrics": []
+                }
+            ],
+            "EncryptionType": "NONE"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_1.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_1.json
@@ -0,0 +1,11 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamNames": [
+            "dev1",
+            "dev2"
+        ],
+        "HasMoreStreams": false,
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_2.json b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/kinesis.ListStreams_2.json
@@ -0,0 +1,11 @@
+{
+    "status_code": 200,
+    "data": {
+        "StreamNames": [
+            "dev1",
+            "dev2"
+        ],
+        "HasMoreStreams": false,
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_1.json b/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev2",
+                "Tags": [
+                    {
+                        "Key": "App",
+                        "Value": "Dev"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_2.json b/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_config_poll_rule_evaluation/tagging.GetResources_2.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:kinesis:us-east-1:644160558196:stream/dev2",
+                "Tags": [
+                    {
+                        "Key": "App",
+                        "Value": "Dev"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_mu.py b/tests/test_mu.py
--- a/tests/test_mu.py
+++ b/tests/test_mu.py
@@ -147,6 +147,33 @@ def test_config_rule_provision(self):
         self.assertEqual(result["FunctionName"], "custodian-sg-modified")
         self.addCleanup(mgr.remove, pl)
 
+    def test_config_poll_rule_evaluation(self):
+        session_factory = self.record_flight_data("test_config_poll_rule_provision")
+        p = self.load_policy({
+            'name': 'configx',
+            'resource': 'aws.kinesis',
+            'mode': {
+                'schedule': 'Three_Hours',
+                'type': 'config-poll-rule'}})
+        mu_policy = PolicyLambda(p)
+        mu_policy.arn = "arn:aws:lambda:us-east-1:644160558196:function:CloudCustodian"
+        events = mu_policy.get_events(session_factory)
+        self.assertEqual(len(events), 1)
+        config_rule = events.pop()
+        self.assertEqual(
+            config_rule.get_rule_params(mu_policy),
+
+            {'ConfigRuleName': 'custodian-configx',
+             'Description': 'cloud-custodian lambda policy',
+             'MaximumExecutionFrequency': 'Three_Hours',
+             'Scope': {'ComplianceResourceTypes': ['AWS::Kinesis::Stream']},
+             'Source': {
+                 'Owner': 'CUSTOM_LAMBDA',
+                 'SourceDetails': [{'EventSource': 'aws.config',
+                                    'MessageType': 'ScheduledNotification'}],
+                 'SourceIdentifier': 'arn:aws:lambda:us-east-1:644160558196:function:CloudCustodian'} # noqa
+             })
+
     def test_config_rule_evaluation(self):
         session_factory = self.replay_flight_data("test_config_rule_evaluate")
         p = self.load_policy(
diff --git a/tests/test_policy.py b/tests/test_policy.py
--- a/tests/test_policy.py
+++ b/tests/test_policy.py
@@ -25,6 +25,7 @@
 from c7n.resources import aws, load_available
 from c7n.resources.aws import AWS, fake_session
 from c7n.resources.ec2 import EC2
+from c7n.policy import ConfigPollRuleMode
 from c7n.schema import generate, JsonSchemaValidator
 from c7n.utils import dumps
 from c7n.query import ConfigSource, TypeInfo
@@ -1212,6 +1213,45 @@ def test_validation(self):
              'mode': {'type': 'phd'}})
 
 
+class ConfigModeTest(BaseTest):
+
+    def test_config_poll(self):
+        factory = self.replay_flight_data('test_config_poll_rule_evaluation')
+        cmock = mock.MagicMock()
+        requests = []
+
+        def record_requests(Evaluations, ResultToken):
+            requests.append(Evaluations)
+
+        cmock.put_evaluations.side_effect = record_requests
+        cmock.put_evaluations.return_value = {}
+        self.patch(
+            ConfigPollRuleMode, '_get_client', lambda self: cmock)
+        p = self.load_policy({
+            'name': 'kin-poll',
+            'resource': 'aws.kinesis',
+            'filters': [{'tag:App': 'Dev'}],
+            'mode': {
+                'type': 'config-poll-rule',
+                'schedule': 'Three_Hours'}},
+            session_factory=factory)
+        event = event_data('poll-evaluation.json', 'config')
+        results = p.push(event, None)
+        self.assertEqual(results, ['dev2'])
+        self.assertEqual(
+            requests,
+            [[{'Annotation': 'The resource is not compliant with policy:kin-poll.',
+               'ComplianceResourceId': 'dev2',
+               'ComplianceResourceType': 'AWS::Kinesis::Stream',
+               'ComplianceType': 'NON_COMPLIANT',
+               'OrderingTimestamp': '2020-05-03T13:55:44.576Z'}],
+             [{'Annotation': 'The resource is compliant with policy:kin-poll.',
+               'ComplianceResourceId': 'dev1',
+               'ComplianceResourceType': 'AWS::Kinesis::Stream',
+               'ComplianceType': 'COMPLIANT',
+               'OrderingTimestamp': '2020-05-03T13:55:44.576Z'}]])
+
+
 class GuardModeTest(BaseTest):
 
     def test_unsupported_resource(self):