Repository: cloud-custodian/cloud-custodian

Issue:
aws - lambda@edge expression on cloudfront resources
### Describe the bug

Ran into a use case where lambda@edge was being used in conjunction with CacheBehaviors on cloudfront. Figured that the jmespath expression for exposing lambda@edge wrt CacheBehavior should be `'CacheBehaviors.Items[].LambdaFunctionAssociations.Items[]'` instead of `'CacheBehaviors.LambdaFunctionAssociations.Items'` as per the [get_distribution](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudfront/client/get_distribution.html) doc

Secondly, many lambda function versions were being used as lambda@edge on a single distribution with different path patterns as shown below, causing that distribution id to show up many times re: c7n:DistributionIds annotation

Example - 
```
CloudFront: [XXXXXXXXXXXXXX](https://us-east-1.console.aws.amazon.com/cloudfront/v3/home?region=us-east-1#/distributions/XXXXXXXXXXXXXX)
arn:aws:cloudfront::123456789123:distribution/XXXXXXXXXXXXXX
Details
Event type: origin-request
Include Body: No
Path pattern: /xyz/abc
Service principal: replicator.lambda.amazonaws.com
Statement ID: replicator.lambda.GetFunction

CloudFront: [XXXXXXXXXXXXXX](https://us-east-1.console.aws.amazon.com/cloudfront/v3/home?region=us-east-1#/distributions/XXXXXXXXXXXXXX)
arn:aws:cloudfront::123456789123:distribution/XXXXXXXXXXXXXX
Details
Event type: origin-request
Include Body: No
Path pattern: /hello/world
Service principal: replicator.lambda.amazonaws.com
Statement ID: replicator.lambda.GetFunction
```

resources.json would have something like
    "c7n:DistributionIds": [
      "XXXXXXXXXXXXXX",
      "XXXXXXXXXXXXXX",
      "XXXXXXXXXXXXXX",
      "XXXXXXXXXXXXXX"
    ]

### What did you expect to happen?

I expected 2 + 2 to equal 4, but instead 2 + 2 equaled 6!

### Cloud Provider

Amazon Web Services (AWS)

### Cloud Custodian version and dependency information

_No response_

### Policy

```shell
policies:
  - name: lambda-edge
    resource: lambda
    filters:
      - type: lambda-edge
        state: True
```


### Relevant log/traceback output

_No response_

### Extra information or context

_No response_


Discussion:

Resolving patch:
diff --git a/c7n/resources/awslambda.py b/c7n/resources/awslambda.py
--- a/c7n/resources/awslambda.py
+++ b/c7n/resources/awslambda.py
@@ -933,7 +933,7 @@ class LambdaEdgeFilter(Filter):
     def get_lambda_cf_map(self):
         cfs = self.manager.get_resource_manager('distribution').resources()
         func_expressions = ('DefaultCacheBehavior.LambdaFunctionAssociations.Items',
-          'CacheBehaviors.LambdaFunctionAssociations.Items')
+          'CacheBehaviors.Items[].LambdaFunctionAssociations.Items[]')
         lambda_dist_map = {}
         for d in cfs:
             for exp in func_expressions:
@@ -941,7 +941,7 @@ def get_lambda_cf_map(self):
                     for function in jmespath_search(exp, d):
                         # Geting rid of the version number in the arn
                         lambda_edge_arn = ':'.join(function['LambdaFunctionARN'].split(':')[:-1])
-                        lambda_dist_map.setdefault(lambda_edge_arn, []).append(d['Id'])
+                        lambda_dist_map.setdefault(lambda_edge_arn, set()).add(d['Id'])
         return lambda_dist_map
 
     def process(self, resources, event=None):
@@ -952,7 +952,7 @@ def process(self, resources, event=None):
         lambda_edge_cf_map = self.get_lambda_cf_map()
         for r in resources:
             if (r['FunctionArn'] in lambda_edge_cf_map and self.data.get('state')):
