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?
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)