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

Compile API and negation #2045

Closed
ovidius72 opened this issue Jan 29, 2020 · 27 comments
Closed

Compile API and negation #2045

ovidius72 opened this issue Jan 29, 2020 · 27 comments
Assignees

Comments

@ovidius72
Copy link

Can the negation be used with the compile APIs for checking the value of an unknown variable ?

allowed[keypad] { #Company C allows only members from C, D and E
  re_match("C", input.subject.group)
  keypad := data.keypads[_]
  g := {"C", "D", "E"}
  keypad.group == g
  not keypad.name == "example"
}

returns the following result:

{
    "result": {
        "queries": [
            [
                {
                    "index": 0,
                    "terms": [
                        {
                            "type": "ref",
                            "value": [
                                {
                                    "type": "var",
                                    "value": "eq"
                                }
                            ]
                        },
                        {
                            "type": "ref",
                            "value": [
                                {
                                    "type": "var",
                                    "value": "data"
                                },
                                {
                                    "type": "string",
                                    "value": "keypads"
                                },
                                {
                                    "type": "var",
                                    "value": "$24"
                                },
                                {
                                    "type": "string",
                                    "value": "group"
                                }
                            ]
                        },
                        {
                            "type": "set",
                            "value": [
                                {
                                    "type": "string",
                                    "value": "C"
                                },
                                {
                                    "type": "string",
                                    "value": "D"
                                },
                                {
                                    "type": "string",
                                    "value": "E"
                                }
                            ]
                        }
                    ]
                },
                {
                    "index": 1,
                    "negated": true,
                    "terms": [
                        {
                            "type": "ref",
                            "value": [
                                {
                                    "type": "var",
                                    "value": "eq"
                                }
                            ]
                        },
                        {
                            "type": "ref",
                            "value": [
                                {
                                    "type": "var",
                                    "value": "$_term_1_21"
                                },
                                {
                                    "type": "string",
                                    "value": "name"
                                }
                            ]
                        },
                        {
                            "type": "string",
                            "value": "example"
                        }
                    ]
                }
            ]
        ]
    }
}

The name of the unknown value is "$_term_1_21" in the second query with the negation. In the first query the unknown value has been replaced with the value from the payload.

As for the documentation For safety, a variable appearing in a negated expression must also appear in another non-negated equality expression in the rule.. Is this the reason it is not correctly evaluated ?

@srenatus
Copy link
Contributor

Just as a side-note -- have you tried a simple not-equal there?

keypad.name != "example"

@ovidius72
Copy link
Author

@srenatus Yes and it works fine. The fact is those are just dummy tests.
I'm trying to translate the query results in a database query and I was just trying to translate the not operand.

@srenatus
Copy link
Contributor

srenatus commented Jan 29, 2020 via email

@ovidius72
Copy link
Author

@srenatus Hey no noise at all :-) . Thank you for your interest.

@srenatus
Copy link
Contributor

Sorry, another side-note:

g := {"C", "D", "E"}
keypad.group == g

Did you by any chance mean g[_] here? As in, the keypad group is one of C, D, and E?

Probably also irrelevant for the question, so please bear with me once more 😄

@srenatus
Copy link
Contributor

Playing around here a little on the REPL, with your code (using g[_]) as keypad.rego, data.json as

[
    {
      "group": "D",
      "name": "not example"
    }
  ]

and running opa run keypad.rego keypads:data.json, I find:

$ opa run keypad.rego keypads:data.json
OPA 0.17.0-dev (commit 54861cdd-dirty, built at 2019-12-24T18:39:50Z)

Run 'help' to see a list of commands.

> unknown input
> data.keypad.allowed
+---------+---------------------+
| Query 1 | data.keypad.allowed |
+---------+---------------------+
> data.keypad.allowed[x]
+---------+-------------------------------------------+
| Query 1 | re_match("C", input.subject.group)        |
|         | x = {"group": "D", "name": "not example"} |
+---------+-------------------------------------------+
>

note that unknown input triggers partial evaluation on the REPL, and the outcome here of course depends on data.json.

However, I'm not sure if this helps you any further. I tend to appreciate the REPL when it comes to partial eval, if only for pretty-printed queries.

I also don't understand (yet) why the Compile API would give you something different. 🤔

@srenatus
Copy link
Contributor

Hmm interesting, running the same opa command with -s, and using the Compile API, I find

