Skip to content

need to represent role assignments for resources #890

@davepacheco

Description

@davepacheco

Recall that:

  • actors are the term we use for a user or program that can get access to something (e.g., "alice")
  • resources are the things in the system that actors can get access to (e.g., a Project)
  • roles define a set of permissions for a specific kind of resource (e.g., "project viewer" grants "read" permissions on a Project and everything inside it)
  • role assignments combine a specific resource, role, and actor, saying: "alice has the 'project viewer' role for Project 'maze-war'"

We need a way to grant role assignments on resources. I expect it'll be pretty straightforward to do this on the database side, but I'm not clear yet how to represent this in the API.

RFD 43 touches on this (heavily inspired by GCP). But much of that RFD has been deferred to post-MVP (including custom roles and custom policies) and it also doesn't actually say how policies are managed (i.e., is there a /policies endpoint where you create them, get back an id, then set a policy_id field on a resource?)

Option 1: separate API endpoints underneath each resource (e.g., .../roles)

e.g., /organization/:org_name/roles, with the usual CRUD to create or remove role assignments for that resource. If you granted access to three Groups and two Users, you'd have five HTTP resources under this one. The HTTP resources under this endpoint would have a representation of the actor and role, with the resource being implied by the path. e.g., GET /organizations/maze-war/roles would show a list of objects with actor and role.

Nice:

  • matches the current database representation pretty well -- CRUD on these corresponds to CRUD of a row in role_assignments
  • common operations are very straightforward: create/remove a role assignment grants/revokes access
  • seems pretty similar to what GitHub does with repo/team assignments

Not so nice:

  • cannot modify the whole ACL for a resource atomically

Risks:

  • are there resources today where we can't do this (e.g., because there's no place to put child resources), or where it's awkward to do it (e.g., because they don't look like a resource that should have child resources)? Edit: I don't think so today, since we only let you assign roles on the Fleet, Silos, Organizations, and Projects.
  • if/when we add custom policies, what will these endpoints do?

Option 2: one separate API endpoint underneath each resource (e.g., .../policy)

This is the same as option 1, but we put the entire list of roles into one resource called policy. So if you have three Groups and two Users with access, you'd have one Policy object with an array of five assignments (rather than five separate HTTP resources, as in the previous case).

Nice:

  • can update the whole ACL atomically
  • it's clear how this could be evolved for custom policies, though it might be awkward (this might turn into an object with one policy_id field that points to some other thing)

Not so nice:

  • More annoying for clients that just want to grant/revoke access to one actor -- they have to do a read-modify-write
  • More complex implementation (every change involves fetching a bunch of records, updating a bunch of records, and maybe doing this in batches)
  • Size limits will be needed to avoid requests being huge or triggering huge database queries. GCP appears to apply such limits so that shouldn't be unreasonable.

Risks:

  • Are there resources today where we can't do this, or where it's awkward to do it? (Same as above, and for the same reason, I don't think so.)

Option 3: a top-level "policy" property on every resource with the policy contents

This looks just like option 2, except instead of the Policy being a separate resource, it's a new top-level property called policy on resources that are allowed to have their own policy. That property directly contains the list of role assignments.

Nice:

  • can easily fit into any resource (whereas putting a "policy" resource under some resources might look awkward)
  • can update the whole ACL atomically
  • it's clear and smooth to evolve this for totally custom policies

Not so nice:

  • object representations may get large, and a large fraction of them might be taken up with these assignments
  • it means fetching a potentially large number of database records every time you want to get every resource, even if the user doesn't want or need those things. This seems like kind of a big deal.

Option 4: Policies as separate HTTP resources

This seems closest to what RFD 43 envisions, where each resource is associated with a policy that's managed via some other endpoint (e.g., /policies, with associated CRUD)

Nice:

  • Smooth transition to custom policies (but possibly only because we have to do all the work we're trying to punt on by not doing custom policies now).

Not so nice:

  • This model seems a lot more annoying to users and clients. If you just want to grant/revoke access to a resource: you need to fetch the resource, look at its policy id, fetch that policy, compute the new value for the policy, and then either update the policy directly or else create a new policy and then update the original resource to point to the new policy.

I lean towards option 1 as it seems to provide a clear, simple model for clients and end users and I think it's also the easiest to implement :) though it does mean creating a bunch more API endpoints, with associated boilerplate, tests, etc.


Note that I think there's a somewhat deeper question here, which is about the model we want to expose to end users.

The model of GitHub, Google Drive, etc. is:

  • There's a relatively small number of "important" resources (for GitHub: organizations and repos; for Drive: directories and documents). These are the main resources that you can grant access to.
  • For each resource, there's basically a list of users or groups with access and a specific level of access. These are basically a small number of canned roles (like "admin", "editor", "commenter", "viewer").

This is similar to how things work in our API today, and we've said this is what we're going to do for MVP because it's much simpler to implement. Personally, I find it pretty intuitive as a user.

The model of AWS and GCP seems to be more like: you can assign arbitrarily complex policies to virtually any resource. This is much more flexible. But we've also heard feedback that IAM is nightmarishly complex in AWS and GCP -- is that partly because in order to grant someone access, you need to create a new policy that grants them a specific role and attach that policy to a resource? Feels like a lot of paperwork for the common case.

Whatever we pick here, I don't think we're foreclosing on custom roles and policies, because we can implement compatibility APIs that read or modify the corresponding bits of a resource's policy. On the other hand, if we want the simpler model for the longer term, it doesn't make sense to do option 4 now, and maybe not 2-3 either.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions