From 6bbe08d6b909109ac605416871b0bd664e0c784b Mon Sep 17 00:00:00 2001 From: Joe Porpeglia Date: Wed, 13 Apr 2022 18:28:02 -0400 Subject: [PATCH] Add documentation for custom permission rules Signed-off-by: Joe Porpeglia --- docs/permission/custom-rules.md | 107 ++++++++++++++++++++++++++++ docs/permission/writing-a-policy.md | 8 --- microsite/sidebars.json | 1 + 3 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 docs/permission/custom-rules.md diff --git a/docs/permission/custom-rules.md b/docs/permission/custom-rules.md new file mode 100644 index 0000000000..de993d4c44 --- /dev/null +++ b/docs/permission/custom-rules.md @@ -0,0 +1,107 @@ +--- +id: custom-rules +title: Defining custom permission rules +description: How to define custom permission rules for existing resources +--- + +For some use cases, you may want to define custom [rules](./concepts.md#resources-and-rules) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. + +## Define a custom rule + +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule in `packages/backend/src/plugins/permission.ts`, but you can put it anywhere that's accessible by your `backend` package. + +```typescript +import type { Entity } from '@backstage/plugin-catalog-model'; +import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend'; +import { createConditionFactory } from '@backstage/plugin-permission-node'; + +export const isInSystemRule = createCatalogPermissionRule({ + name: 'IS_IN_SYSTEM', + description: 'Checks if an entity is part of the system provided', + resourceType: 'catalog-entity', + apply: (resource: Entity, systemRef: string) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === 'partOf') + .some(relation => relation.targetRef === systemRef); + }, + toQuery: (systemRef: string) => ({ + key: 'relations.partOf', + value: systemRef, + }), +}); + +const isInSystem = createConditionFactory(isInSystemRule); +``` + +For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). + +## Provide the rule during plugin setup + +Now that we have a custom rule defined, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. + +The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. + +```typescript +// packages/backend/src/plugins/catalog.ts + +import { isInSystemRule } from './permission'; + +... + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + builder.addPermissionRules(isInSystem); + ... + return router; +} +``` + +The new rule is now ready for use in a permission policy! + +## Use the rule in a policy + +Let's bring this all together by extending the example policy from the previous section. + +```diff +// packages/backend/src/plugins/permission.ts + ++ import { isInSystem } from './catalog'; + +... + +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { + if (isResourcePermission(request.permission, 'catalog-entity')) { + return createCatalogConditionalDecision( + request.permission, +- catalogConditions.isEntityOwner( +- user?.identity.ownershipEntityRefs ?? [], +- ), ++ { ++ anyOf: [ ++ catalogConditions.isEntityOwner( ++ user?.identity.ownershipEntityRefs ?? [] ++ ), ++ isInSystem('interviewing') ++ ] ++ } + ); + } + + return { result: AuthorizeResult.ALLOW }; + } +``` + +The updated policy will allow catalog entity resource permissions if any of the following are true: + +- User owns the target entity +- Target entity is part of the `'interviewing'` system diff --git a/docs/permission/writing-a-policy.md b/docs/permission/writing-a-policy.md index ecc5104185..d0d7321760 100644 --- a/docs/permission/writing-a-policy.md +++ b/docs/permission/writing-a-policy.md @@ -133,14 +133,6 @@ In this example, we use [`isResourcePermission`](https://backstage.io/docs/refer _Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. -## Custom conditions - -In addition to the conditions provided by the catalog plugin, you can write your own conditions for the catalog (or for any plugin that implements permissions, for that matter). To write a custom condition you must do the following: - -1. Implement a new [`PermissionRule`](https://backstage.io/docs/reference/plugin-permission-node.permissionrule). -2. Register this rule with the plugin backend that owns the resource. New rules are added to the catalog plugin via [`CatalogBuilder#addPermissionRules`](https://github.com/backstage/backstage/blob/1f873a0908d73888892b30faca9a79dfcf908ad1/plugins/catalog-backend/src/service/CatalogBuilder.ts#L344), but each plugin may define its own API for this functionality. -3. Use the [`createConditionFactory`](https://backstage.io/docs/reference/plugin-permission-node.createconditionfactory) helper to integrate with your conditional policy decisions. - ## Conclusion Through a combination of permissions and conditions, you should be able to author a policy that works for your instance of Backstage and your organization. As the ecosystem around permissions in Backstage matures, you may be able to choose from other authorization packages instead of writing your own policy. If you're interested in more detailed descriptions of the concepts that comprise the permission framework, check out the [Concepts page](./concepts.md). diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 51da26c7b5..02f16ffc78 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -266,6 +266,7 @@ "permission/concepts", "permission/getting-started", "permission/writing-a-policy", + "permission/custom-rules", { "type": "subcategory", "label": "Tutorial: using Permissions in your plugin",