Repository: cloud-custodian/cloud-custodian

Issue:
config rule not working for resource type 'app-elb'
So we have a policy that looks for app-elb resource types and reports the resources back. The policy runs just fine over c7n CLI but when its run inside AWS Config as a Lambda function it blows up with the following error message. Also, the same behavior is observed when the Lambda is manually executed. The same rule works just fine with Config and Lambda for 'elb' resource type.

It complains about the Tags array.

`$ custodian run -s . policy.yml`

 Here is the policy snippet

```
policies:

  - name: app-elb-outdated-securitypolicy
    description: Notify when the app-elb has outdated security policy
    resource: app-elb
    mode:
        type: config-rule
        role: arn:aws:iam::<account id>:role/<role>
    filters:
       - .......

```
expected string or buffer: TypeError
Traceback (most recent call last):
File "/var/task/custodian_policy.py", line 5, in run
return handler.dispatch_event(event, context)
File "/var/task/c7n/handler.py", line 91, in dispatch_event
p.push(event, context)
File "/var/task/c7n/policy.py", line 776, in push
return mode.run(event, lambda_ctx)
File "/var/task/c7n/policy.py", line 662, in run
resources = super(ConfigRuleMode, self).run(event, lambda_context)
File "/var/task/c7n/policy.py", line 441, in run
resources = self.resolve_resources(event)
File "/var/task/c7n/policy.py", line 645, in resolve_resources
return [source.load_resource(self.cfg_event['configurationItem'])]
File "/var/task/c7n/resources/appelb.py", line 106, in load_resource
for t in json.loads(item['supplementaryConfiguration']['Tags'])]
File "/usr/lib64/python2.7/json/__init__.py", line 339, in loads
return _default_decoder.decode(s)
File "/usr/lib64/python2.7/json/decoder.py", line 364, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
TypeError: expected string or buffer

Any insight would be appreciated.

Thank you


Discussion:
possibly the load balancer in question is missing tags, as that json load is unconditional which leads to the error.
@kapilt, this was something that I checked as soon as I encountered this error. Here is what is being sent. This is from CloudWatch logs. It looks to be in the correct format but let me know if you were expecting something different.

```
"supplementaryConfiguration": {
	"LoadBalancerAttributes": [{
		"key": "SomeKey1",
		"value": "true"
	},
	{
		"key": "SomeKey2",
		"value": ""
	},
	{
		"key": "SomeKey3",
		"value": "false"
	}],
	"Tags": [{
		"key": "SomeTagKey1",
		"value": "SomeTagKeyValue"
	},
	{
		"key": "SomeTagKey2",
		"value": "SomeTagKeyValue"
	},
	{
		"key": "SomeTagKey3",
		"value": "SomeTagKeyValue"
	}]
}

```
It looks like config changed its record format for app elb, it used to have a bug where it double json encoded the supplementary configuration for app elb, we had worked around that to support app load balancers. They went ahead and fixed the format to not double encode, which caused our workaround to error out. I'm removing the work around.

Resolving patch:
diff --git a/c7n/resources/appelb.py b/c7n/resources/appelb.py
--- a/c7n/resources/appelb.py
+++ b/c7n/resources/appelb.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2017 Capital One Services, LLC
+# Copyright 2016-2018 Capital One Services, LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 import json
 import itertools
 import logging
+import six
 
 from collections import defaultdict
 from c7n.actions import ActionRegistry, BaseAction
