Search code examples
open-policy-agentrego

Parameters in Rego rules [Open Policy Agent]


How to use parameters in Rego rules? I would have something like this:

deny[reason] {
  input.request.kind.kind == "Route"
  not valid_route_request[label]
  reason := sprintf("missing or wrong router selector label: %v", [label])
}

valid_route_request[label] {
  requester := input.request.userInfo.username
  some i # iterate on all users
  requester == data.kubernetes.users[i].metadata.name
  label := input.request.object.metadata.labels["router-selector"]
  label == data.kubernetes.users[i].metadata.annotations[router_selector_key]
}

where label is used to build the error message. I get error from OPA: var label is unsafe ...

Generally speaking, it is still not clear to me how to pass parameters in Rego.


Solution

  • Your example is almost correct--the problem you're facing is that label is "unsafe".

    TLDR; assign label inside the deny rule:

    deny[reason] {
        input.request.kind.kind == "Route"
        label := input.request.object.metadata.labels["router-selector"]
        not valid_route_request[label]
        reason := sprintf("wrong 'router-selector' label: %v", [label])
    }
    
    deny[reason] {
        input.request.kind.kind == "Route"
        not input.request.object.metadata.labels["router-selector"]
        reason := "missing 'router-selector' label"
    }
    

    Note, I've created TWO deny rules. One for the case where the path input.request.object.metadata.labels["route-selector'] is undefined and the other for an invalid value.

    Why does OPA generate a safety error in the original example? Safety is a property of Rego that ensures that all variables can be assigned a finite number of values. To be considered "safe", a variable must appear as the output of at-least-one non-negated expression.

    Here are some examples that are all safe:

    # 'a' is assigned to the value referenced by input.foo
    a := input.foo
    
    # 'x' is assigned to the keys of the collection referenced by input.foo
    input.foo[x]
    
    # 'label' is is assigned to the keys of the collection referenced by valid_route_request
    valid_route_request[label]
    
    # 'x' is safe because it is assigned outside the negated expression
    x := 7; not illegal_numbers[x]
    

    Here are examples of unsafe expressions:

    # 'x' is unsafe because it does not appear as an output of a non-negated expression
    not p[x]; not q[x]
    
    
    # 'y' is unsafe because it only appears as a built-in function input
    count(y)
    

    Safety errors can also occur with variables that appear in the head of the rule:

    # 'msg' is unsafe because it is not assigned inside the body of the rule.
    deny[msg] {
      input.request.kind.kind == "BadKind"
    }
    

    Safety is important as it ensures that OPA can enumerate all of the values that could be assigned to the variable. If the variable is unsafe it means there could be an infinite number of variable assignments. In your example, the statement valid_route_request generates a set of values (labels?). The not valid_route_request[label] statement in the deny rule is unsafe because label is not assigned elsewhere in the deny rule (and label does not appear in the global scope presumably.) This actually becomes a bit clearer if you include 'some' in the deny rule:

    deny[reason] {
      some label
      input.request.kind.kind == "Route"
      not valid_route_request[label]
      reason := ...
    }
    

    This rule says (in English):

    reason is in deny if for some label, input.request.kind.kind equals Route and label is not in valid_route_request, and ...
    

    Technically there would be an infinite number of assignments to label that satisfy this rule (e.g., the string "12345" would NOT be contained in valid_route_request and so would "123456" and so would ...)