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

How to express "no matching predicates"? #169

Open
ahupp opened this issue Oct 3, 2024 · 3 comments
Open

How to express "no matching predicates"? #169

ahupp opened this issue Oct 3, 2024 · 3 comments

Comments

@ahupp
Copy link

ahupp commented Oct 3, 2024

Quite possibly there's a way to do what I want, but haven't been able to figure it out. I'm gating access to some object, based on data in that object. Normally the policy just checks for simple ownership:

Authority:

user(1234);

Authorizer:

check if user({content_owner});
// or equivalently, where the content_owner() fact is injected based on the object contents
content_owner({content_owner});
check if user($u), content_owner($u);

However, sometimes there's an extra field on the content (say, a group) and the user needs to also be a member of that if present. So you could write the authorizer like:

check if 
  user({content_owner}),
  user_group({content_group});

I could dynamically add the check depending on the object properties, but if I'm going to have some random code doing that I might as well just check it directly; my hope is that the authorizer is static and the source of truth for the rules. There are a lot of different cases here and I was hoping to have a single Authorizer file for each.

So, I'm struggling to write policies that can selectively depend on certain facts. Conceptually what I want is something like:

check if no_fact_exists(object_group($oid, $_)) or object_group($oid, $gid), user_group($gid);

Or, some way to construct a set out of all the terms in a fact, where "no match" results in an empty set.

The best solution I've found so far is to make the injected fact carry a set, ab empty set indicates a null/missing value, and this fact is always added to the Authorizer e.g:

object_group(..., []); // object has no required groups
object_group(..., [1234]); // object does require groups

check if 
  object_group($oid, $groups), user_groups($user_groups), 
  $object_groups == $user_groups || $object_groups.intersect($user_groups).length() > 0;

Any thoughts?

@ahupp ahupp changed the title Support for way to express "no matching predicates" How to express "no matching predicates"? Oct 3, 2024
@divarvel
Copy link
Collaborator

divarvel commented Oct 4, 2024

about negative matching

currently, there is one way to assert the absence of a fact, the deny policy. It might work with your use-case, exploiting the fact that policies are lazily matched (contrary to checks).

An upcoming feature is something equivalent is reject if which extends this behaviour to checks and can thus be used in blocks as well as authorizers and don’t depend on ordering. The motivating use-case was to allow AWS-style policies, with the (explicit) Deny, (explicit) Allow, (implicit) Deny evaluation order.

for your case

I would generate different facts based on whether the object has a restriction on groups

// generated based on the object being accessed
object("owner"); // no group restriction
object("owner", ["groups"]); // group restrictions

// generated based on the user info
user("user id",["user groups"]);

and in the authorizer:

check if user($user, $user_groups), object($user)
  or user($user, $user_groups), object($user, $object_groups), $object_groups.intersection($user_groups).length() > 0;

@ahupp
Copy link
Author

ahupp commented Oct 6, 2024

Thanks! That will work for me.

With the deny policy, my challenge is the reverse. e.g say I have several different kinds of actors in the system, where "users" can be in a group, but for another kind that's not possible. But maybe that other entity can pass a different allow check. So if I wrote:

deny if
   object_group({oid}, $group_id),
   user_groups($user_groups),
   !$user_groups.contains($group_id);

Then if we didn't define an user_groups fact this would fail open. Of course we control the authority so could just require user_groups always exist (and/or assert check if user_groups($_); but it leaves me feeling a little wary of non-obvious failure cases.

@divarvel
Copy link
Collaborator

divarvel commented Oct 7, 2024

yeah, relying on deny policies is a bit more risky indeed.

From what I understood of your case, putting group info in the same fact as the user id seems to be the simplest way to go

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

No branches or pull requests

2 participants