@@ -50,15 +51,13 @@ class AppELB(QueryResourceManager):
     """
 
     class resource_type(object):
-
         service = 'elbv2'
         type = 'loadbalancer/app'
-
         enum_spec = ('describe_load_balancers', 'LoadBalancers', None)
         name = 'LoadBalancerName'
         id = 'LoadBalancerArn'
-        filter_name = None
-        filter_type = None
+        filter_name = "Names"
+        filter_type = "list"
         dimension = "LoadBalancer"
         date = 'CreatedTime'
         config_type = 'AWS::ElasticLoadBalancingV2::LoadBalancer'
@@ -88,6 +87,15 @@ def get_source(self, source_type):
 
 class DescribeAppElb(DescribeSource):
 
+    def get_resources(self, ids, cache=True):
+        """Support server side filtering on arns or names
+        """
+        if ids[0].startswith('arn:'):
+            params = {'LoadBalancerArns': ids}
+        else:
+            params = {'Names': ids}
+        return self.query.filter(self.manager, **params)
+
     def augment(self, albs):
         _describe_appelb_tags(
             albs,
@@ -102,8 +110,23 @@ class ConfigAppElb(ConfigSource):
 
     def load_resource(self, item):
         resource = super(ConfigAppElb, self).load_resource(item)
-        resource['Tags'] = [{u'Key': t['key'], u'Value': t['value']}
-          for t in json.loads(item['supplementaryConfiguration']['Tags'])]
+        item_tags = item['supplementaryConfiguration']['Tags']
+
+        # Config originally stored supplementaryconfig on elbv2 as json
+        # strings. Support that format for historical queries.
+        if isinstance(item_tags, six.string_types):
+            item_tags = json.loads(item_tags)
+        resource['Tags'] = [
+            {'Key': t['key'], 'Value': t['value']} for t in item_tags]
+
+        item_attrs = item['supplementaryConfiguration'][
+            'LoadBalancerAttributes']
+        if isinstance(item_attrs, six.string_types):
+            item_attrs = json.loads(item_attrs)
+        # Matches annotation of AppELBAttributeFilterBase filter
+        resource['Attributes'] = {
+            attr['key']: parse_attribute_value(attr['value']) for
+            attr in item_attrs}
         return resource
 
 
@@ -508,6 +531,16 @@ def _process_listeners(alb):
             list(w.map(_process_listeners, albs))
 
 
+def parse_attribute_value(v):
+    if v.isdigit():
+        v = int(v)
+    elif v == 'true':
+        v = True
+    elif v == 'false':
+        v = False
+    return v
+
+
 class AppELBAttributeFilterBase(object):
     """ Mixin base class for filters that query LB attributes.
     """
@@ -524,13 +557,7 @@ def _process_attributes(alb):
                 # flatten out the list of dicts and cast
                 for pair in results['Attributes']:
                     k = pair['Key']
-                    v = pair['Value']
-                    if v.isdigit():
-                        v = int(v)
-                    elif v == 'true':
-                        v = True
-                    elif v == 'false':
-                        v = False
+                    v = parse_attribute_value(pair['Value'])
                     alb['Attributes'][k] = v
 
         with self.manager.executor_factory(max_workers=2) as w:


Test patch:
diff --git a/tests/data/config/app-elb-config-event.json b/tests/data/config/app-elb-config-event.json
new file mode 100644
--- /dev/null
+++ b/tests/data/config/app-elb-config-event.json
@@ -0,0 +1,12 @@
+{
+    "configRuleId": "config-rule-hrjmby",
+    "version": "1.0",
+    "configRuleName": "custodian-app-elb-checker",
+    "configRuleArn": "arn:aws:config:us-east-1:644160558196:config-rule/config-rule-hrjmby",
+    "invokingEvent": "{\"configurationItemDiff\":{\"changedProperties\":{\"SupplementaryConfiguration.Tags.1\":{\"previousValue\":null,\"updatedValue\":{\"key\":\"Env\",\"value\":\"Dev\"},\"changeType\":\"CREATE\"},\"SupplementaryConfiguration.Tags.0\":{\"previousValue\":null,\"updatedValue\":{\"key\":\"App\",\"value\":\"DevTest\"},\"changeType\":\"CREATE\"}},\"changeType\":\"UPDATE\"},\"configurationItem\":{\"relatedEvents\":[],\"relationships\":[{\"resourceId\":\"sg-4b9ada34\",\"resourceName\":null,\"resourceType\":\"AWS::EC2::SecurityGroup\",\"name\":\"Is associated with SecurityGroup\"},{\"resourceId\":\"subnet-6f273034\",\"resourceName\":null,\"resourceType\":\"AWS::EC2::Subnet\",\"name\":\"Is attached to Subnet\"},{\"resourceId\":\"subnet-ecfb30a4\",\"resourceName\":null,\"resourceType\":\"AWS::EC2::Subnet\",\"name\":\"Is attached to Subnet\"},{\"resourceId\":\"vpc-f1516b97\",\"resourceName\":null,\"resourceType\":\"AWS::EC2::VPC\",\"name\":\"Is contained in Vpc\"}],\"configuration\":{\"loadBalancerArn\":\"arn:aws:elasticloadbalancing:us-east-1:644160558196:loadbalancer/app/test/7299960b1f36557d\",\"dNSName\":\"internal-test-288037075.us-east-1.elb.amazonaws.com\",\"canonicalHostedZoneId\":\"Z35SXDOTRQ7X7K\",\"createdTime\":\"2018-09-12T13:54:12.070Z\",\"loadBalancerName\":\"test\",\"scheme\":\"internal\",\"vpcId\":\"vpc-f1516b97\",\"state\":{\"code\":\"active\",\"reason\":null},\"type\":\"application\",\"availabilityZones\":[{\"zoneName\":\"us-east-1b\",\"subnetId\":\"subnet-6f273034\",\"loadBalancerAddresses\":[]},{\"zoneName\":\"us-east-1a\",\"subnetId\":\"subnet-ecfb30a4\",\"loadBalancerAddresses\":[]}],\"securityGroups\":[\"sg-4b9ada34\"],\"ipAddressType\":\"ipv4\"},\"supplementaryConfiguration\":{\"LoadBalancerAttributes\":[{\"key\":\"access_logs.s3.prefix\",\"value\":\"\"},{\"key\":\"routing.http2.enabled\",\"value\":\"true\"},{\"key\":\"access_logs.s3.bucket\",\"value\":\"\"},{\"key\":\"deletion_protection.enabled\",\"value\":\"false\"},{\"key\":\"access_logs.s3.enabled\",\"value\":\"false\"},{\"key\":\"idle_timeout.timeout_seconds\",\"value\":\"60\"}],\"Tags\":[{\"key\":\"App\",\"value\":\"DevTest\"},{\"key\":\"Env\",\"value\":\"Dev\"}]},\"tags\":{\"App\":\"DevTest\",\"Env\":\"Dev\"},\"configurationItemVersion\":\"1.3\",\"configurationItemCaptureTime\":\"2018-09-14T10:38:48.177Z\",\"configurationStateId\":1536921528177,\"awsAccountId\":\"644160558196\",\"configurationItemStatus\":\"OK\",\"resourceType\":\"AWS::ElasticLoadBalancingV2::LoadBalancer\",\"resourceId\":\"arn:aws:elasticloadbalancing:us-east-1:644160558196:loadbalancer/app/test/7299960b1f36557d\",\"resourceName\":\"test\",\"ARN\":\"arn:aws:elasticloadbalancing:us-east-1:644160558196:loadbalancer/app/test/7299960b1f36557d\",\"awsRegion\":\"us-east-1\",\"availabilityZone\":\"Multiple Availability Zones\",\"configurationStateMd5Hash\":\"\",\"resourceCreationTime\":\"2018-09-12T13:54:12.070Z\"},\"notificationCreationTime\":\"2018-09-14T10:38:50.976Z\",\"messageType\":\"ConfigurationItemChangeNotification\",\"recordVersion\":\"1.3\"}",
+    "resultToken": "eyJlbmNyeXB0ZWREYXRhIjpbLTI4LC0xNSwtNDMsNywtMzcsLTI0LDc5LC0xMjcsLTEwMiw2NywtOTksNzcsLTYzLDk5LC02OCwtNTksNTMsLTgsLTM5LDQ1LDM1LC01MCwyMSwtMTcsMjUsLTEsNDksLTI0LC0xOSwtMTQsLTQ0LC0xOSwtMTA5LC04OCwtMjksLTc5LDMxLDYyLDY3LDI2LC0yNSwtMjMsLTExLDQxLDk2LC04NSwtMTgsLTQyLDExLC0xMTMsLTk0LDYxLC0xMjUsMjksNDUsLTQ3LDExOSwtODcsMTUsLTM5LDc0LDEyMSwxMDQsLTIwLC04Nyw2OCwtNCw4MiwtMjgsLTY3LDI1LC03Myw0MiwtNDMsLTYwLC01NSwxMjUsLTE2LDkzLDQ1LDEwLDY4LC0zOSwtNzEsMjAsNjYsLTEwNywtMTEzLC03OCwxMCw4NCwtMzEsLTU3LDkzLC0xMDgsMTIyLDEyLC05LDIxLDk4LC0zNCwtMzksMzYsMTA3LC0xNCwyOCwtMiw3NSwxMTUsNTEsMzMsLTMzLDQ1LC0xLDg5LDQ3LDU4LC0xMjQsLTc5LC02OCwtMTEwLC0xMjYsLTgwLDUwLDEzLDExNywzMiwxMDQsMTI1LC05MywtMTE1LC03NCwtMTI4LDMsLTgzLDMwLDExNCw4OSwxMDMsLTEyMCwxMDMsLTgyLC0xMTIsLTg3LDU4LDUxLC04MCw5LDU4LC04MywxNywxMjIsNzUsNzgsNzUsLTYsLTMzLC00OCwyMiwtNjEsMTYsLTEwMSw2NiwxMCw2Niw3MSwyNCwtMzksLTYxLC0zNyw3NCw2NSwtMjgsLTk0LC02Niw3MywyNywzOCwxMjcsNzYsMyw0OSwtNDQsNiw1MywtMTAzLC0zLC0xMjYsNTIsMjQsMTgsLTI4LDg3LC0xMTcsLTc2LDU3LC0xMTAsMTA2LC04MCw3OSwtNjQsMiw1Myw3MywtNzYsLTMxLDE2LC0xMSwtOSwyMiwtOTcsLTE5LC01OCwtMzksLTE1LDE3LC02OCwtMzEsLTgsMTE1LDI5LDE3LDI0LC0xNiw4NiwtMjksMTA5LDEyNiw2NSwtMzAsLTUzLDEwMywtOTksLTcwLC0xMDEsMTAyLC03MiwtMzMsLTcwLDI1LC01OCwtMTQsNSwtMTEyLC03NiwtMTIxLDcwLDEwOCw1Miw0Nyw4OCw4MCwtMjMsMjksNDksMTA1LDExMiwtNzcsLTYyLDU1LC02LC0xOSwxMiw5NiwtNjQsLTk4LDc1LC05OSwxMjQsMTEyLDgxLC0xMDYsLTExMCwtNzYsNzUsLTEwNSwxMCwxMTYsLTEyNSw0LDQ3LDkwLC0xMTYsLTMsLTEyLC04OSw0MSwtMjgsNTQsLTExNiw2NywtOSw2MSwtNjksLTg0LDEzLDgzLDcyLDk3LDg2LC0yNiwtNDgsLTMxLC03NSwtNDAsMTYsLTksLTgwLC0yNiwtMTIyLDEwMiwwLDM4LDc5LDEwLDgxLC02NSw5OSw2OCwtMjgsLTgyLDMsODIsLTM3LDk0LC0zOSwtNTAsLTcsLTQ2LDgzLDEyMCwxMjUsNCw0NiwxMDMsLTEwNSwtNjgsNzYsLTMwLC0yNywtMzAsLTc2LDExOSwxMDYsLTQ2LC0xMDYsNjcsNDgsLTcwLDExNiwxMTgsLTEyOCw0NSwtNTEsLTUyLDQ0LC01LDcsMTEyLDEwNiwtMjAsLTg3LC0xMSwtNDcsLTE2LC0yMSwtMTA5LC05NywtNjYsMTA3LC0xMTYsLTIsNjQsLTQxLDg3LDEyNCw1NSw4Nyw5MSwtMzMsLTYwLDEyNywtMTI3LDEyLDExNiwtODIsLTUsLTEyNSwyNywyLC0yMiwtMTIxLC0xMCwxMDYsLTEyMiwtNDEsLTEyMiwxMDYsLTQ5LDc3LC04MywxOCwtNTIsNDIsMTIsOTUsODIsLTQsLTI0LC03OSwyNSwxMTYsNzgsNTcsNjgsMSw4MSw3NCw2NSwtNjUsMzgsLTUzLC0xMTIsLTEwOSwyMyw5LC03MiwtMjcsNjEsLTM1LC0yMyw0NiwtMTgsLTM0LC0xMTIsMjcsLTY2LC03Miw2OSwzMiw5MCwtNjksNTgsMCwtNzYsMzksLTM2LC05NywxMDIsLTcwLDQ5LC0zNiwtNTgsNzUsNyw1NiwyOCw3MywtNTYsODcsNjEsMTEwLC02NCw2OCw4OCwzMCwtODEsLTEyNyw0NCwxMTAsLTYxLC02Miw2NiwtNyw4OSwtNDMsLTY1LC0xMTgsLTQ4LDExLC0xMiwtMTgsNDUsLTg4LC0xMjIsNTUsLTMzLC03MCw0NywtMTA1LC0zNCwtNzAsLTEwOCw4NSwtMTgsLTcyLC01NywtNTUsNTUsLTQ2LC04NywxMjYsLTExOSwxMjIsNjMsMzAsMTE4LC0xMDAsLTc4LDIsLTQwLDUsLTY1LDU4LDI1LC00OSwtMzAsNTIsLTMzLDMzLDg5LDIxLDEwNywtMjQsMjYsLTEwLC00NSwtNjIsNDMsNTMsNTEsLTMyLDcwLDE2LC0xMiwtOTgsLTM4LC01LC0zNiw1MiwtNTksNjksLTQ5LDk0LDIsLTY3LDY1LDk1LC0xOCw3MSwxMTcsMjEsLTUzLC01OCwtMSwtMTA2LC0yMiw1Myw2NywtOTgsLTEwLC0xMywtMTEwLC05NSwtMjAsNTEsODEsMTE3LC02LDcwLC01Miw1MiwyNiw5Myw0MiwtNywtMTE5LC0xLDUsMyw2NiwtODIsLTcxLDk0LC0xMDQsLTYwLC00LC0zNywtMTAyLC0xMDEsLTQsODMsLTgxLC0xMjUsOTgsLTEyMSwtOTMsMTI1LC0xMTQsMTE2LC0xMTYsMTIsMzMsLTY3LDc1LC03NiwtOSwtNDcsMiwtMzIsNDgsLTQyLC00OCwtNjgsNjgsMzQsMzcsLTk5LDEwNSw0NiwzNSwzMCwxMDYsLTExMCwtMjMsLTI4LDQ0LDMsLTYwLDI1LDU2LDI3LC00Nyw1NSw2MSw5Miw4MCwtNSwtMTI1LC05OSwyOSwzNSwtNzMsLTEyNSwtODYsLTcwLC04NywtMTgsLTU0LC03NSwtNDMsMTIsLTEyOCw3MiwzMCwtMTEzLDI2LC02MCwtOSwtNTcsLTMyLDkwLDEwMSwtMTA0LC0zOSwxMywtMTE3LDI5LDg1LC03NCwtMTEsLTEwNSwtNDFdLCJtYXRlcmlhbFNldFNlcmlhbE51bWJlciI6MSwiaXZQYXJhbWV0ZXJTcGVjIjp7Iml2IjpbLTIyLDY3LDU5LC0xMDgsLTY1LC01NCwtNjcsOTksMTA3LC0yNSwtODUsLTcxLDMsLTQyLC01NSwyMF19fQ==",
+    "eventLeftScope": false,
+    "debug": true,
+    "executionRoleArn": "arn:aws:iam::644160558196:role/service-role/config-role-us-east-1",
+    "accountId": "644160558196"
+}
diff --git a/tests/data/placebo/test_appelb_config_event/config.PutEvaluations_1.json b/tests/data/placebo/test_appelb_config_event/config.PutEvaluations_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_appelb_config_event/config.PutEvaluations_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedEvaluations": [],
+        "ResponseMetadata": {
+            "RequestId": "46fadbcf-b806-11e8-bcd8-a5874065ffe6",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "46fadbcf-b806-11e8-bcd8-a5874065ffe6",
+                "strict-transport-security": "max-age=86400",
+                "content-type": "application/x-amz-json-1.1",
+                "content-length": "24",
+                "date": "Fri, 14 Sep 2018 10:09:32 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_appelb.py b/tests/test_appelb.py
--- a/tests/test_appelb.py
+++ b/tests/test_appelb.py
@@ -23,6 +23,32 @@
 
 class AppELBTest(BaseTest):
 
+    def test_appelb_config_event(self):
+        session_factory = self.replay_flight_data('test_appelb_config_event')
+        p = self.load_policy({
+            'name': 'appelb-checker',
+            'resource': 'app-elb',
+            'mode': {
+                'type': 'config-rule'}},
+            session_factory=session_factory)
+        event = event_data("app-elb-config-event.json", "config")
+        result = p.push(event, {})[0]
+        self.assertEqual(
+            result['DNSName'],
+            'internal-test-288037075.us-east-1.elb.amazonaws.com')
+        self.assertEqual(
+            result['Tags'],
+            [{'Key': 'App', 'Value': 'DevTest'},
+             {'Key': 'Env', 'Value': 'Dev'}])
+        self.assertEqual(
+            result['Attributes'],
+            {'access_logs.s3.bucket': '',
+             'access_logs.s3.enabled': False,
+             'access_logs.s3.prefix': '',
+             'deletion_protection.enabled': False,
+             'idle_timeout.timeout_seconds': 60,
+             'routing.http2.enabled': True})
+
     def test_appelb_config_source(self):
         event = event_data("app-elb.json", "config")
         p = self.load_policy({"name": "appelbcfg", "resource": "app-elb"})