This next question is completely unrelated to ques...
# help
j
This next question is completely unrelated to question above. We're using scoped policies for multi-tenancy. I'm still not sure the best way to handle "default" policy rules. Let's imagine we take the approach that most people would likely take: default scope -> define default rules tenant scope -> define tenant specific rules Sticking with the JIRA analogy from the previous ticket. We might have a resource "ticket" and define some default rules, e.g. role "USER" can "update" a JIRA ticket if they belong to the project. A tenant might not want to inherit the default rules. But the only way they can do that is to create a tenant scoped resource policy that's exhaustive (e.g. it does EFFECT_ALLOW or EFFECT_DENY on all possible scenarios). It seems it would be more powerful and less dangerous if in combination with lenientScopeSearch, a tenant can optionally define a policy and choose to halt evaluating parent scopes.
Copy code
resourcePolicy:
  version: default
  resource: ticket
  scope: tenantA
  evaluateParentScope: false  # defaults to true
Or conditionally:
Copy code
resourcePolicy:
  version: default
  resource: ticket
  scope: tenantA
  evaluateParentScope:
    condition:
      match:
        expr: R.attr.someAttr in P.attr.someAttrs
I haven't really thought it through 100% and I'm not sure if scoped Principal policies matter. Just throwing this out there! 🙂
c
That's an interesting idea. We've approached it from the point of view that he default scope defines the base set of rules that apply to the entire system. Scopes are just exceptions to those rules and ideally there aren't that many of those. If you want the tenants to have their own custom policies that are not "inherited", then it's probably better to be explicit about that and use a different set of policies for each tenant . You can use the
version
of the policy as a tenant identifier in that case.
j
I think the problem with using the
version
is we need to know ahead of time at the call site (app) if they've decided to ignore the default set of rules for a specific resource.
If we include the tenant in the
version
field as a default for that tenant, it means all resources would not be inherited. But they may want some hybrid, e.g. most policies use the default except for these 1 or 2 complicated resources
c
I see. So I take it that the default policy is more permissive than you want? Otherwise, accidentally or intentionally falling back to the default policy shouldn't be a problem because most things would be denied anyway.
j
Yeah. For example, we may in our base policy say "JUNIOR_ASSOCIATE" can update this JIRA ticket when status is OPEN. But the client may only want a SENIOR_ASSOCIATE to update.
If 90% of clients want JUNIOR_ASSOCIATE, it seems to make sense to put that in the default policy
So in the tenant scoped policy, they would have to negate anything in the default policy. Whereas with my suggested feature they can just choose to halt evaluating any parent scope
c
I see. Even if that feature existed, you still have to be aware of these special cases from the outset, don't you? So, maybe one way to solve this is to introduce a dummy scope in the middle that denies everything? You'd give the tenant a scope like
deny_all.foo
and make sure that the policy for
deny_all
scope has a wildcard rule that denies everything.
j
That sounds like a good option 👍 Thanks. I'm really trying to drill into this because I'm presenting Cerbos internally to two different groups today (one technical and one semi-technical). And these specific questions have already come up. "How does a tenant use the default resource policy". "How can a tenant override the default policy". And it's the latter that will be difficult to explain. But I think your suggestion works. We would just need to be consistent across all resources. Thanks!
it does mean we will have to create a ton of these small resource policies 🙂 since Cerbos cannot have a gap in scope. I'll have to think about it a bit. Thanks for your help. Maybe this will lead to some internal discussion that provides a more convenient UX for this scenario
👍 1
Quick followup. If we create a dummy scope
deny_all
it means you have a single policy that acts as a filter for all tenants. So all tenant inheritance rules are intermingled in one file. The pro is you have a single file with a clear scope name. An alternative is to instead create
foo
and
foo.custom
where the tenant scope (
foo
) decides whether to inherit for that specific resource.
Copy code
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: default
  resource: ticket
  scope: foo
  rules:
    # Do not inherit default
    - actions: ["*"]
      roles: ["*"]
      effect: EFFECT_DENY
And then the tenant overrides are defined in
foo.custom
.
Copy code
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: default
  resource: ticket
  scope: foo.custom
  rules:
    - actions:
        - "update"
      effect: EFFECT_ALLOW
      roles:
        - SENIOR_ASSOCIATE
The call to the PDP would always pass resource scope
<tenant>.custom
and lenientScopeSearch must be on. The downside to this is if most clients have overrides, then you have an additional file per client.
I think I'm leaning towards the
deny_all
scope. Seems clearer to me.
c
Yeah, that would work too. If most of your tenants seem to require custom policies, you can always go back to the other method. (It would be a bit painful but can probably be automated.)
👍 1