Repository: cloud-custodian/cloud-custodian

Issue:
is there capability to compare two JMESpath attributes?
Am working on implementing CIS AWS Benchmarks, and am working on CIS 1.23 "IAM users with keys created at user creation time."

Is there a way to compare two JMESpath attributes?

Within resource iam-user, type credential - Am seeking to compare   user_creation_time with access_keys.last_rotated.  If equal, it indicates the keys were created at account creation time.

policies:
- name: iam-users-with-keys-created-at-user-creation-time
  description: |
    CIS 1.23 IAM users with keys created at user creation time.
  resource: iam-user
  filters:
    - type: credential
      key: access_keys.active
      value: true
    - type: credential
      key: user_creation_time
      op: eq
      value: access_keys.last_rotated
#    - access_keys.last_rotated = user_creation_time


Discussion:
there isn't but we could add something like that as a value_type: expr or jmespath 
that'd be great - could see several use cases for having the capability.  thanks for the reply.

Resolving patch:
diff --git a/c7n/filters/core.py b/c7n/filters/core.py
--- a/c7n/filters/core.py
+++ b/c7n/filters/core.py
@@ -258,7 +258,7 @@ class ValueFilter(Filter):
             'key': {'type': 'string'},
             'value_type': {'enum': [
                 'age', 'integer', 'expiration', 'normalize', 'size',
-                'cidr', 'cidr_size', 'swap', 'resource_count']},
+                'cidr', 'cidr_size', 'swap', 'resource_count', 'expr']},
             'default': {'type': 'object'},
             'value_from': ValuesFrom.schema,
             'value': {'oneOf': [
@@ -271,6 +271,10 @@ class ValueFilter(Filter):
 
     annotate = True
 
+    def __init__(self, data, manager=None):
+        super(ValueFilter, self).__init__(data, manager)
+        self.expr = {}
+
     def _validate_resource_count(self):
         """ Specific validation for `resource_count` type
 
@@ -354,11 +358,11 @@ def get_resource_value(self, k, i):
                     break
         elif k in i:
             r = i.get(k)
-        elif self.expr:
-            r = self.expr.search(i)
+        elif k not in self.expr:
+            self.expr[k] = jmespath.compile(k)
+            r = self.expr[k].search(i)
         else:
-            self.expr = jmespath.compile(k)
-            r = self.expr.search(i)
+            r = self.expr[k].search(i)
         return r
 
     def match(self, i):
@@ -385,7 +389,7 @@ def match(self, i):
 
         # value type conversion
         if self.vtype is not None:
-            v, r = self.process_value_type(self.v, r)
+            v, r = self.process_value_type(self.v, r, i)
         else:
             v = self.v
 
@@ -409,10 +413,13 @@ def match(self, i):
 
         return False
 
-    def process_value_type(self, sentinel, value):
+    def process_value_type(self, sentinel, value, resource):
         if self.vtype == 'normalize' and isinstance(value, basestring):
             return sentinel, value.strip().lower()
 
+        elif self.vtype == 'expr':
+            return sentinel, self.get_resource_value(value, resource)
+
         elif self.vtype == 'integer':
             try:
                 value = int(value.strip())


Test patch:
diff --git a/tests/test_filters.py b/tests/test_filters.py
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -147,26 +147,39 @@ class TestValueFilter(unittest.TestCase):
     def test_value_type(self):
         sentinel = datetime.now()
         value = 5
+        resource = {'a': 1, 'Tags': [{'Key': 'xtra', 'Value': 'hello'}]}
         vf = filters.factory({'tag:ASV': 'absent'})
         vf.vtype = 'size'
-        res = vf.process_value_type(sentinel, value)
+        res = vf.process_value_type(sentinel, value, resource)
         self.assertEqual(res, (sentinel, 0))
         vf.vtype = 'age'
-        res = vf.process_value_type(sentinel, value)
+        res = vf.process_value_type(sentinel, value, resource)
         self.assertEqual(res, (0, sentinel))
         vf.vtype = 'cidr'
         sentinel = '10.0.0.0/16'
         value = '10.10.10.10'
-        res = vf.process_value_type(sentinel, value)
+        res = vf.process_value_type(sentinel, value, resource)
         self.assertEqual(
             (str(res[0]), str(res[1])),
             (sentinel, value),
         )
         vf.vtype = 'cidr_size'
         value = '10.10.10.300'
-        res = vf.process_value_type(sentinel, value)
+        res = vf.process_value_type(sentinel, value, resource)
         self.assertEqual(res, (sentinel, 0))
 
+        vf.vtype = 'expr'
+        value = 'tag:xtra'
+        sentinel = None
+        res = vf.process_value_type(sentinel, value, resource)
+        self.assertEqual(res, (None, 'hello'))
+
+        vf.vtype = 'expr'
+        value = 'a'
+        sentinel = None
+        res = vf.process_value_type(sentinel, value, resource)
+        self.assertEqual(res, (None, 1))
+
 
 class TestAgeFilter(unittest.TestCase):