Skip to content

[Question] Optimization for IAM-like policies #1688

@CharlesPhilippeLabbe

Description

@CharlesPhilippeLabbe

Hello,
I'm trying to design an AWS IAM like system where we have a set of resources which you can "allow" or "deny" for certain roles and users.

My Initial was this:

Initial Option: Model
[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, obj, act, eft

[role_definition]
g = _, _, _ # Roles permission
g2 = _, _ # Data group
g3 = _, _ # Action group

[policy_effect]
# Allow as soon as there one rule allowing
#e = some(where (p.eft == allow))
#Deny as soon as there is one rule denying
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

[matchers]
m = g3(r.act, p.act) && g(r.sub, p.sub, r.dom) && keyMatch5(r.obj, p.obj)
Initial Option: Policy
# sub/perm, resource, action
p, perm:instance:list, /instance, READ, allow
p, perm:instance:delete, /instance/{name}, DELETE, allow
p, perm:instance:describe, /instance/{name}, READ, allow
p, perm:instance:create, /instance/{name}, POST, allow

p, perm:service:list, /service, READ, allow

p, perm:proxy:read, /proxy/{name}/*, READ, allow
p, perm:proxy:write, /proxy/{name}/*, WRITE, allow
p, perm:proxy:crashes, /proxy/{name}/crashes, READ, allow
p, block:proxy:crashes, /proxy/{name}/crashes, READ, deny

# Adding wild cards in the group does seem to add a lot of time when users grow
#
#g, role:instance:admin, perm:instance:*, *
#g, role:service:admin, perm:service:*, *
#g, role:proxy:admin, perm:proxy:*, *
#
g, role:instance:admin, perm:instance:list, *
g, role:instance:admin, perm:instance:delete, *
g, role:instance:admin, perm:instance:describe, *
g, role:instance:admin, perm:instance:create, *

g, role:service:admin, perm:service:list, *

g, role:proxy:admin, perm:proxy:read, *
g, role:proxy:admin, perm:proxy:write, *

g, role:instance:viewer, perm:instance:list, *
g, role:instance:viewer, perm:instance:describe, *

g, role:service:viewer, perm:service:list, *

g, role:proxy:viewer, perm:proxy:read, *
g, role:proxy:viewer, block:proxy:crashes, *

g, user:admin, role:instance:admin, *
g, user:admin, role:service:admin, *
g, user:admin, role:proxy:admin, *

g, user:alice, role:instance:admin, orgA
g, user:alice, role:service:admin, orgA
g, user:alice, role:proxy:admin, orgA

g, user:alice, role:instance:viewer, orgB
g, user:alice, role:service:viewer, orgB
g, user:alice, role:proxy:viewer, orgB

g, user:bob, role:instance:viewer, orgB
g, user:bob, role:service:viewer, orgB
g, user:bob, role:proxy:viewer, orgB

g, user:nancy, role:proxy:admin, orgA

g3, POST, WRITE
g3, PUT, WRITE
g3, PATCH, WRITE
g3, DELETE, WRITE

g3, OPTIONS, READ
g3, HEAD, READ
g3, GET, READ

g3, READ, ANY
g3, WRITE, ANY

I find it's not ideal because I would need to duplicate every p to have one for deny and one for allow.

So My second option is this:

Tentative Option; Model
[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, obj, eft, dom

[role_definition]
g = _, _, _ # Roles permission
g2 = _, _, _ # Data group
g3 = _, _ # Action group

[policy_effect]
# Allow as soon as there one rule allowing
#e = some(where (p.eft == allow))
#Deny as soon as there is one rule denying
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

[matchers]
m = globMatch(r.dom, p.dom) && g(r.sub, p.sub, r.dom) && g2(r.obj, p.obj, r.act)
Tentative Option; Policy
#
p, role:instance:admin, perm:instance:list, allow, *
p, role:instance:admin, perm:instance:delete, allow, *
p, role:instance:admin, perm:instance:describe, allow, *
p, role:instance:admin, perm:instance:create, allow, *
p, role:service:admin, perm:service:list, allow, *
p, role:proxy:admin, perm:proxy:read, allow, *
p, role:proxy:admin, perm:proxy:write, allow, *
p, role:instance:viewer, perm:instance:list, allow, *
p, role:instance:viewer, perm:instance:describe, allow, *
p, role:service:viewer, perm:service:list, allow, *
p, role:proxy:viewer, perm:proxy:read, allow, *
p, role:proxy:viewer, perm:proxy:crashes, deny, *
# sub/perm, resource, action
g2, /instance, perm:instance:list, GET
g2, /instance/{name}, perm:instance:delete, DELETE
g2, /instance/{name}, perm:instance:describe, GET
g2, /instance/{name}, perm:instance:create, POST
g2, /service, perm:service:list, GET
g2, /proxy/{name}/*, perm:proxy:read, GET
g2, /proxy/{name}/*, perm:proxy:write, POST
g2, /proxy/{name}/crashes, perm:proxy:crashes, GET


g, user:admin, role:instance:admin, *
g, user:admin, role:service:admin, *
g, user:admin, role:proxy:admin, *

g, user:alice, role:instance:admin, orgA
g, user:alice, role:service:admin, orgA
g, user:alice, role:proxy:admin, orgA

g, user:alice, role:instance:viewer, orgB
g, user:alice, role:service:viewer, orgB
g, user:alice, role:proxy:viewer, orgB

g, user:bob, role:instance:viewer, orgB
g, user:bob, role:service:viewer, orgB
g, user:bob, role:proxy:viewer, orgB

g, user:nancy, role:proxy:admin, orgA

Both work, but the first one is significantly (at least 8 times) faster for a few thousand checks. Is there a way for me to optimize option 2 or am I better off going with my initial design?

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions