Permissions for workflow templates

This blueprint covers the design for a permission system for workflow templates.

The general idea is that workflow templates are something like method calls on workspaces, defining their API, and some of that API can be a public interface for the workspace, or a limited internal API.

We need a permission system to define who can access such APIs.

Use cases

  1. “Publish to proposed-updates”: users do not have write access to a workspace but can invoke one of its workflow templates to publish packages to a hardcoded collection. The target collection has index files that get properly generated and signed.

  2. “Maintenance”: workspace owners can set up workflow templates to use as workspace maintenance tasks, which only workspace owners can run

Current situation

Starting a workflow

Currently, workflow templates have a can_display permission which is granted to users who have the VIEWER role on the containing workspace.

Workflow templates also have a can_run permission which is granted to users who have the CONTRIBUTOR role on the containing workspace.

This distinction is too coarse grained and too limited: it does not allow to grant a group of users can_run access without making them CONTRIBUTOR on the workspace, and it doesn’t allow to limit can_run to a more restricted set of users.

Permissions while running the workflow

Currently, when permission checks are performed while running a workflow or one of its work requests, they are done as the user who started the workflow.

This is going to change soon to be more restrictive, that is, running with a subset of the permissions that the user who started the workflow would have, so that only permissions relevant to workflow activities are granted. For example, running code for a work request is not going to be granted permissions to affect a group membership, or create workspaces.

The actual permissions checks currently being done are:

  • accessing build environments and other artifacts: ExternalTask.fetch_artifact calls ArtifactView/WorkspaceArtifactView, which need can_display on the workspace

  • can_display checks when serving APT repositories from private workspaces

  • using assets to sign releases

Adding results to collections is performed by event reactions, that currently do not perform permission checks. They are however set up by workspace code, which can validate inputs and perform the appropriate gatekeeping.

The rest of workflow code currently runs without permission checks.

There is currently no way to grant a user permission to sign the release files on a collection, even if they could start a workflow template that published to that collection.

Proposed updates to starting a workflow

Define roles for WorkflowTemplate

Define possible roles that users can have on WorkflowTemplate:

  • OWNER: people who can edit and run the workflow template

  • STARTER: people who can use (display and run) the workflow template, but not modify it

  • VIEWER: people who can display, but not run, the workflow template

Add explicit role assignments to WorkflowTemplate

Add a WorkflowTemplateRole model to explicitly grant roles to a group for a WorkflowTemplate instance.

Add role inferences

To handle the common cases as they are now, we can have these role inferences:

  • OWNER implied by Workspace.Roles.OWNER

  • STARTER implied by Workspace.Roles.CONTRIBUTOR

  • VIEWER implied by Workspace.Roles.VIEWER

This implements the status quo, and allows granting extra roles to groups of people, addressing the starting side of the “Publish to proposed-updates” use case for users that are not part of the WorkflowTemplate’s workspace.

This is not sufficient to handle the “Maintenance” use case.

Add a restricted flag to WorkflowTemplate

We can add a restricted flag to WorkflowTemplate, which changes the implications: when set to True, the implications become:

  • OWNER implied by Workspace.Roles.OWNER

  • STARTER implied by Workspace.Roles.OWNER

  • VIEWER implied by Workspace.Roles.VIEWER

This would address the “Maintenance” use case: the workflow templates in question can be configured with restricted=True, and access would be restricted to workspace owners, handling the “Maintenance” use case.

It is still possible to have a group for helpers of the workspace owners, who can be allowed to start some such maintenance workflow templates.

Starting workflows on private repositories

These changes would allow to grant users the ability to start a workflow on a private repository that they cannot access.

While we currently have no use cases for this, it could become a useful tool for modeling embargoed workspaces with a limited public API.

This would however not be easily supported by the UI, which treats an inaccessible private workspace as a workspace that does not exist.

While we could consider changing the UI to treat private workspaces with viewable workflow templates differently, the problem could be sidestepped by setting up “proxy” workflow templates in different, accessible workspaces that, when started, start designated workflow templates in the private workspaces.

We may need special care to see if and how we want to allow the person who created such a workflow to see its progress even if it is in a workspace they cannot access.

Proposed updates to permission checking while running a workflow

Permission checking during the workflow execution

We can allow a workflow template to optionally be configured with additional groups. If that is present, all permission checks run as part of the workflow execution consider the user as if it were also a member of those groups.

The user is never effectively added to the group, though, and their membership is unchanged for all operations outside of that workflow execution.

This would solve the signing permission issue of the “Publish to proposed-updates” use case: one can define a group for signers with the appropriate roles, and configure it as an additional group for the workflow template.

Alternatively, one can assign the workspace owners group as additional group, which would be a simpler setup that is adequate for most organizational needs.

Refactor permission predicates

Recent changes around permission predicates have led to various special casing for checks running as part of workflows or work requests, and this would add another one.

We can refactor permission predicates to take an argument which contains the user to check, the work request or workflow if executing for a worker, extra groups to be granted, and possibly more as will be needed.

Such argument could be an object that is typed as a superclass of debusine.db.context.Context, or something easily constructed from the same: this would allow to pass the current application context, or to construct alternate inputs for other kinds of permission checking.

Permission check and predicate decorators can then access the structure being passed instead of using the application context, avoiding the risk of broken assumptions when checking permissions of users other than the current one.