Merge pull request #29202 from 04kash/scaffolder-task-granular-permissions

Scaffolder task granular permissions
This commit is contained in:
Ben Lambert
2025-07-08 12:54:43 +02:00
committed by GitHub
28 changed files with 875 additions and 101 deletions
@@ -73,7 +73,7 @@ class ExamplePermissionPolicy implements PermissionPolicy {
isPermission(request.permission, templateParameterReadPermission) ||
isPermission(request.permission, templateStepReadPermission)
) {
if (user?.info.userEntityRef === 'user:default/spiderman')
if (user?.info.userEntityRef === 'user:default/bob')
return createScaffolderTemplateConditionalDecision(request.permission, {
not: scaffolderTemplateConditions.hasTag({ tag: 'secret' }),
});
@@ -87,7 +87,7 @@ class ExamplePermissionPolicy implements PermissionPolicy {
}
```
In this example, the user `spiderman` is not authorized to read parameters or steps marked with the `secret` tag.
In this example, the user `bob` is not authorized to read parameters or steps marked with the `secret` tag.
By combining this feature with restricting the ingestion of templates in the Catalog as recommended in our threat model, you can create a solid system to restrict certain actions.
@@ -113,7 +113,7 @@ class ExamplePermissionPolicy implements PermissionPolicy {
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, actionExecutePermission)) {
if (user?.info.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/alice') {
return createScaffolderActionConditionalDecision(request.permission, {
not: scaffolderActionConditions.hasActionId({
actionId: 'debug:log',
@@ -130,10 +130,10 @@ class ExamplePermissionPolicy implements PermissionPolicy {
}
```
With this permission policy, the user `spiderman` won't be able to execute the `debug:log` action.
With this permission policy, the user `alice` won't be able to execute the `debug:log` action.
You can also restrict the input provided to the action by combining multiple rules.
In the example below, `spiderman` won't be able to execute `debug:log` when passing `{ "message": "not-this!" }` as action input:
In the example below, `alice` won't be able to execute `debug:log` when passing `{ "message": "not-this!" }` as action input:
```ts title="packages/backend/src/plugins/permission.ts"
/* highlight-add-start */
@@ -151,7 +151,7 @@ class ExamplePermissionPolicy implements PermissionPolicy {
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, actionExecutePermission)) {
if (user?.info.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/alice') {
return createScaffolderActionConditionalDecision(request.permission, {
not: {
allOf: [
@@ -176,7 +176,9 @@ class ExamplePermissionPolicy implements PermissionPolicy {
### Authorizing scaffolder tasks
The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder.
The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder. You can configure policies to restrict access to tasks to only their creators, while also granting specific users the permission to view all tasks.
The following is a simple example of how to do this:
```ts title="packages/src/backend/plugins/permissions.ts"
/* highlight-add-start */
@@ -185,6 +187,20 @@ import {
taskCreatePermission,
taskReadPermission,
} from '@backstage/plugin-scaffolder-common/alpha';
import {
AuthorizeResult,
isPermission,
PolicyDecision,
} from '@backstage/plugin-permission-common';
import {
PermissionPolicy,
PolicyQuery,
PolicyQueryUser,
} from '@backstage/plugin-permission-node';
import {
createScaffolderTaskConditionalDecision,
scaffolderTaskConditions,
} from '@backstage/plugin-scaffolder-backend/alpha';
/* highlight-add-end */
class ExamplePermissionPolicy implements PermissionPolicy {
@@ -193,26 +209,58 @@ class ExamplePermissionPolicy implements PermissionPolicy {
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, taskCreatePermission)) {
if (user?.info.userEntityRef === 'user:default/spiderman') {
if (isPermission(request.permission, taskReadPermission)) {
// Allow alice to read any task
if (user?.info.userEntityRef === 'user:default/alice') {
return {
result: AuthorizeResult.ALLOW,
};
}
// Allow users to read tasks they created
return createScaffolderTaskConditionalDecision(
request.permission,
scaffolderTaskConditions.isTaskOwner({
createdBy: user?.info.userEntityRef ? [user?.info.userEntityRef] : [],
}),
);
}
if (isPermission(request.permission, taskCreatePermission)) {
const userArray = ['user:default/bob', 'user:default/alice'];
const allowed = userArray.some(
allowedUser => user?.info.userEntityRef === allowedUser,
);
if (allowed) {
return {
result: AuthorizeResult.ALLOW,
};
}
return {
result: AuthorizeResult.DENY,
};
}
if (isPermission(request.permission, taskCancelPermission)) {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
if (isPermission(request.permission, taskReadPermission)) {
if (user?.info.userEntityRef === 'user:default/spiderman') {
// Allow bob to cancel only his tasks
if (user?.info.userEntityRef === 'user:default/bob') {
return createScaffolderTaskConditionalDecision(
request.permission,
scaffolderTaskConditions.isTaskOwner({
createdBy: user?.info.userEntityRef
? [user?.info.userEntityRef]
: [],
}),
);
}
// Allow alice to cancel any task
if (user?.info.userEntityRef === 'user:default/alice') {
return {
result: AuthorizeResult.ALLOW,
};
}
return {
result: AuthorizeResult.DENY,
};
}
/* highlight-add-end */
@@ -223,13 +271,21 @@ class ExamplePermissionPolicy implements PermissionPolicy {
}
```
In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources:
In the provided example permission policy, we only grant the user `bob` permissions to perform/access the following actions/resources:
- Read all scaffolder tasks and their associated events/logs.
- Cancel any ongoing scaffolder tasks.
- Read scaffolder tasks and their associated events/logs for tasks created by `bob`.
- Cancel ongoing scaffolder tasks created by `bob`.
- Trigger software templates, which effectively creates new scaffolder tasks.
Any other user would be denied access to these actions/resources.
On the other hand, the user `alice` is granted:
- Read access to all scaffolder tasks and their associated events/logs.
- The ability to cancel any ongoing scaffolder task.
- The ability to trigger software templates.
All other users are granted permissions to perform/access the following actions/resources:
- Read scaffolder tasks and their associated events/logs for tasks created by the user.
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases.