Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

topdown+rego+server: allow opt-in for evaluating non-det builtins in PE #7313

Merged
merged 3 commits into from
Jan 27, 2025

Conversation

srenatus
Copy link
Contributor

@srenatus srenatus commented Jan 27, 2025

Some use cases of PE, notably generating queries that are to be translated into filters of some sort (think SQL), require the evaluation of non-deterministic builtins. This is because the result of the builtin informs what queries are returned.

Imagine that the user associated with a request is known at PE-time, but we need extra information from an HTTP API to determine the filters that should be applied.

Previously, that was just impossible to do. Now, we can opt-in to evaluate non-det builtins during PE from the Rego API.

Note that it would probably make sense to include this in the inlining controls, as sent to the Compile API. (Considered out of scope for this PR.)

Also note that this will take highest precedence over the ast.IgnoreDuringPartialEval map and the "Nondeterministic" value of the registered builtin. If the new option is provided, both of these are ignored.


It's nicest to compare with and without in opa eval's PE CLI:

$ echo '{"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}'| ./opa_darwin_amd64 eval -fpretty -p -I -d foo.rego -u input.fruits data.ex.include
+---------+-------------------------------------------------------------------------------------+
| Query 1 | http.send({"method": "GET", "url": "https://httpbin.org/json"}, __local0__1)        |
|         | input.fruits.name = object.get(__local0__1.body, ["slideshow", "title"], "unknown") |
+---------+-------------------------------------------------------------------------------------+
$ echo '{"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}'| ./opa_darwin_amd64 eval -fpretty -p -I -d foo.rego -u input.fruits data.ex.include --nondeterminstic-builtins
+---------+-----------------------------------------+
| Query 1 | input.fruits.name = "Sample Slide Show" |
+---------+-----------------------------------------+

Fixes #6496. More or less. Previously, all non-det builtin calls were saved; with this, we allow the user to declare that none of them should be saved (unless their args are known). For builtins like time.now_ns(), which has no args, this doesn't really make a difference. Let's see where this takes us, if there's a future need for a more fine-grained control, we can invest more time.

Copy link

netlify bot commented Jan 27, 2025

Deploy Preview for openpolicyagent ready!

Name Link
🔨 Latest commit 381fd68
🔍 Latest deploy log https://app.netlify.com/sites/openpolicyagent/deploys/67975951b4061300084c29e2
😎 Deploy Preview https://deploy-preview-7313--openpolicyagent.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@srenatus srenatus changed the title topdown+rego: allow opt-in for evaluating non-det builtins in PE topdown+rego+server: allow opt-in for evaluating non-det builtins in PE Jan 27, 2025
@srenatus srenatus marked this pull request as ready for review January 27, 2025 10:09
Comment on lines 368 to 374
found = len(node.With) > 0
if !ic.nondeterministicBuiltins { // skip evaluating non-det builtins for PE
found = ignoreExprDuringPartial(node)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️ This is what it's about, really. The test is pushing the configurable through all the layers of HTTP, CLI and the Rego package.

Some use cases of PE, notably generating queries that are to be translated
into filters of some sort (think SQL), require the evaluation of non-deterministic
builtins. This is because the result of the builtin informs what queries are
returned.

Imagine that the user associated with a request is known at PE-time, but we need
extra information from an HTTP API to determine the filters that should be applied.

Previously, that was just impossible to do. Now, we can opt-in to evaluate non-det
builtins during PE from the Rego API.

Note that it would probably make sense to include this in the inlining controls, as
sent to the Compile API. (Considered out of scope for this PR.)

Also note that this will take highest precedence over the `ast.IgnoreDuringPartialEval`
map and the "Nondeterministic" value of the registered builtin. If the new option is
provided, both of these are ignored.

Signed-off-by: Stephan Renatus <[email protected]>
With `foo.rego` as

```rego
package ex

include if input.fruits.name == object.get(http.send(input.req).body, input.path, "unknown")
```

the following queries show the difference:

```interactive
$ curl -v http://127.0.0.1:8181/v1/compile \
  -d '{"input": {"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}, "query": "data.ex.include", "unknowns": ["input.fruits"]}'
{
  "result": {
    "queries": [
      [
        {
          "index": 0,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "http"
                },
                {
                  "type": "string",
                  "value": "send"
                }
              ]
            },
            {
              "type": "object",
              "value": [
                [
                  {
                    "type": "string",
                    "value": "method"
                  },
                  {
                    "type": "string",
                    "value": "GET"
                  }
                ],
                [
                  {
                    "type": "string",
                    "value": "url"
                  },
                  {
                    "type": "string",
                    "value": "https://httpbin.org/json"
                  }
                ]
              ]
            },
            {
              "type": "var",
              "value": "__local0__1"
            }
          ]
        },
        {
          "index": 1,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "eq"
                }
              ]
            },
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "input"
                },
                {
                  "type": "string",
                  "value": "fruits"
                },
                {
                  "type": "string",
                  "value": "name"
                }
              ]
            },
            {
              "type": "call",
              "value": [
                {
                  "type": "ref",
                  "value": [
                    {
                      "type": "var",
                      "value": "object"
                    },
                    {
                      "type": "string",
                      "value": "get"
                    }
                  ]
                },
                {
                  "type": "ref",
                  "value": [
                    {
                      "type": "var",
                      "value": "__local0__1"
                    },
                    {
                      "type": "string",
                      "value": "body"
                    }
                  ]
                },
                {
                  "type": "array",
                  "value": [
                    {
                      "type": "string",
                      "value": "slideshow"
                    },
                    {
                      "type": "string",
                      "value": "title"
                    }
                  ]
                },
                {
                  "type": "string",
                  "value": "unknown"
                }
              ]
            }
          ]
        }
      ]
    ]
  }
}
```
Here, the builtin call to http.send is preserved.

If we also pass `nondeterminsticBuiltins: true` to the options, we get this:
```interactive
$ curl http://127.0.0.1:8181/v1/compile \
  -d '{"input": {"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}, "query": "data.ex.include", "unknowns": ["input.fruits"], "options": {"nondeterministicBuiltins": true}}'
{
  "result": {
    "queries": [
      [
        {
          "index": 0,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "eq"
                }
              ]
            },
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "input"
                },
                {
                  "type": "string",
                  "value": "fruits"
                },
                {
                  "type": "string",
                  "value": "name"
                }
              ]
            },
            {
              "type": "string",
              "value": "Sample Slide Show"
            }
          ]
        }
      ]
    ]
  }
}
```
Here, all args to http.send have been known at PE time and the call was fully
evaluated.

Signed-off-by: Stephan Renatus <[email protected]>
```interactive
$ echo '{"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}'| ./opa_darwin_amd64 eval -fpretty -p -I -d foo.rego -u input.fruits data.ex.include
+---------+-------------------------------------------------------------------------------------+
| Query 1 | http.send({"method": "GET", "url": "https://httpbin.org/json"}, __local0__1)        |
|         | input.fruits.name = object.get(__local0__1.body, ["slideshow", "title"], "unknown") |
+---------+-------------------------------------------------------------------------------------+
$ echo '{"req": {"url": "https://httpbin.org/json", "method":"GET"}, "path": ["slideshow", "title"]}'| ./opa_darwin_amd64 eval -fpretty -p -I -d foo.rego -u input.fruits data.ex.include --nondeterminstic-builtins
+---------+-----------------------------------------+
| Query 1 | input.fruits.name = "Sample Slide Show" |
+---------+-----------------------------------------+
```

Signed-off-by: Stephan Renatus <[email protected]>
Copy link
Contributor

@johanfylling johanfylling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -328,6 +329,7 @@ access.
evalCommand.Flags().BoolVarP(&params.coverage, "coverage", "", false, "report coverage")
evalCommand.Flags().StringArrayVarP(&params.disableInlining, "disable-inlining", "", []string{}, "set paths of documents to exclude from inlining")
evalCommand.Flags().BoolVarP(&params.shallowInlining, "shallow-inlining", "", false, "disable inlining of rules that depend on unknowns")
evalCommand.Flags().BoolVarP(&params.nondeterministicBuiltions, "nondeterminstic-builtins", "", false, "evaluate nondeterministic builtins (if all arguments are known) during partial eval")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there's a future need for a more fine-grained control, we can invest more time

Agreed, no need to over-engineer this from the get-go 👍 .
If we want to give the user precise control of what built-ins to evaluate, we can add something like a --nondeterminstic-builtin (singular) that takes the name and can be repeated.

@srenatus srenatus merged commit 4b8a138 into open-policy-agent:main Jan 27, 2025
28 checks passed
@srenatus srenatus deleted the sr/pe-with-non-det-builtins branch January 27, 2025 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PE: allow adjusting which non-deterministic builtins are to be evaluated
2 participants