hi, i noticed a bug relating to scopes using PlanR...
# help
n
hi, i noticed a bug relating to scopes using PlanResource(), i have 2 policies a scoped and its related unscoped one [as below] I noticed calling PlanResources seems to always read from the unscoped (seems to ignore the scoped one - i had DENY in the scoped but ALLOW in unscoped and it returns CONDITIONAL) UNSCOPED:
{
"apiVersion": "<http://api.cerbos.dev/v1|api.cerbos.dev/v1>",
"resourcePolicy": {
"resource": "top",
"version": "default",
"rules": [
{
"actions": [
"VIEW"
],
"roles": [
"customer-user"
],
"condition": {
"match": {
"any": {
"of": [
{
"expr": "R.attr.custAnal in P.attr.custAllowedValues"
},
{
"expr": "R.attr.salhAnal in P.attr.salhAllowedValues"
}
]
}
}
},
"effect": "EFFECT_ALLOW"
}
]
}
}
SCOPED:
{
"apiVersion": "<http://api.cerbos.dev/v1|api.cerbos.dev/v1>",
"resourcePolicy": {
"resource": "top",
"version": "default",
"rules": [
{
"actions": [
"VIEW"
],
"roles": [
"customer-user"
],
"condition": {
"match": {
"any": {
"of": [
{
"expr": "R.attr.custAnal in P.attr.custAllowedValues"
},
{
"expr": "R.attr.salhAnal in P.attr.salhAllowedValues"
}
]
}
}
},
"effect": "EFFECT_DENY"
}
],
"scope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
}
}
d
What’s in the request?
n
hi, is there an easy way of dumping the request ?
principal and request both have scopes set correctly.. and principal has role set to customer-user
additionally principal has some values set both for custAllowedValues and salhAllowedValues
d
Hm… If you have this example in the playground, you can DM the URL
n
sorry not familiar with the playground 🙂 .. can you maybe run the above 2 policies and check
d
I’ll need the request. You can paste it here or DM me
Cerbos has a playground which is a convenient way to get started with Cerbos policies. More info https://cerbos.dev/playground
n
Copy code
{
  "requestId": "123123",
  "principal": {
    "id": "123",
    "roles": [
      "customer-user"
    ],
    "attr": {
      "custAllowedValues": "VALUE1",
      "salhAllowedValues": "VALUE2"
    },
    "scope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
  },
  "resources": [
    {
      "resource": {
        "kind": "top",
        "id": "123",
        "scope": "T8eeab29e-9622-48e6-967d-5137b485d8aa",
        "attr": {}
      },
      "actions": [
        "VIEW"
      ]
    }
  ]
}
d
Make sure you pass
scope
in the resource object. For example,
Copy code
{
  "requestId": "query-plan",
  "resource": {
    "kind": "top",
    "scope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
  },
  "principal": {
    "id": "123",
    "roles": [
      "customer-user"
    ],
    "attr": {
      "custAllowedValues": "VALUE1",
      "salhAllowedValues": "VALUE2"
    }
  },
  "action": "VIEW"
}
Depending on the presence of the resource scope, the query planner returns the inverse condition.
n
please check again.. as shown in my request.. the request has the scope set to the correct value
c
Yes, the plan doesn't look quite right. We'll investigate. Thanks for reporting.
OK, this is a tricky one. The root cause is that the data types in the request are wrong. The policy condition uses the
in
operator, which requires a list to operate on. In the request you're sending a string instead. So, because the condition is invalid, the planner ignores it. If you use the correct data types in the request, the response is correct: Request:
Copy code
cat <<EOF | curl --silent "<http://localhost:3592/api/plan/resources?pretty>" -d @-
{
  "requestId": "query-plan",
  "resource": {
    "kind": "top",
    "scope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
  },
  "principal": {
    "id": "123",
    "roles": [
      "customer-user"
    ],
    "attr": {
      "custAllowedValues": ["VALUE1"],
      "salhAllowedValues": ["VALUE2"]
    }
  },
  "action": "VIEW",
  "includeMeta": true
}
EOF
Response:
Copy code
{
  "requestId": "query-plan",
  "action": "VIEW",
  "resourceKind": "top",
  "filter": {
    "kind": "KIND_CONDITIONAL",
    "condition": {
      "expression": {
        "operator": "not",
        "operands": [
          {
            "expression": {
              "operator": "or",
              "operands": [
                {
                  "expression": {
                    "operator": "in",
                    "operands": [
                      {
                        "variable": "request.resource.attr.custAnal"
                      },
                      {
                        "value": [
                          "VALUE1"
                        ]
                      }
                    ]
                  }
                },
                {
                  "expression": {
                    "operator": "in",
                    "operands": [
                      {
                        "variable": "request.resource.attr.salhAnal"
                      },
                      {
                        "value": [
                          "VALUE2"
                        ]
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  },
  "meta": {
    "filterDebug": "(not (or (in request.resource.attr.custAnal [\"VALUE1\"]) (in request.resource.attr.salhAnal [\"VALUE2\"])))",
    "matchedScope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
  }
}
I think we need to do some work on our side to surface this back to the caller. The invalid expression shouldn't have been silently ignored. I'll create an issue to track it. Thanks again for reporting it.
BTW, schemas would have caught this problem earlier. I'd highly recommend using them if at all possible. https://docs.cerbos.dev/cerbos/latest/policies/schemas.html
n
thanks charith .. to simplify i changed it to string.. am sure im sending a string array .. let me check this and get back, thanks 🙂
my issue was that cerbos seemed to ignore the scoped policy (if that had a DENY - it picked up the unscoped one), can you kindly check ? Ideally i wanted to set DENY at the unscoped .. and ALLOW at the scoped (this is what wasnt working)
c
So, it's not ignoring the DENY. It's just that you have a condition that must be satisfied in order to DENY something. Therefore, the plan is CONDITIONAL.
In scoped policies, we stop at the first policy that matches the criteria. Because the idea is to allow you to override the base set of rules. So, a child policy can ALLOW something that's DENIED by the parent policy.
n
mmm, thanks charith.. so what i observe is: 1. scoped policy has ALLOW {condition} 2. unscoped has DENY {same condition} 3. The condition returned by PlanRes() is CONDITIONAL (as expected) but [NOT condition] (a negation/NOT of the condition) What i was expecting was it checks the scoped policy and just returns (doesnt go to the unscoped one)
c
It doesn't go to the unscoped one. If you use
includeMeta
in the request, you can see that it matched the scoped policy. If you want a flat out deny, you should remove the condition on the rule
I removed the condition from your scoped policy and then the plan is DENY
Copy code
{
  "requestId": "query-plan",
  "action": "VIEW",
  "resourceKind": "top",
  "filter": {
    "kind": "KIND_ALWAYS_DENIED"
  },
  "meta": {
    "filterDebug": "(false)",
    "matchedScope": "T8eeab29e-9622-48e6-967d-5137b485d8aa"
  }
}
n
thanks charith, you have been very helpful .. i guess there was a gap in my understanding 🙂
c
Glad to help 🙂