21. Service accounts
There are three levels of security checks in a microservice web application that uses OpenID Connect:
- scope claims limit the possible operations to what the user (or admin on behalf of the organization) consented to
- service authorization limit the possible operations to what specific services are allowed to do, on behalf of users or even without them
- permission checks limit the possible operations to the relationships between subject, permission and resource allow
This ADR deals with a prerequisite for service authorization: service accounts.
Some services need access to file content without a user being logged in. We currently pass the owner or manager of a space in events which allows the search service to impersonate that user to extract metadata from the changed resource. There are two problems with this:
- The service could get all permissions of the user and gain write permission
- There is a race condition where the user in the event might no longer have read permission, causing the index to go stale
The race condition will become more of an issue when we start working on a workflow engine.
How can we grant services the least amount of permissions required for their purpose?
- It should be possible to represent this as servicePrincipals in the libregraph API, similar to the MS Graph servicePrincipal.
- Services should check permissions using the oCIS permissions or reva auth service, we don’t want to introduce a new mechanism for this
Chosen option: Service Accounts
- Good, because it allows provisioning permissions for services
- Good, because it uses existing CS3 concepts
- Good, because it uses the existing permissions service
- Good, because it can be mapped to libre graph permissions
- Bad, because we have to make the reva auth manager aware of CS3
USER_TYPE_SERVICE
- Bad, because we have to provision and manage service accounts on init
- Bad, because external APIs may need to filter out service accounts
- Bad, because we need to persist service accounts in addition to normal user accounts
Make the reva auth manager and registry aware of CS3 users of type USER_TYPE_SERVICE
. Then we can provision service accounts at oCIS initialization and use the permissions service to check permissions.
When assigning permissions we use the permission constraints to define the scope of permissions, see Permission Checks for more details.
To authenticate service accounts the static reva auth registry needs to be configured with a new auth provider for type service
. The actual provider can use a plain JSON file or JSONCS3 that is provisioned once with ocis init
. TODO Furthermore, the user provider needs to be able to return users for service accounts.
- Good, because we could replace machine auth with specific service accounts and no longer have to distribute a shared secret everywhere
- Bad, because we don’t know if a there are places in the code that try to look up a user with USER_TYPE_SERVICE at the cs3 users service … they might not exist there … or do we have to implement a userregistry, similar to the authregistry?
- Bad, because we have to provision and manage service accounts on init
- Bad, because we have to write code to manage service accounts or at least filter them out in the admin ui
We could implement a new auth manager that can authenticate space owners, a CS3 user type we introduced for project spaces which ‘have no owner’, only one or more managers.
- Good, because it reuses the space owner user type
- Bad, because the space owner always has write permission
- Bad, because we don’t know if a there are places in the code that try to look up a user with USER_TYPE_SPACE_OWNER at the cs3 users service … they might not exist there … or do we have to implement a userregistry, similar to the authregistry?
- Bad, because it feels like another hack and does not protect against compromised services that try to execute operations that the user did not consent to.
- MS Graph servicePrincipal
- reva auth managers - lacks docs for
auth_machine
, to be found in the code
When checking permissions we do not check for global permissions but for the concrete permission. Global permissions describe permissions that are used when assigning permissions, e.g. the index service account has the read permission constrained to tenant. The concrete permission check always contains a resource and a specific permission like Resource.Read
or Space.Delete
. That we currently check if a user has the delete-all-spaces
permission is wrong. It should instead check if the user has the permission Space.Delete
on a specific space. The permissions service can implement the check by taking the permission constraint into account.
Another example would be a Resource.Read
check for a specific resource. Normal users like the demo users Einstein and Marie would have the permission Resource.ReadWrite
with the constraint ALL (which limits them to all files they own and that have been shared with them). The permissions service can return true. Service accounts like the indexer would have Resource.Read
with the constraint TENANT and thus be granted read access to all resources.
In the storage drive implementation we can check the ACLs first (which would allow service accounts that are known to the underlying storage system, e.g. EOS to access the resource) and then make a call to the permissions service. At least for the Read Resource permission. Other permission checks can be introduced as needed.
The permission names and constraints are different from the MS Graph API. Giving permission like Files.ReadWrite.All
a different meaning, depending on the type of user (for normal users it means all files they have access to, for service accounts it means all files in the organization) is a source of confusion which only gets worse when there are two different UUIDs for this.