Search code examples
opaopen-policy-agent

OPA doesn't find key if its value is false


I have noticed that if a certain key has false as a value, OPA ignores it. Take this policy code:

package play

violation[{"msg": msg}] {
    provided := {context | input.securityContext[context]}
    required := {context | context := input.requiredContexts[_]}
    missing := required - provided
    count(missing) > 0
    msg := sprintf("Missing: %v. Provided: %v. Required: %v", [missing, provided, required])
}

given the following input:

{
    "requiredContexts": [
        "runAsNonRoot",
        "privileged"
    ],
    "securityContext": {
        "capabilities": {
            "add": [
                "NET_ADMIN",
                "SYS_TIME"
            ]
        },
        "runAsNonRoot": true,
        "privileged": false
    }
}

The output is:

{
    "violation": [
        {
            "msg": "Missing: {\"privileged\"}. Provided: {\"capabilities\", \"runAsNonRoot\"}. Required: {\"privileged\", \"runAsNonRoot\"}"
        }
    ]
}

Notice how privileged is missing even though it is provided in the input.

If privileged is switched to true, the code behaves as expected. I think this is me not knowing OPA well enough. Why does this happen?

https://play.openpolicyagent.org/p/TAcoRAZZcS


Solution

  • That's a good observation! What you're seeing using that type of looping construct, i.e.

    provided := {context | input.securityContext[context]}
    

    Is the result of unification, meaning that OPA "will assign variables to values that make the comparison true". The false value does not make the evaluation true. If you want to stick to the "old" (but certainly still valid) looping construct, you can make the unification explicit using a wildcard on the left hand side, basically saying that you don't care which value it's assigned.

    provided := {context | _ = input.securityContext[context]}
    

    If you think that's hard to grasp, you wouldn't be alone. Modern Rego instead often prefer to make use of the more recent some ... in construct for iteration, which is similar to for k, v in ..., where you also may make use of a wildcard if you don't care for either the key or the value:

    import future.keywords.in
    
    provided := {context | some context, _ in input.securityContext}
    

    If you're only interested in the keys however, I'd recommend using the fairly recent object.keys built-in function:

    provided := object.keys(input.securityContext)