open-policy-agent / gatekeeper

🐊 Gatekeeper - Policy Controller for Kubernetes
https://open-policy-agent.github.io/gatekeeper/
Apache License 2.0
3.72k stars 764 forks source link

rego.v1 syntax #3577

Open sergii-auctane opened 1 month ago

sergii-auctane commented 1 month ago

Hi, sorry if it's a wrong template, i wasn't sure if it's a bug or a feature request. As the OPA which runs inside of the gatekeeper container supports rego.v1 syntax i consider this more like a bug, as the only limiting factor from support rego.v1 is rule-schema at first look.

But feel free to change the label, if you think it's a feature request.

What steps did you take and what happened: I create a ConstraintTemplate which contains import rego.v1 line.

import rego.v1

violation[{"msg": msg}] if {
...
}

and get error in ConstraintTemplate status

    - errors:
      - code: ingest_error
        message: 'Could not ingest Rego: invalid ConstraintTemplate: invalid rego:
          invalid module: missing required rules: [violation]'

What did you expect to happen: I expect it would parse the rule as it supports rego.v1 syntax as if i remove if from violation[{"msg": msg}] if { i get another error, which can exist in v1 only:

    - errors:
      - code: ingest_error
        message: |-
          Could not ingest Rego: invalid ConstraintTemplate: 2 errors occurred:
          template:6: rego_parse_error: `if` keyword is required before rule body
          template:6: rego_parse_error: `contains` keyword is required for partial set rules

Anything else you would like to add: [Miscellaneous information that will assist in solving the issue.]

Environment:

maxsmythe commented 1 month ago

@srenatus

Does using v1 syntax change the rule name somehow? Here is how Gatekeeper analyzes Rego's rules for presence (via Rego's parsing library):

https://github.com/open-policy-agent/frameworks/blob/e84361fed75850acbbc0293f0b47df0f5ad6176c/constraint/pkg/client/drivers/rego/driver.go#L439-L462

srenatus commented 1 month ago

Yeah that code will need updating, I believe, but not just for rego.v1. With our without that import, you can have a rule like

foo[x].bar[y].baz { ... }

and it's not clear what its name would be. You should try using .Ref() instead.

maxsmythe commented 1 month ago

Thanks!

It looks like Ref is a list of Term... is that correct?

https://github.com/open-policy-agent/opa/blob/78a73025233189adf1573b483dcc4742b7673944/ast/term.go#L880

What does that look like for a more complex reference like what you cited above? I.e. how are brackets handled?

Is it possible to have a tree-like structure to these?

e.g.

foo[bar[x]].baz[y] {}?

srenatus commented 1 month ago

Ref is []*Term, yeah, but the possible ref-heads of rules are more limited. I can't find a location right now, but it should be safe to assume that it's only strings and vars. @johanfylling do you know where we enforce that?

johanfylling commented 1 month ago

I don't think we do much more enforcement than requiring it to be a valid ref. Any terms containing other things than scalar values and vars - like refs and calls - will be moved into the body and replaced with vars by the compiler. So a compiled rule head's ref should only contain strings and vars, yes; but one that has only been through the parser, I'd not expect to be so clean.


violation is supposed to be a partial rule? I.e. it's building a set?

violation[{"msg": msg}] if {
...
}

is semantically equivalent to

violation[{"msg": msg}] := true if {
...
}

and will create an object at violation with key {"msg": msg} and value true if the rule validates.

To create a set-building partial rule in v1 you need to use the contains keyword:

violation contains {"msg": msg} if {
...
}

The rule name will only be available for rules where a name can be inferred; which excludes rules with multiple ref terms in the head. Non-ref-head complete rules (violation if {...}) and non-ref-head partial rules (violation contains x if {...}, violation[x] := y {...}) should be assigned a name I think, though.


Tree structures can be constructed through rules with multiple variables in the rule head's ref.

foo[bar[x]].baz[y] if {
...
}

is indeed a valid rule head.