$ curl -vk http://127.0.0.1:8181/v1/compile -d '{"query": "data.keypad.allowed[x]"}' | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8181 (#0)
> POST /v1/compile HTTP/1.1
> Host: 127.0.0.1:8181
> User-Agent: curl/7.62.0
> Accept: */*
> Content-Length: 35
> Content-Type: application/x-www-form-urlencoded
>
} [35 bytes data]
* upload completely sent off: 35 out of 35 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Fri, 31 Jan 2020 15:43:01 GMT
< Content-Length: 534
<
{ [534 bytes data]
100   569  100   534  100    35   260k  17500 --:--:-- --:--:-- --:--:--  277k
* Connection #0 to host 127.0.0.1 left intact
{
  "result": {
    "queries": [
      [
        {
          "index": 0,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "re_match"
                }
              ]
            },
            {
              "type": "string",
              "value": "C"
            },
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "input"
                },
                {
                  "type": "string",
                  "value": "subject"
                },
                {
                  "type": "string",
                  "value": "group"
                }
              ]
            }
          ]
        },
        {
          "index": 1,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "eq"
                }
              ]
            },
            {
              "type": "var",
              "value": "x"
            },
            {
              "type": "object",
              "value": [
                [
                  {
                    "type": "string",
                    "value": "group"
                  },
                  {
                    "type": "string",
                    "value": "D"
                  }
                ],
                [
                  {
                    "type": "string",
                    "value": "name"
                  },
                  {
                    "type": "string",
                    "value": "not example"
                  }
                ]
              ]
            }
          ]
        }
      ]
    ]
  }
}
$

...which closely matches the REPL output I've posted above. Not sure what's going on here. We might be using different versions? 💭

@srenatus
Copy link
Contributor

Ok posted too fast once more. You're probably setting data.keypads to unknown in order to meet your goals? I'll try to give that a shot later.

@ovidius72
Copy link
Author

ovidius72 commented Jan 31, 2020

@srenatus thanks for following up.
In this case I wanted the result to be an array like object so that it can be translated in an IN database query. Using keypad.group = ["A", "B", "C"] i think leads to the same results.

Regarding the negation those are the data I'm using for my tests:

policy.rego

package example.keypads.defaults

default allow = false

allow {
  input.subject.role = "admin"
} 
# else {  # return always true ?? why ??
#   input.action.type == "READ"
#   input.path == ["keypads"]
#   allowedB[key]
# }
else {
  input.action.type == "READ"
  input.path == ["keypads"]
  allowed[keypad] 
}

# allowedB[key] {
#   re_match("[B]", input.subject.group)
#   key := data.keypads[_]
#   key.group = "B"
# }

allowed[keypad] { 
  # Company A
  re_match("[A-B]", input.subject.group)
  keypad = data.keypads[_]
  keypad.group = "A"
  keypad.owner = input.subject.user
}

allowed[keypad] { 
  #Company C allows only members from C, D and E
  g := {"C", "D", "E"}
  arr:= ["C", "D", "E"]
  input.subject.group == g[i] 
  keypad := data.keypads[_]
  keypad.group = "C"
  # re_match("C", input.subject.group)
  # keypad := data.keypads[_]
  # g := {"C", "D", "E"}
  # keypad.group = g
  keypad.group = arr
  not keypad.name = "example"
}

allowed[keypad] {
  #Company H allows members from G,D,H
  g := {"G", "D", "H"}
  input.subject.group == g[i] 
  keypad := data.keypads[_]
  keypad.group = "H"
}

allowed[keypad] {
  #Company M allows members from M,N, H
  g := {"M", "N", "H"}
  input.subject.group == g[i] 
  keypad := data.keypads[_]
  keypad.group = "M"
}

allowed[keypad] {
  #Company N allows all members
  re_match(".*", input.subject.group)
  keypad := data.keypads[_]
  keypad.group = "N"
}

input:

{
  "query": "data.example.keypads.defaults.allow = true",
  "unknowns": ["data.keypads"],
  "input": {
    "subject": {
      "user": "Jack",
      "group": "C",
      "role": "not_admin",
      "isMale": false,
      "location": "ROME"
    },
    "env": {
      "ip": 123,
      "strings": ["str1", "str2"],
      "domain": "D"
    },
    "action": { "type": "READ" },
    "method": "GET",
    "path": ["keypads"]
  }
}

I'm running opa run -s policy.rego -w from the command line and using the rest api http://localhost:8181/v1/compile
Try to change the subject.group input value to see the different results.
Thanks

@ovidius72
Copy link
Author

Opa version is 0.16.2

@ovidius72
Copy link
Author

ovidius72 commented Jan 31, 2020

@srenatus However I guess the way you've added the not operand is not correct. that way it matches the string value 'not example' in the keypad.name field. It's not a negation.

@srenatus
Copy link
Contributor

Thanks for sharing more details 👍 With that, I was able to reproduce what you had initially posted.

Running the same stuff using opa eval, looking at the pretty formatted output, we find:

$ opa eval -p -d policy.rego -u data.keypads -i input.json 'data.example.keypads.defaults.allow = true' --format=pretty
+---------+-----------------------------------------+
| Query 1 | data.keypads[_].group = "C"             |
|         | data.keypads[_].group = ["C", "D", "E"] |
|         | not _.name = "example"                  |
+---------+-----------------------------------------+
| Query 2 | data.keypads[_].group = "N"             |
+---------+-----------------------------------------+

(input.json is the input section of your request data)

So, what does this suggest for how to proceed? I'm not certain, it's been a while since I've looked at the data filtering examples. I'd start looking for insights here, https://github.com/open-policy-agent/contrib/blob/master/data_filter_example/data_filter_example/opa.py#L39, if you haven't already.

@srenatus
Copy link
Contributor

Gave it a quick go, but I just ran out of time. With your policy.rego loaded, the relevant python call throws an exception attempting to build the query set:

>>> inp
{u'action': {u'type': u'READ'}, u'path': [u'keypads'], u'method': u'GET', u'subject': {u'role': u'not_admin', u'location': u'ROME', u'group': u'C', u'user': u'Jack', u'isMale': False}}
>>> result = opa.compile(q='data.example.keypads.defaults.allow = true', input=inp, unknowns=['keypads'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "data_filter_example/opa.py", line 161, in compile
    queryPreprocessor().process(query_set)
  File "data_filter_example/opa.py", line 302, in process
    walk.walk(query_set, self)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 12, in walk
    walk(q, next)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 15, in walk
    walk(e, next)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 6, in walk
    next = vis(node)
  File "data_filter_example/opa.py", line 312, in __call__
    walk.walk(o, self)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 24, in walk
    walk(node.value, next)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 6, in walk
    next = vis(node)
  File "data_filter_example/opa.py", line 328, in __call__
    row_id = node.terms[2].value
IndexError: list index out of range

@ovidius72
Copy link
Author

ovidius72 commented Jan 31, 2020

Thank you so much @srenatus.
It seems that when there is a keyword before the variable (in our case the not keyword before keypad.name) the resultant query is probably wrong because it is replaced with the _ placeholder in place of the unknown data.
I guess this is the reason the python code fails while walking the node containing that term value ($_term_1_21), but to be honest I'm not entirely sure about that.
I've already looked at that code previously but the problem is in the returned query in my opinion.

❯ opa eval -p -d policyCustom.rego -u data.keypads -i input.json 'data.example.keypads.defaults.allow = true' --format=pretty
+---------+-----------------------------------------+
| Query 1 | data.keypads[_].group = "C"             |
|         | data.keypads[_].group = ["C", "D", "E"] |
|         | not $_term_1_21.name = "example"        |
+---------+-----------------------------------------+
| Query 2 | data.keypads[_].group = "N"             |
+---------+-----------------------------------------+

I don't know if we have a different opa version but i'm getting $_term_1_21 and not the underscore as in you example.

@srenatus
Copy link
Contributor

srenatus commented Feb 1, 2020

Adding to this, I've tried using != instead of not, out of curiosity:

The rendered query then becomes

+---------+-----------------------------------------+
| Query 1 | data.keypads[_].group = "C"             |
|         | data.keypads[_].group = ["C", "D", "E"] |
|         | data.keypads[_].name != "example"       |
+---------+-----------------------------------------+
| Query 2 | data.keypads[_].group = "N"             |
+---------+-----------------------------------------+

and the python translation fails like this:

>>> result = opa.compile(q='data.example.keypads.defaults.allow = true', input=inp, unknowns=['keypads'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "data_filter_example/opa.py", line 162, in compile
    clauses = queryTranslator(from_table).translate(query_set)
  File "data_filter_example/opa.py", line 213, in translate
    walk.walk(query_set, self)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 12, in walk
    walk(q, next)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 6, in walk
    next = vis(node)
  File "data_filter_example/opa.py", line 224, in __call__
    self._translate_query(node)
  File "data_filter_example/opa.py", line 236, in _translate_query
    walk.walk(expr, self)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 6, in walk
    next = vis(node)
  File "data_filter_example/opa.py", line 226, in __call__
    self._translate_expr(node)
  File "data_filter_example/opa.py", line 259, in _translate_expr
    walk.walk(term, self)
  File "/Users/stephan/.pyenv/versions/2.7.15/lib/python2.7/site-packages/rego/walk.py", line 6, in walk
    next = vis(node)
  File "data_filter_example/opa.py", line 228, in __call__
    self._translate_term(node)
  File "data_filter_example/opa.py", line 285, in _translate_term
    raise TranslationError('invalid term: type not supported: %s' % v.__class__.__name__)
data_filter_example.opa.TranslationError: invalid term: type not supported: Array
>>>

which I'd take to mirror my confusion on how to translate that query 1 into SQL. 💭

@ovidius72
Copy link
Author

Yes. That's a problem of the python translator program but the query returned by opa is correct. I guess it's because the array type is not handled in the python code. I'm using code written by myself in typescript and I've added the case of sets and arrays(as well as concat, startsWith, endsWith and others.. all working fine).
The thing i'm missing now is the not operator because the returned opa query is not valid.

@ovidius72
Copy link
Author

Unfortunately I don't know Go language to understand the code deeply but I was looking at this line here

opa/ast/parser.go

Line 3466 in 12ff9c0

func (c *current) onNotKeyword1(val interface{}) (interface{}, error) {

Is it correct the way the ast is parsed for the not keyword ?

@srenatus
Copy link
Contributor

srenatus commented Feb 1, 2020

This looks relevant but it was fixed some time ago: #1814

@srenatus
Copy link
Contributor

srenatus commented Feb 1, 2020

ℹ️ FWIW, the compile API returns $_term_1_21 for the version of OPA I'm using, too, it's just that opa eval seems to translate it to _ (as noted above).

Also, this still does seem like a bug: what is not _.name = "example" supposed to mean?

@ovidius72
Copy link
Author

ovidius72 commented Feb 1, 2020

It should be not keypad.name == "example".
opa eval is what i used (as in the example above) and I don't see the underscore.
What do you mean exactly when you say what is supposed to mean ? Are you talking about what it should be when translated in a DB query ? Otherwise, as far as I understand, opa should replace the $_term_1_21 value with the unknown value.

@ovidius72
Copy link
Author

@srenatus basically it should be the same as explained here https://www.openpolicyagent.org/docs/latest/policy-language/#negation

@srenatus
Copy link
Contributor

srenatus commented Feb 1, 2020

What do you mean exactly when you say what is supposed to mean ?

Yup, I'm not sure about the semantics of _.anything. 🤔 I can't make
sense of it rego-wise.

Also DB-wise, I'm curious (genuinely!) how you're translating query 1. Assuming the not was not keypad.name = "example", so,

data.keypads[_].group = "C"
data.keypads[_].group = ["C", "D", "E"]
not keypad.name = "example"

Since in rego, these are implicitly AND'ed together, this would roughly mean "allow is true if"

  1. there's a keypad with group = "C"
  2. there's a (presumably different) keypad with group = ["C", "D", "E"] (an array, so, a different column type than (1.))
  3. some keypad (which one?) has a name that is not "example"

Ah, the IMHO perhaps contradictory requirements in query 1 come from those lines (cut out):

allowed[keypad] { 
  #...
  arr:= ["C", "D", "E"]
  #...
  keypad.group = "C"
  #...
  keypad.group = arr
  #...
}

(Please note that I'm not trying to be annoying on purpose, but I'm kinda interested in this case now 😅 Even more so since you've taken your own route with a TS implementation.)

@ovidius72
Copy link
Author

ovidius72 commented Feb 1, 2020

The idea is to generate a custom tree from the opa results that allows one to build queries for different database engines.
Since the opa result is an AST and not just a true/false values. (like in the example you referred to
written in python).

For now I'm getting something like this from the code I wrote:

DBTree {
    "value": "OR",
    "descendants": [
        {
            "value": "AND",
            "descendants": [
                {
                    "value": [
                        "C",
                        "D",
                        "E"
                    ],
                    "descendants": [],
                    "negate": false,
                    "operator": "IN",
                    "type": "array",
                    "field": "group"
                },
                {
                    "value": "example",
                    "descendants": [],
                    "negate": true,
                    "operator": "=",
                    "type": "string",
                    "field": "$_term_1_21.name"
                }
            ],
            "negate": false,
            "type": "junction"
        },
        {
            "value": "AND",
            "descendants": [
                {
                    "value": "N",
                    "descendants": [],
                    "negate": false,
                    "operator": "=",
                    "type": "string",
                    "field": "group"
                }
            ],
            "negate": false,
            "type": "junction"
        }
    ],
    "negate": false,
    "type": "junction"
}

this would allow (hopefully) each database adapters to build their own specific query.

As a result, with a basic sqlAdapter i'm working on now, this allows me to generate this:

***clause:  "( group IN @1 AND NOT($_term_1_21.name = @2) ) OR ( group = @3 )"
***values:  [
    [
        "C",
        "D",
        "E"
    ],
    "example",
    "N"
]

I've found a couple of example with node JS over the Internet where I've got the idea, so I decided to go on my own trying to make it database agnostic. But there is that not operator causing troubles 😄

In a couple of projects I'm working on it is required to give permission to access some resources not only based on the user role and permissions (known as RBAC) but based on attributes (ABAC), something like users can access if their IP is x or is location is from y.
I find opa to be the best thing to do that, but I need a way use it with database queries.

@tsandall
Copy link
Member

@ovidius72 sorry for the delayed response. To answer your original question, yes you can partially evaluate policies that use negation. If a negated expression contains an unknown value, it will saved. If the negated expression depends on unknowns, transitively, (e.g., support you wrote not p[x] and the definition of p depended on some unknown value), OPA will try to inline the result. If the result is too big to inline, support rules are generated.

Can you share the parameters you used to generate the partial eval result in the original issue description? When I run partial eval with the input from the comment above the output is better:

Output

$ opa eval -d simple.rego -p -u data.keypads  -i input.json.1 'data.foo.allowed[x]'  -f source
# Query 1
x = data.keypads[_]
x.group = {"C", "D", "E"}
not x.name = "example"
x

Policy

package foo

allowed[keypad] { #Company C allows only members from C, D and E
  re_match("C", input.subject.group)
  keypad := data.keypads[_]
  g := {"C", "D", "E"}
  keypad.group == g
  not keypad.name == "example"
}

Input

{
  "action": {
    "type": "READ"
  },
  "env": {
    "domain": "D",
    "ip": 123,
    "strings": [
      "str1",
      "str2"
    ]
  },
  "method": "GET",
  "path": [
    "keypads"
  ],
  "subject": {
    "group": "C",
    "isMale": false,
    "location": "ROME",
    "role": "not_admin",
    "user": "Jack"
  }
}

@ovidius72
Copy link
Author

ovidius72 commented Feb 24, 2020

Thank you @tsandall

You are asking to run for the allowed incremental definition in the rule while I'm asking for allow = true.
What you get if you run
opa eval -d simple.rego -p -u data.keypads -i input.json.1 'data.foo.allow = true' -f source ?
Do you see an underscore _ or a string $_term_1_21 in place of data.keypads[_]?
I see an underscore today, but as for my first examples above I got the string $_term_1_21.
I'm now running v0.17.2

Here are my results:

Using allow = true

➜ opa eval -d ./policyCustom.rego -p -u data.keypads -i ./src/input.json 'data.example.keypads.defaults.allow = true' -f source
# Query 1
data.keypads[_].group = {"C", "D", "E"}
not _.name = "example"

# Query 2
data.keypads[_].group = "N"

OR

Using allowed[x]

➜ opa eval -d policyCustom.rego -p -u data.keypads -i ./src/input.json 'data.example.keypads.defaults.allowed[x]' -f source    
# Query 1
x = data.keypads[_]
x.group = {"C", "D", "E"}
not x.name = "example"
x

# Query 2
x = data.keypads[_]
x.group = "N"
not x.name = "fromN"
x

@tsandall
Copy link
Member

tsandall commented Mar 6, 2020

I'm seeing the same behaviour. Adding a simple allow rule reproduces the problems:

$ opa eval -d simple.rego -p -u data.keypads  -i input.json.1 'data.foo.allow=true'  -f source# Query 1
data.keypads[_].group = {"C", "D", "E"}
not _.name = "example"

There are two problems with this output. The first issue is that not _.name = "example" is considered unsafe since _ is a unique variable and all variables that appear inside negated expressions MUST appear as an output another non-negated expression in the same query. Since each occurrence of _ is unique, it will always be unsafe. @srenatus has filed #2053 tracking this. This is primarily a presentation issue.

However, the second issue is that the _ variable in not _.name = "example" is never assigned in the output. You can see this by looking at the raw AST output / JSON. By convention, _ variables are represented internally as "$...". In this case the variable is $_term_1_21.

{
  "partial": {
    "queries": [
      [
        {
          "index": 0,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "eq"
                }
              ]
            },
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "data"
                },
                {
                  "type": "string",
                  "value": "keypads"
                },
                {
                  "type": "var",
                  "value": "$02"
                },
                {
                  "type": "string",
                  "value": "group"
                }
              ]
            },
            {
              "type": "set",
              "value": [
                {
                  "type": "string",
                  "value": "C"
                },
                {
                  "type": "string",
                  "value": "D"
                },
                {
                  "type": "string",
                  "value": "E"
                }
              ]
            }
          ]
        },
        {
          "index": 1,
          "negated": true,
          "terms": [
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "eq"
                }
              ]
            },
            {
              "type": "ref",
              "value": [
                {
                  "type": "var",
                  "value": "$_term_1_21"
                },
                {
                  "type": "string",
                  "value": "name"
                }
              ]
            },
            {
              "type": "string",
              "value": "example"
            }
          ]
        }
      ]
    ]
  }
}

simple.rego:

package foo

allow {
  input.action.type == "READ"
  input.path == ["keypads"]
  allowed[keypad]
}

allowed[keypad] { #Company C allows only members from C, D and E
  re_match("C", input.subject.group)
  keypad := data.keypads[_]
  g := {"C", "D", "E"}
  keypad.group == g
  not keypad.name == "example"
}

input.json.1:

{
  "action": {
    "type": "READ"
  },
  "env": {
    "domain": "D",
    "ip": 123,
    "strings": [
      "str1",
      "str2"
    ]
  },
  "method": "GET",
  "path": [
    "keypads"
  ],
  "subject": {
    "group": "C",
    "isMale": false,
    "location": "ROME",
    "role": "not_admin",
    "user": "Jack"
  }
}

@tsandall tsandall added the bug label Mar 6, 2020
@patrick-east patrick-east self-assigned this Apr 7, 2020
patrick-east added a commit to patrick-east/opa that referenced this issue Apr 11, 2020
In copy propogation we ran into issues where an expression was negated
and required a variable in it to show up in a non-negated expression
to be safe. We had the binding for the var and could have created it
but there were two issues:

1) We didn't resolve the variable binding for refs in negated
   expressions. This left the "wrong" thing in the result.

2) Once we had the unified representation for the value in the result
   we needed to ensure that not only was the binding contained in the
   result somewhere, but it needed to appear in a non-negated
   expression.

The copy propagation code will then add an equality expression for
bindings that are left over and that are not safely contained in the
result. However, we don't always need the full equality expression.

If the key isn't found anywhere in the result we don't need the lhs of
the equality. We are essentially adding a variable that will be
ignored when all we care is that the rhs is defined. So now we will
only generate an expression for the rhs in those cases.

Fixes: open-policy-agent#2045
Signed-off-by: Patrick East <east.patrick@gmail.com>
patrick-east added a commit to patrick-east/opa that referenced this issue Apr 21, 2020
In copy propagation we ran into issues where an expression was negated
and required a variable in it to show up in a non-negated expression
to be safe.

What we do now is expose some of the safety helpers from the ast
package and check if any of the removed expressions provided safety
to any vars left in the result (that are otherwise unsafe). If they
do we will re-add the expression.

This isn't done until after computing the initial result to ensure
that we get the minimal set of expressions. If we attempt to check
eagerly, before we kill/remove any, we might let one remain only to
later add an expression to the result that makes it no longer used.

The current pattern of removing as aggressively as possible and then
taking another pass over the removed expressions to re-add seems to
be the most optimal approach.

Fixes: open-policy-agent#2045
Signed-off-by: Patrick East <east.patrick@gmail.com>
@ovidius72
Copy link
Author

ovidius72 commented May 10, 2024

I'm sorry to reopen a very old issue. However, a couple of days ago I started to work again on the project from where I found that issue. I haven't had the chance to check it at that time.

Even with the latest opa release I'm still getting the same issue.
Was this addressed somehow at any point ?
The problem still occurs with negation like this:

allowed[keypad] { #Company C allows only members from C, D and E
  re_match("C", input.subject.group)
  keypad := data.keypads[_]
  g := {"C", "D", "E"}
  keypad.group == g
  not keypad.name == "example"
}

part of the compile api response.

{
 "type": "ref",
 "value": [
    {
        "type": "var",
        "value": "$_term_1_21"
    },
    {
         "type": "string",
         "value": "name"
      }
  ]
},

thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants