Hi <@U02874JL5D3> / All I think I found a bug. ...
# help
n
Hi @Charith (Cerbos) / All I think I found a bug. I started with a simple policy (policy1) as below (2 roles, one allowing and other denying). If I am part of both roles then CheckAccess returns DENY (makes sense as the most restrictive role wins). However if I define a derived-role as policy-2 below and I am part of both roles, then the response comes as ALLOW (and not conditional, poweruser seems to win AND restricted-user is not factored into consideration). The only way I can make it return conditional is if I am not part of poweruser and only part of “restricted-user”, this doesn’t seem to be following the rule of “most restrictive wins”. So what seems to work for roles, is not working for derivedRoles. Policy1:
{
"apiVersion": "<http://api.cerbos.dev/v1|api.cerbos.dev/v1>",
"resourcePolicy": {
"resource": "crm_prospects",
"version": "default",
"importDerivedRoles": [
"sales-i-roles"
],
"rules": [
{
"actions": [
"VIEW"
],
"roles": [
"poweruser"
],
"effect": "EFFECT_ALLOW"
},
{
"actions": [
"VIEW"
],
"roles": [
"restricted-user"
],
"effect": "EFFECT_DENY"
}
],
"scope": "TENANT-00101581-3dd4-40b8-a2e3-175624586f85"
}
}
Policy2:
{
"apiVersion": "<http://api.cerbos.dev/v1|api.cerbos.dev/v1>",
"resourcePolicy": {
"resource": "crm_prospects",
"version": "default",
"importDerivedRoles": [
"sales-i-roles"
],
"rules": [
{
"actions": [
"VIEW"
],
"roles": [
"poweruser"
],
"effect": "EFFECT_ALLOW"
},
{
"actions": [
"VIEW"
],
"derivedRoles": [
"nimit-restrict"
],
"effect": "EFFECT_ALLOW"
}
],
"scope": "TENANT-00101581-3dd4-40b8-a2e3-175624586f85"
}
}
Derived Role:
{
"apiVersion": "<http://api.cerbos.dev/v1|api.cerbos.dev/v1>",
"derivedRoles": {
"name": "sales-i-roles",
"definitions": [
{
"name": "nimit-restrict",
"parentRoles": [
"restricted-user"
],
"condition": {
"match": {
"any": {
"of": [
{
"expr": "R.attr.createdByID == P.attr.subjectid"
},
{
"expr": "R.attr.updatedByID == P.attr.subjectid"
}
]
}
}
}
}
]
}
}
c
Just from a quick glance, policy 2 doesn't have any
DENY
rules. So isn't it working as expected or am I missing something?
n
Hi, it is indeed ALLOW (which should give conditional access)
to add - which it does return, but only when i am not part of poweruser role and only part of restricted-user role
c
Sorry, I don't see the problem. Can you share your request body
n
Ah .. i think i got it .. as soon as i switched it to DENY.. im getting conditional 🙂
c
Yeah 🙂 Policy1 and Policy2 are not the same
n
my bad 😄
hey @Charith (Cerbos) i guess i still have a question 🙂 .. if you were to evaluate policy-2 (and the user is part of both the roles) .. would you expect an ALLOW or CONDITIONAL ?
I would think conditional, as power-user says ALLOW but restricted-user access is conditional (on the expression) .. so by rule of most restrictive wins.. i would think conditional
c
That's an interesting question. Indeed, the current behaviour of PlanResources is that the generated plan is conditional even if the user has a role that gives it unconditional access. I think it's better to err on the side of caution because the planner is not able to reason about it like a human would. It doesn't have enough information on hand to make sure that the principal definitely has access to the resource. In this particular case, it won't be wrong if the plan was unconditional as well. We currently don't try to optimise for that because there are lots of edge cases to cater for and we don't want to accidentally give someone access when they shouldn't have access.
n
hi @Charith (Cerbos), yes indeed the response should be conditional.. but it isn't .. the response is full_access (engine seems to honour poweruser and ignore restricted-user)
c
Really? It returns a conditional plan to me (provided there's an explicit DENY rule -- which you didn't have in your original policy2)
Copy code
cat <<EOF | curl --silent "<http://localhost:3592/api/plan/resources?pretty>" -d @- 
{
  "requestId": "query-plan",
  "resource": {
    "kind": "crm_prospects"
  },
  "principal": {
    "id": "foo",
    "roles": [
      "poweruser",
      "restricted-user"
    ],
    "attr": {
      "subjectid": "bar"
    }
  },
  "action": "VIEW",
  "includeMeta": true
}
EOF
Copy code
{
  "requestId": "query-plan",
  "action": "VIEW",
  "resourceKind": "crm_prospects",
  "filter": {
    "kind": "KIND_CONDITIONAL",
    "condition": {
      "expression": {
        "operator": "not",
        "operands": [
          {
            "expression": {
              "operator": "or",
              "operands": [
                {
                  "expression": {
                    "operator": "eq",
                    "operands": [
                      {
                        "variable": "request.resource.attr.createdByID"
                      },
                      {
                        "value": "bar"
                      }
                    ]
                  }
                },
                {
                  "expression": {
                    "operator": "eq",
                    "operands": [
                      {
                        "variable": "request.resource.attr.updatedByID"
                      },
                      {
                        "value": "bar"
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  },
  "meta": {
    "filterDebug": "(not (or (eq request.resource.attr.createdByID \"bar\") (eq request.resource.attr.updatedByID \"bar\")))"
  }
}
Anyway, as I mentioned above, we haven't really tried to optimise this path. It could be conditional or unconditional -- both of which are correct because the user has conflicting roles.
n
hi, yes i do get a conditional if there's a deny .. but as i understand it should be a conditional even for allow (as that allow is also conditional - based on the expression)
just that with DENY .. it adds a NOT in front of the expression
c
Yes, because we prioritise explicit DENY rules. Otherwise, because there's an unconditional rule that gives
poweruser
access, the plan would be unconditional as well.
n
yup that makes sense .. but i was hoping the precedence is DENY , CONDITIONAL and ALLOW (in that order)
c
Yeah, fair enough. We simply prioritise DENY. After that, if there's any ambiguity, we take the path of least resistance.
n
hey @Charith (Cerbos) sorry to keep coming back to you, having discussions with my team and we all do feel that adopting the path of least resistance would mean you will grant access to someone who does not deserve.. still feel the right order should be deny, conditional then full-access.. do you foresee this as a big/breaking change to your engine ?
c
Can you explain further? If a user has a role that grants them access to something, why shouldn't they be given access?
n
Scenario-1 - when a user is part of 2 roles 1 granting access and the other denying.. the engine chooses DENY Scenarios-2 - when a user is part of 2 roles 1 granting access and the other conditional-access.. the engine chooses ALLOW (going by the above scenario, think it should be conditional) The engine chooses to prioritise DENY over ALLOW but not conditional-access over ALLOW
c
Yeah I can see your point. But, for most users, it'd be pretty baffling if they are denied access to something that they clearly have access to. Imagine an admin who accidentally adds themselves to a restricted role and suddenly getting locked out of the system. That's why we chose to only prioritise DENY. Ideally, there shouldn't be such conflicting role assignments in the first place. It's a signal that there's something not quite right with the design of roles.
n
ok, think we can rest it there