Repository: cloud-custodian/cloud-custodian

Issue:
Cache lookup doesn't return correct results with kms key alias
### Describe the bug

When the below shown policies are run with cache set, custodian returns incorrect results

```
policies:
  - name: kms-key-all-resources
    resource: aws.kms-key
  - name: sns-encryption-with-key-alias
    resource: aws.sns
    filters:
      - or:
        - type: value
          key: KmsMasterKeyId
          op: eq
          value: null
        - not:
          - or:
            - type: kms-key
              key: c7n:AliasName
              value: alias/tes/pratyush
            - type: kms-key
              key: c7n:AliasName
              op: regex
              value: ^(alias\/tes\/pratyush\/.*)
```
Output:
```
(cloud-custodian) ➜  policies custodian run -c kms.yml -s output --region us-east-1 --cache-period=25
2023-04-18 23:08:59,944: custodian.policy:INFO policy:kms-key-all-resources resource:aws.kms-key region:us-east-1 count:49 time:4.32
2023-04-18 23:09:01,062: custodian.filters:WARNING Resource sns:arn:aws:sns:us-east-1:644160558196:test123 references non existant Key: alias/tes/pratyush
2023-04-18 23:09:01,064: custodian.filters:WARNING Resource sns:arn:aws:sns:us-east-1:644160558196:test123 references non existant Key: alias/tes/pratyush
2023-04-18 23:09:01,064: custodian.policy:INFO policy:sns-encryption-with-key-alias resource:aws.sns region:us-east-1 count:1 time:1.11
```
The key `alias/tes/pratyush` is present but looks like it's not found in the cache. I did some investigation and found that cache lookup returns correct results only when the looked up via key arn and key id.

### What did you expect to happen?

The policy should not filter any resources since the key is present and the topic is encrypted with the key

### Cloud Provider

Amazon Web Services (AWS)

### Cloud Custodian version and dependency information

0.9.26

### Policy

Posted above

### Relevant log/traceback output

Posted above

### Extra information or context

To reproduce this error, I created a topic and applied encryption simultaneously using the key alias
```
response = client.create_topic(Name='test123', Attributes={'KmsMasterKeyId': 'alias/tes/pratyush'})
```


Discussion:

Resolving patch:
diff --git a/c7n/filters/kms.py b/c7n/filters/kms.py
--- a/c7n/filters/kms.py
+++ b/c7n/filters/kms.py
@@ -55,12 +55,12 @@ def get_related(self, resources):
             related = resource_manager.resources()
         related_map = {}
 
-        # A resource's key property may point to an explicit ID or a key alias.
-        # Be sure that a related key lookup covers both cases.
         for r in related:
+            # `AliasNames` is set when we fetch keys, but only for keys
+            # which have aliases defined. Fall back to an empty string
+            # to avoid lookup errors in filters.
+            r['c7n:AliasName'] = r.get('AliasNames', ('',))[0]
             related_map[r['KeyId']] = r
-            for alias in r.get('AliasNames', []):
-                related_map[alias] = r
 
         return related_map
 
@@ -68,6 +68,8 @@ def get_related_ids(self, resources):
         related_ids = super().get_related_ids(resources)
         normalized_ids = []
         for rid in related_ids:
+            if rid.startswith('alias'):
+                rid = self.alias_to_id.get(rid, rid)
             if rid.startswith('arn:'):
                 normalized_ids.append(rid.rsplit('/', 1)[-1])
             else:
@@ -75,10 +77,15 @@ def get_related_ids(self, resources):
         return normalized_ids
 
     def process(self, resources, event=None):
+        self.alias_to_id = self.key_alias_to_key_id()
         related = self.get_related(resources)
-        for r in related.values():
-            # `AliasNames` is set when we fetch keys, but only for keys
-            # which have aliases defined. Fall back to an empty string
-            # to avoid lookup errors in filters.
-            r['c7n:AliasName'] = r.get('AliasNames', ('',))[0]
         return [r for r in resources if self.process_resource(r, related)]
