Search code examples
terraformopen-policy-agent

Can I loop over keys and values of an object in OPA to validate if they adhere to a certain format (CamelCase)


We are using conftest to validate if our terraform changeset applies to certain rules & compliances. One thing we want to validate is wether our AWS resources are tagged according to the AWS tagging convention, which specifies certain tags to use (e.g. Owner, ApplicationRole, Project) and specifies that all tags and values are in CamelCase.

in terraform the changeset is portrayed in the following (simplified) json output:

{
   "resource_changes":{
      "provider_name":"aws",
      "change":{
         "before":{

         },
         "after":{
            "tags":{
               "ApplicationRole":"SomeValue",
               "Owner":"SomeValue",
               "Project":"SomeValue"
            }
         }
      }
   }
}

What I am now trying to do is to validate the following:

  1. Check wether tags are set.
  2. Validate if the keys and values are all camelcase.
  3. Check that the keys include the set (ApplicationRole, Owner, Project) in the minimum.

However, I am having trouble defining that in Rego (I am quite new to OPA).

Is there a way to "loop" over the keys and values of an object, and validate if they are formatted correctly?

in pseudo code:

for key, value in tags {
  re_match(`([A-Z][a-z0-9]+)+`, key)
  re_match(`([A-Z][a-z0-9]+)+`, value)
}

I have tried the following:

tags_camel_case(tags) {
    some key
    val := tags[key]
    re_match(`^([A-Z][a-z0-9]+)+`, key) # why is key not evaluated?
    re_match(`^([A-Z][a-z0-9]+)+`, val)
}

However, when evaluating against the following test json:

{
  "AppRole": "SomeValue",
  "appRole": "SomeValue"
}

the rule returns true, even though I am checking both key and value vs the regex


Solution

  • The tags_camel_case(tags) function returns true for the input with two keys because (by default) variables in Rego are existentially quantified. This means rule bodies are satisfied if for some set of variable bindings, the statements in the rule body are true. In the example above, the rule body would be satisfied by {key=AppRole, val=SomeValue}.

    To express for all you can use a simple trick. First write a rule to check if any of the tags ARE NOT camel case. Second write the rule to check if the first rule is not satisfied.

    For example:

    # checks if all tags are camel case
    tags_camel_case(tags) {
      not any_tags_not_camel_case(tags)
    }
    
    # checks if any tags are NOT camel case
    any_tags_not_camel_case(tags) {
        some key
        val := tags[key]
        not is_camel_case(key, val)
    }
    
    # checks if a and b are both camel case
    is_camel_case(a, b) {
      re_match(`^([A-Z][a-z0-9]+)+`, a)
      re_match(`^([A-Z][a-z0-9]+)+`, b)
    }
    

    For more info on expression 'for all' in Rego see https://www.openpolicyagent.org/docs/latest/how-do-i-write-policies/#universal-quantification-for-all