-                r[annotation_key] = lambda_edge_cf_map.get(r['FunctionArn'])
+                r[annotation_key] = list(lambda_edge_cf_map.get(r['FunctionArn']))
                 results.append(r)
             elif (r['FunctionArn'] not in lambda_edge_cf_map and not self.data.get('state')):
                 results.append(r)


Test patch:
diff --git a/tests/data/placebo/test_lambda_edge_multiple_associations_cache/cloudfront.ListDistributions_1.json b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/cloudfront.ListDistributions_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/cloudfront.ListDistributions_1.json
@@ -0,0 +1,209 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "DistributionList": {
+            "Marker": "",
+            "MaxItems": 100,
+            "IsTruncated": false,
+            "Quantity": 1,
+            "Items": [
+                {
+                    "Id": "E3Q5UC7SQLL7MN",
+                    "ARN": "arn:aws:cloudfront::644160558196:distribution/E3Q5UC7SQLL7MN",
+                    "Status": "InProgress",
+                    "LastModifiedTime": {
+                        "__class__": "datetime",
+                        "year": 2024,
+                        "month": 3,
+                        "day": 20,
+                        "hour": 4,
+                        "minute": 15,
+                        "second": 57,
+                        "microsecond": 649000
+                    },
+                    "DomainName": "d1j7wua3tltg12.cloudfront.net",
+                    "Aliases": {
+                        "Quantity": 0
+                    },
+                    "Origins": {
+                        "Quantity": 1,
+                        "Items": [
+                            {
+                                "Id": "c7n-test-bucket.s3.us-east-1",
+                                "DomainName": "c7n-test-bucket.s3.us-east-1.amazonaws.com",
+                                "OriginPath": "",
+                                "CustomHeaders": {
+                                    "Quantity": 0
+                                },
+                                "S3OriginConfig": {
+                                    "OriginAccessIdentity": ""
+                                },
+                                "ConnectionAttempts": 3,
+                                "ConnectionTimeout": 10,
+                                "OriginShield": {
+                                    "Enabled": false
+                                },
+                                "OriginAccessControlId": ""
+                            }
+                        ]
+                    },
+                    "OriginGroups": {
+                        "Quantity": 0
+                    },
+                    "DefaultCacheBehavior": {
+                        "TargetOriginId": "c7n-test-bucket.s3.us-east-1",
+                        "TrustedSigners": {
+                            "Enabled": false,
+                            "Quantity": 0
+                        },
+                        "TrustedKeyGroups": {
+                            "Enabled": false,
+                            "Quantity": 0
+                        },
+                        "ViewerProtocolPolicy": "allow-all",
+                        "AllowedMethods": {
+                            "Quantity": 2,
+                            "Items": [
+                                "HEAD",
+                                "GET"
+                            ],
+                            "CachedMethods": {
+                                "Quantity": 2,
+                                "Items": [
+                                    "HEAD",
+                                    "GET"
+                                ]
+                            }
+                        },
+                        "SmoothStreaming": false,
+                        "Compress": true,
+                        "LambdaFunctionAssociations": {
+                            "Quantity": 0
+                        },
+                        "FunctionAssociations": {
+                            "Quantity": 0
+                        },
+                        "FieldLevelEncryptionId": "",
+                        "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
+                    },
+                    "CacheBehaviors": {
+                        "Quantity": 2,
+                        "Items": [
+                            {
+                                "PathPattern": "/beta/hello",
+                                "TargetOriginId": "c7n-test-bucket.s3.us-east-1",
+                                "TrustedSigners": {
+                                    "Enabled": false,
+                                    "Quantity": 0
+                                },
+                                "TrustedKeyGroups": {
+                                    "Enabled": false,
+                                    "Quantity": 0
+                                },
+                                "ViewerProtocolPolicy": "redirect-to-https",
+                                "AllowedMethods": {
+                                    "Quantity": 2,
+                                    "Items": [
+                                        "HEAD",
+                                        "GET"
+                                    ],
+                                    "CachedMethods": {
+                                        "Quantity": 2,
+                                        "Items": [
+                                            "HEAD",
+                                            "GET"
+                                        ]
+                                    }
+                                },
+                                "SmoothStreaming": false,
+                                "Compress": true,
+                                "LambdaFunctionAssociations": {
+                                    "Quantity": 1,
+                                    "Items": [
+                                        {
+                                            "LambdaFunctionARN": "arn:aws:lambda:us-east-1:644160558196:function:c7n-lambda-edge-new:1",
+                                            "EventType": "origin-request",
+                                            "IncludeBody": false
+                                        }
+                                    ]
+                                },
+                                "FunctionAssociations": {
+                                    "Quantity": 0
+                                },
+                                "FieldLevelEncryptionId": "",
+                                "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
+                            },
+                            {
+                                "PathPattern": "/beta/world",
+                                "TargetOriginId": "c7n-test-bucket.s3.us-east-1",
+                                "TrustedSigners": {
+                                    "Enabled": false,
+                                    "Quantity": 0
+                                },
+                                "TrustedKeyGroups": {
+                                    "Enabled": false,
+                                    "Quantity": 0
+                                },
+                                "ViewerProtocolPolicy": "redirect-to-https",
+                                "AllowedMethods": {
+                                    "Quantity": 2,
+                                    "Items": [
+                                        "HEAD",
+                                        "GET"
+                                    ],
+                                    "CachedMethods": {
+                                        "Quantity": 2,
+                                        "Items": [
+                                            "HEAD",
+                                            "GET"
+                                        ]
+                                    }
+                                },
+                                "SmoothStreaming": false,
+                                "Compress": true,
+                                "LambdaFunctionAssociations": {
+                                    "Quantity": 1,
+                                    "Items": [
+                                        {
+                                            "LambdaFunctionARN": "arn:aws:lambda:us-east-1:644160558196:function:test-lambda-edge:4",
+                                            "EventType": "viewer-response",
+                                            "IncludeBody": false
+                                        }
+                                    ]
+                                },
+                                "FunctionAssociations": {
+                                    "Quantity": 0
+                                },
+                                "FieldLevelEncryptionId": "",
+                                "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6"
+                            }
+                        ]
+                    },
+                    "CustomErrorResponses": {
+                        "Quantity": 0
+                    },
+                    "Comment": "",
+                    "PriceClass": "PriceClass_All",
+                    "Enabled": true,
+                    "ViewerCertificate": {
+                        "CloudFrontDefaultCertificate": true,
+                        "SSLSupportMethod": "vip",
+                        "MinimumProtocolVersion": "TLSv1",
+                        "CertificateSource": "cloudfront"
+                    },
+                    "Restrictions": {
+                        "GeoRestriction": {
+                            "RestrictionType": "none",
+                            "Quantity": 0
+                        }
+                    },
+                    "WebACLId": "",
+                    "HttpVersion": "HTTP2",
+                    "IsIPV6Enabled": true,
+                    "Staging": false
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_edge_multiple_associations_cache/lambda.ListFunctions_1.json b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/lambda.ListFunctions_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/lambda.ListFunctions_1.json
@@ -0,0 +1,74 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "Functions": [
+            {
+                "FunctionName": "c7n-lambda-edge-new",
+                "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:c7n-lambda-edge-new",
+                "Runtime": "nodejs14.x",
+                "Role": "arn:aws:iam::644160558196:role/CloudCustodianRole",
+                "Handler": "index.handler",
+                "CodeSize": 376,
+                "Description": "Blueprint for returning HTTP redirect implemented in NodeJS.",
+                "Timeout": 1,
+                "MemorySize": 128,
+                "LastModified": "2023-08-30T03:28:55.000+0000",
+                "CodeSha256": "qxW4g+uEr2cGdO3l52WOfSVjv5TdNG6Pd+rj8YJFjAw=",
+                "Version": "$LATEST",
+                "TracingConfig": {
+                    "Mode": "PassThrough"
+                },
+                "RevisionId": "6780e2d7-4336-46c1-aba9-f5c6f7aa766a",
+                "PackageType": "Zip",
+                "Architectures": [
+                    "x86_64"
+                ],
+                "EphemeralStorage": {
+                    "Size": 512
+                },
+                "SnapStart": {
+                    "ApplyOn": "None",
+                    "OptimizationStatus": "Off"
+                },
+                "LoggingConfig": {
+                    "LogFormat": "Text",
+                    "LogGroup": "/aws/lambda/c7n-lambda-edge-new"
+                }
+            },
+            {
+                "FunctionName": "test-lambda-edge",
+                "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:test-lambda-edge",
+                "Runtime": "nodejs14.x",
+                "Role": "arn:aws:iam::644160558196:role/CloudCustodianRole",
+                "Handler": "index.handler",
+                "CodeSize": 389,
+                "Description": "Blueprint for modifying CloudFront response header implemented in NodeJS.",
+                "Timeout": 1,
+                "MemorySize": 128,
+                "LastModified": "2023-03-15T01:20:54.053+0000",
+                "CodeSha256": "PRrkU1iZ2meuOdge3XCeQPebrEknjcZlEEMBbMAT70g=",
+                "Version": "$LATEST",
+                "TracingConfig": {
+                    "Mode": "PassThrough"
+                },
+                "RevisionId": "7e0c0cd4-45ec-4b9a-a33f-bf2e18d22cbc",
+                "PackageType": "Zip",
+                "Architectures": [
+                    "x86_64"
+                ],
+                "EphemeralStorage": {
+                    "Size": 512
+                },
+                "SnapStart": {
+                    "ApplyOn": "None",
+                    "OptimizationStatus": "Off"
+                },
+                "LoggingConfig": {
+                    "LogFormat": "Text",
+                    "LogGroup": "/aws/lambda/test-lambda-edge"
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_1.json b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_1.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:lambda:us-east-1:644160558196:function:c7n-lambda-edge-new",
+                "Tags": [
+                    {
+                        "Key": "lambda-console:blueprint",
+                        "Value": "cloudfront-http-redirect"
+                    }
+                ]
+            },
+            {
+                "ResourceARN": "arn:aws:lambda:us-east-1:644160558196:function:test-lambda-edge",
+                "Tags": [
+                    {
+                        "Key": "lambda-console:blueprint",
+                        "Value": "cloudfront-modify-response-header"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_2.json b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_edge_multiple_associations_cache/tagging.GetResources_2.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:cloudfront::644160558196:distribution/E3Q5UC7SQLL7MN",
+                "Tags": [
+                    {
+                        "Key": "test",
+                        "Value": "c7n"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_lambda.py b/tests/test_lambda.py
--- a/tests/test_lambda.py
+++ b/tests/test_lambda.py
@@ -506,6 +506,23 @@ def test_lambda_edge(self):
             [r["FunctionName"] for r in resources],
             ["c7n-lambda-edge-new", "test-lambda-edge"])
 
+    def test_lambda_edge_multiple_associations_cache(self):
+        factory = self.replay_flight_data("test_lambda_edge_multiple_associations_cache")
+        p = self.load_policy(
+            {
+                "name": "lambda-edge-filter",
+                "resource": "lambda",
+                "filters": [{"type": "lambda-edge",
+                            "state": True}],
+            },
+            session_factory=factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 2)
+        self.assertEqual(
+            [r["FunctionName"] for r in resources],
+            ["c7n-lambda-edge-new", "test-lambda-edge"])
+
 
 class LambdaTagTest(BaseTest):