+
+    def key_alias_to_key_id(self):
+        # convert key alias to key id for cache lookup
+        # else cache lookup returns [] even if the key exists
+        key_manager = self.get_resource_manager()
+        alias_to_id = {}
+        for kid, kaliases in key_manager.alias_map.items():
+            alias_to_id.update({alias: kid for alias in kaliases})
+        return alias_to_id
diff --git a/c7n/filters/related.py b/c7n/filters/related.py
--- a/c7n/filters/related.py
+++ b/c7n/filters/related.py
@@ -1,8 +1,8 @@
 # Copyright The Cloud Custodian Authors.
 # SPDX-License-Identifier: Apache-2.0
 import importlib
-
 import jmespath
+from functools import lru_cache
 
 from .core import ValueFilter, OPERATORS
 from c7n.query import ChildResourceQuery
@@ -49,6 +49,7 @@ def get_related(self, resources):
         return {r[model.id]: r for r in related
                 if r[model.id] in related_ids}
 
+    @lru_cache(maxsize=None)
     def get_resource_manager(self):
         mod_path, class_name = self.RelatedResource.rsplit('.', 1)
         module = importlib.import_module(mod_path)


Test patch:
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_1.json
@@ -0,0 +1,33 @@
+{
+    "status_code": 200,
+    "data": {
+        "KeyMetadata": {
+            "AWSAccountId": "644160558196",
+            "KeyId": "5a4af27f-a9e6-40fb-a2ed-fc3fbfdb34a5",
+            "Arn": "arn:aws:kms:us-east-1:644160558196:key/5a4af27f-a9e6-40fb-a2ed-fc3fbfdb34a5",
+            "CreationDate": {
+                "__class__": "datetime",
+                "year": 2021,
+                "month": 4,
+                "day": 13,
+                "hour": 5,
+                "minute": 47,
+                "second": 15,
+                "microsecond": 826000
+            },
+            "Enabled": true,
+            "Description": "Default master key that protects my SQS messages when no other key is defined",
+            "KeyUsage": "ENCRYPT_DECRYPT",
+            "KeyState": "Enabled",
+            "Origin": "AWS_KMS",
+            "KeyManager": "AWS",
+            "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
+            "KeySpec": "SYMMETRIC_DEFAULT",
+            "EncryptionAlgorithms": [
+                "SYMMETRIC_DEFAULT"
+            ],
+            "MultiRegion": false
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_2.json b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.DescribeKey_2.json
@@ -0,0 +1,33 @@
+{
+    "status_code": 200,
+    "data": {
+        "KeyMetadata": {
+            "AWSAccountId": "644160558196",
+            "KeyId": "c928024b-2802-4bd7-bee1-d879c584a0ef",
+            "Arn": "arn:aws:kms:us-east-1:644160558196:key/c928024b-2802-4bd7-bee1-d879c584a0ef",
+            "CreationDate": {
+                "__class__": "datetime",
+                "year": 2023,
+                "month": 5,
+                "day": 3,
+                "hour": 13,
+                "minute": 16,
+                "second": 58,
+                "microsecond": 534000
+            },
+            "Enabled": true,
+            "Description": "",
+            "KeyUsage": "ENCRYPT_DECRYPT",
+            "KeyState": "Enabled",
+            "Origin": "AWS_KMS",
+            "KeyManager": "CUSTOMER",
+            "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
+            "KeySpec": "SYMMETRIC_DEFAULT",
+            "EncryptionAlgorithms": [
+                "SYMMETRIC_DEFAULT"
+            ],
+            "MultiRegion": false
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListAliases_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListAliases_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListAliases_1.json
@@ -0,0 +1,59 @@
+{
+    "status_code": 200,
+    "data": {
+        "Aliases": [
+            {
+                "AliasName": "alias/aws/sqs",
+                "AliasArn": "arn:aws:kms:us-east-1:644160558196:alias/aws/sqs",
+                "TargetKeyId": "5a4af27f-a9e6-40fb-a2ed-fc3fbfdb34a5",
+                "CreationDate": {
+                    "__class__": "datetime",
+                    "year": 2021,
+                    "month": 4,
+                    "day": 13,
+                    "hour": 5,
+                    "minute": 47,
+                    "second": 16,
+                    "microsecond": 8000
+                },
+                "LastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2021,
+                    "month": 4,
+                    "day": 13,
+                    "hour": 5,
+                    "minute": 47,
+                    "second": 16,
+                    "microsecond": 8000
+                }
+            },
+            {
+                "AliasName": "alias/kms-cache-check",
+                "AliasArn": "arn:aws:kms:us-east-1:644160558196:alias/kms-cache-check",
+                "TargetKeyId": "c928024b-2802-4bd7-bee1-d879c584a0ef",
+                "CreationDate": {
+                    "__class__": "datetime",
+                    "year": 2023,
+                    "month": 5,
+                    "day": 3,
+                    "hour": 13,
+                    "minute": 17,
+                    "second": 23,
+                    "microsecond": 922000
+                },
+                "LastUpdatedDate": {
+                    "__class__": "datetime",
+                    "year": 2023,
+                    "month": 5,
+                    "day": 3,
+                    "hour": 13,
+                    "minute": 17,
+                    "second": 23,
+                    "microsecond": 922000
+                }
+            }
+        ],
+        "Truncated": false,
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListKeys_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListKeys_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/kms.ListKeys_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "Keys": [
+            {
+                "KeyId": "5a4af27f-a9e6-40fb-a2ed-fc3fbfdb34a5",
+                "KeyArn": "arn:aws:kms:us-east-1:644160558196:key/5a4af27f-a9e6-40fb-a2ed-fc3fbfdb34a5"
+            },
+            {
+                "KeyId": "c928024b-2802-4bd7-bee1-d879c584a0ef",
+                "KeyArn": "arn:aws:kms:us-east-1:644160558196:key/c928024b-2802-4bd7-bee1-d879c584a0ef"
+            }
+        ],
+        "Truncated": false,
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_1.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 200,
+    "data": {
+        "Attributes": {
+            "QueueArn": "arn:aws:sqs:us-east-1:644160558196:test-kms-cache-custom-key",
+            "ApproximateNumberOfMessages": "0",
+            "ApproximateNumberOfMessagesNotVisible": "0",
+            "ApproximateNumberOfMessagesDelayed": "0",
+            "CreatedTimestamp": "1683134373",
+            "LastModifiedTimestamp": "1683134373",
+            "VisibilityTimeout": "30",
+            "MaximumMessageSize": "262144",
+            "MessageRetentionPeriod": "345600",
+            "DelaySeconds": "0",
+            "ReceiveMessageWaitTimeSeconds": "0",
+            "KmsMasterKeyId": "alias/kms-cache-check",
+            "KmsDataKeyReusePeriodSeconds": "300",
+            "SqsManagedSseEnabled": "false"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_2.json b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_2.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 200,
+    "data": {
+        "Attributes": {
+            "QueueArn": "arn:aws:sqs:us-east-1:644160558196:test-kms-cache-default-key",
+            "ApproximateNumberOfMessages": "0",
+            "ApproximateNumberOfMessagesNotVisible": "0",
+            "ApproximateNumberOfMessagesDelayed": "0",
+            "CreatedTimestamp": "1683134354",
+            "LastModifiedTimestamp": "1683134354",
+            "VisibilityTimeout": "30",
+            "MaximumMessageSize": "262144",
+            "MessageRetentionPeriod": "345600",
+            "DelaySeconds": "0",
+            "ReceiveMessageWaitTimeSeconds": "0",
+            "KmsMasterKeyId": "alias/aws/sqs",
+            "KmsDataKeyReusePeriodSeconds": "300",
+            "SqsManagedSseEnabled": "false"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_3.json b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.GetQueueAttributes_3.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200,
+    "data": {
+        "Attributes": {
+            "QueueArn": "arn:aws:sqs:us-east-1:644160558196:test-kms-cache-unencrypted",
+            "ApproximateNumberOfMessages": "0",
+            "ApproximateNumberOfMessagesNotVisible": "0",
+            "ApproximateNumberOfMessagesDelayed": "0",
+            "CreatedTimestamp": "1683134334",
+            "LastModifiedTimestamp": "1683134334",
+            "VisibilityTimeout": "30",
+            "MaximumMessageSize": "262144",
+            "MessageRetentionPeriod": "345600",
+            "DelaySeconds": "0",
+            "ReceiveMessageWaitTimeSeconds": "0",
+            "SqsManagedSseEnabled": "true"
+        },
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.ListQueues_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.ListQueues_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/sqs.ListQueues_1.json
@@ -0,0 +1,11 @@
+{
+    "status_code": 200,
+    "data": {
+        "QueueUrls": [
+            "https://sqs.us-east-1.amazonaws.com/644160558196/test-kms-cache-custom-key",
+            "https://sqs.us-east-1.amazonaws.com/644160558196/test-kms-cache-default-key",
+            "https://sqs.us-east-1.amazonaws.com/644160558196/test-kms-cache-unencrypted"
+        ],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_1.json b/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_1.json
@@ -0,0 +1,8 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_2.json b/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_kms_key_related_cache_lookup/tagging.GetResources_2.json
@@ -0,0 +1,8 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/test_kms.py b/tests/test_kms.py
--- a/tests/test_kms.py
+++ b/tests/test_kms.py
@@ -7,6 +7,7 @@
 from c7n.resources.aws import shape_validate
 from .common import BaseTest, functional
 
+from c7n.config import Config
 from c7n.executor import MainThreadExecutor
 from c7n.filters.iamaccess import CrossAccountAccessFilter
 
@@ -360,6 +361,59 @@ def test_kms_key_related(self):
             for res in resources
         ))
 
+        # Whether a resource specifies a key by ID or alias, it should resolve
+        # to the same ID for related resource lookups.
+        related_ids = p.resource_manager.filters[0].get_related_ids(resources)
+        self.assertEqual(set(related_ids), {target_key['KeyMetadata']['KeyId']})
+
+    def test_kms_key_related_cache_lookup(self):
+        """Validate that the kms-key filter can perform alias lookups
+        against cached keys.
+
+        See https://github.com/cloud-custodian/cloud-custodian/issues/8504
+        """
+        session_factory = self.replay_flight_data("test_kms_key_related_cache_lookup")
+        key_alias = "alias/kms-cache-check"
+        p = self.load_policy(
+            {
+                "name": "load-keys-into-cache",
+                "resource": "aws.kms-key",
+            },
+            cache=True,
+            config=Config.empty(
+                cache='memory',
+                cache_period=10,
+                output_dir=self.get_temp_dir(),
+            ),
+            session_factory=session_factory,
+        )
+        resources = p.run()
+        self.assertGreater(len(resources), 0)
+
+        p = self.load_policy(
+            {
+                "name": "sqs-kms-key-related-from-cache",
+                "resource": "aws.sqs",
+                "filters": [
+                    {
+                        "type": "kms-key",
+                        "key": "c7n:AliasName",
+                        "value": key_alias,
+                        "op": "eq"
+                    }
+                ]
+            },
+            cache=True,
+            config=Config.empty(
+                cache='memory',
+                cache_period=10,
+                output_dir=self.get_temp_dir(),
+            ),
+            session_factory=session_factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
     def test_kms_post_finding(self):
         factory = self.replay_flight_data('test_kms_post_finding')
         p = self.load_policy({