From 45349c4a0560a70e239aa16578a1c3ca1d716a68 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 25 Jun 2024 10:57:07 +0200 Subject: [PATCH] Additional Docs (#212) * more docing --------- Signed-off-by: Peter Macdonald --- README.md | 4 +- docs/README.md | 8 +- docs/_sidebar.md | 26 ++- .../docs => docs/assets}/card1.png | Bin .../docs => docs/assets}/card2.png | Bin {img => docs/assets}/opa-policies-plugin.png | Bin docs/index.html | 23 +-- docs/opa-backend/introduction.md | 19 +++ docs/opa-backend/quick-start.md | 69 ++++++++ .../example-entity-checker-policy.md | 6 + docs/opa-entity-checker/introduction.md | 40 +++++ docs/opa-entity-checker/local-development.md | 139 +++++++++++++++ docs/opa-entity-checker/quick-start.md | 159 ++++++++++++++++++ .../catalog-rules.md | 71 ++++++++ .../example-rbac-policy.md | 8 +- .../introduction.md | 51 +++--- .../quick-start.md | 4 + .../scaffolder-rules.md | 59 +++++++ docs/opa-policies/introduction.md | 31 ++++ docs/opa-policies/quick-start.md | 80 +++++++++ example-opa-policies/README.md | 127 -------------- 21 files changed, 743 insertions(+), 181 deletions(-) rename {plugins/backstage-opa-entity-checker/docs => docs/assets}/card1.png (100%) rename {plugins/backstage-opa-entity-checker/docs => docs/assets}/card2.png (100%) rename {img => docs/assets}/opa-policies-plugin.png (100%) create mode 100644 docs/opa-backend/introduction.md create mode 100644 docs/opa-backend/quick-start.md rename example-opa-policies/entity_checker.rego => docs/opa-entity-checker/example-entity-checker-policy.md (87%) create mode 100644 docs/opa-entity-checker/introduction.md create mode 100644 docs/opa-entity-checker/local-development.md create mode 100644 docs/opa-entity-checker/quick-start.md create mode 100644 docs/opa-permissions-wrapper-module/catalog-rules.md rename example-opa-policies/rbac_policy.rego => docs/opa-permissions-wrapper-module/example-rbac-policy.md (89%) create mode 100644 docs/opa-permissions-wrapper-module/scaffolder-rules.md create mode 100644 docs/opa-policies/introduction.md create mode 100644 docs/opa-policies/quick-start.md delete mode 100644 example-opa-policies/README.md diff --git a/README.md b/README.md index 54beb6a..305d7bf 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ This repository contains a collection of plugins for [Backstage](https://backsta ## Policies -- [backstage-opa-policies](https://github.com/Parsifal-M/backstage-opa-policies#hello) - A collection of policies that can be used with the plugins in this repository. +- [backstage-opa-policies](https://github.com/Parsifal-M/backstage-opa-policies#hello) - A collection of policies that can be used with the plugins in this repository. (WIP) ## Additional Documentation -You can find some additional documentation including an architecture overview in the [docs](./docs) folder. +Each Plugin has its own documentation in the [Plugins](./plugins/) Folder, I am however, slowly moving things to [Github pages](https://parsifal-m.github.io/backstage-opa-plugins/#/). Feel free to help out! ## Local Development diff --git a/docs/README.md b/docs/README.md index 52a0e73..e38f537 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,10 +4,10 @@ This repository contains a collection of plugins for [Backstage](https://backsta ## Plugins -- [backstage-opa-backend](./plugins/backstage-opa-backend/README.md) - A Backend Plugin that the [backstage-opa-entity-checker](./plugins/backstage-opa-entity-checker/README.md) consumes to evaluate policies. +- [backstage-opa-backend](../plugins/backstage-opa-backend/README.md) - A Backend Plugin that the [backstage-opa-entity-checker](./plugins/backstage-opa-entity-checker/README.md) consumes to evaluate policies. - [plugin-permission-backend-module-opa-wrapper](/opa-permissions-wrapper-module/introduction.md) - An isolated OPA Client and a Policy Evaluator that integrates with the Backstage permissions framework and uses OPA to evaluate policies, making it possible to use OPA for permissions (like RBAC). -- [backstage-opa-entity-checker](./plugins/backstage-opa-entity-checker/README.md) - A frontend plugin that provides a component card that displays if an entity has the expected entity metadata according to an opa policy. -- [backstage-opa-policies](./plugins/backstage-opa-policies/README.md) - A frontend component designed to be added to entity pages to fetch and display the OPA policy that entity uses based on a URL provided in an annotation in the `catalog-info.yaml` file. +- [backstage-opa-entity-checker](../plugins/backstage-opa-entity-checker/README.md) - A frontend plugin that provides a component card that displays if an entity has the expected entity metadata according to an opa policy. +- [backstage-opa-policies](../plugins/backstage-opa-policies/README.md) - A frontend component designed to be added to entity pages to fetch and display the OPA policy that entity uses based on a URL provided in an annotation in the `catalog-info.yaml` file. ## Policies @@ -15,7 +15,7 @@ This repository contains a collection of plugins for [Backstage](https://backsta ## Additional Documentation -You can find some additional documentation including an architecture overview in the [docs](./docs) folder. +Each plugin also has its own documentation in the README file in the plugin folder. ## Local Development diff --git a/docs/_sidebar.md b/docs/_sidebar.md index be4de4e..9887a0b 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,9 +1,19 @@ - [Home](/) -- [OPA Permissions Wrapper Module](/opa-permissions-wrapper-module/introduction.md) - - [Introduction](/opa-permissions-wrapper-module/introduction.md) - - [Quick Start](/opa-permissions-wrapper-module/quick-start.md) - - [Local Development](/opa-permissions-wrapper-module/local-development.md) - - [Example Inputs and Outputs](/opa-permissions-wrapper-module/inputs-and-outputs.md) - - [Architecture](/opa-permissions-wrapper-module/architecture.md) -- [Contributing](/CONTRIBUTING.md) -- [Adopters](/ADOPTERS.md) +- [OPA Permissions Wrapper Module](opa-permissions-wrapper-module/introduction.md) + - [Quick Start](opa-permissions-wrapper-module/quick-start.md) + - [Example Permissions (RBAC) Policy](opa-permissions-wrapper-module/example-rbac-policy.md) + - [Catalog Rules](opa-permissions-wrapper-module/catalog-rules.md) + - [Scaffolder Rules](opa-permissions-wrapper-module/scaffolder-rules.md) + - [Local Development](opa-permissions-wrapper-module/local-development.md) + - [Example Inputs and Outputs](opa-permissions-wrapper-module/inputs-and-outputs.md) + - [Architecture](opa-permissions-wrapper-module/architecture.md) +- [OPA Backend](opa-backend/introduction.md) + - [Quick Start](opa-backend/quick-start.md) +- [OPA Entity Checker](opa-entity-checker/introduction.md) + - [Quick Start](opa-entity-checker/quick-start.md) + - [Local Development](opa-entity-checker/local-development.md) + - [Example Entity Checker Policy](opa-entity-checker/example-entity-checker-policy.md) +- [OPA Policies](opa-policies/introduction.md) + - [Quick Start](opa-policies/quick-start.md) +- [Contributing](CONTRIBUTING.md) +- [Adopters](ADOPTERS.md) diff --git a/plugins/backstage-opa-entity-checker/docs/card1.png b/docs/assets/card1.png similarity index 100% rename from plugins/backstage-opa-entity-checker/docs/card1.png rename to docs/assets/card1.png diff --git a/plugins/backstage-opa-entity-checker/docs/card2.png b/docs/assets/card2.png similarity index 100% rename from plugins/backstage-opa-entity-checker/docs/card2.png rename to docs/assets/card2.png diff --git a/img/opa-policies-plugin.png b/docs/assets/opa-policies-plugin.png similarity index 100% rename from img/opa-policies-plugin.png rename to docs/assets/opa-policies-plugin.png diff --git a/docs/index.html b/docs/index.html index e841eb1..4182130 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,8 +7,12 @@ name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, shrink-to-fit=no, viewport-fit=cover" /> - - + + + Backstage OPA Plugins Documentation - - - + +
- + diff --git a/docs/opa-backend/introduction.md b/docs/opa-backend/introduction.md new file mode 100644 index 0000000..2322e1e --- /dev/null +++ b/docs/opa-backend/introduction.md @@ -0,0 +1,19 @@ +![NPM Version](https://img.shields.io/npm/v/%40parsifal-m%2Fplugin-opa-backend?logo=npm) ![NPM Downloads](https://img.shields.io/npm/dw/%40parsifal-m%2Fplugin-opa-backend) + +# backstage-opa-backend + +A backend plugin for Backstage, this plugin integrates with the Open Policy Agent (OPA) to facilitate policy evaluation. It's designed to work with the frontend plugins [OPA Entity Checker](../opa-entity-checker/introduction.md) and [OPA Policies](../opa-policies/introduction.md). By itself, this plugin does not provide any user-facing features. + +> Note: This plugin is **NOT** required for the [OPA Permissions Wrapper Module](../opa-permissions-wrapper-module/introduction.md). + +To quickly get started with this plugin, follow the steps below. + +- [Quick-start Guide](./quick-start.md) + +## Contributing + +Contributions are welcome! If you're interested in enhancing this plugin, please fork the repository and submit a PR with your changes. For any questions or discussions, feel free to reach out on [Mastodon](https://hachyderm.io/@parcifal). + +## License + +Licensed under the Apache 2.0 License. diff --git a/docs/opa-backend/quick-start.md b/docs/opa-backend/quick-start.md new file mode 100644 index 0000000..cf771b1 --- /dev/null +++ b/docs/opa-backend/quick-start.md @@ -0,0 +1,69 @@ +# Quick Start + +This guide will help you get started with the OPA Backend module for Backstage. + +## Pre-requisites + +- You have deployed OPA, kindly see how to do that [here](https://www.openpolicyagent.org/docs/latest/deployments/), or see below. + +## Deploying OPA + +There are many ways to deploy OPA, and there is no one size fits all. A good way is to deploy OPA as a sidecar to your Backstage instance. This way, you can ensure that OPA is always available when your Backstage instance is running. + +Here is an example of how you could update your Backstage `k8s` deployment to include OPA, this would be an extension of the `k8s` deployment that you are using for your Backstage instance. + +```yaml +#... Backstage deployment configuration with OPA +spec: + containers: + - name: backstage + image: your-backstage-image + ports: + - name: http + containerPort: 7007 + - name: opa + image: openpolicyagent/opa:0.65.0 # Pin a version of your choice + ports: + - name: http + containerPort: 8181 + args: + - 'run' + - '--server' + - '--log-format=json-pretty' + - '--set=decision_logs.console=true' + - '--ignore=.*' + - '--watch' # Watch for policy changes, this allows updating the policy without restarting OPA + - '/policies' + volumeMounts: + - readOnly: true + name: opa-policy + mountPath: /policies + volumes: + - name: opa-policy + configMap: + name: opa-policy +``` + +## Installing The OPA Backend Plugin + +Run the following command to install the OPA Backend Plugin in your Backstage project. + +```bash +yarn add --cwd packages/backend @parsifal-m/plugin-opa-backend +``` + +Then make the following changes to the `packages/backend/src/index.ts` file in your Backstage project. + +```diff +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +// ..... other plugins ++ backend.add(import('@parsifal-m/plugin-opa-backend')); +``` + +## Recommendations + +I recommend using [Regal: A linter and language server for Rego](https://github.com/StyraInc/regal) to help you write your policies. It provides syntax highlighting, linting, and type checking for Rego files. diff --git a/example-opa-policies/entity_checker.rego b/docs/opa-entity-checker/example-entity-checker-policy.md similarity index 87% rename from example-opa-policies/entity_checker.rego rename to docs/opa-entity-checker/example-entity-checker-policy.md index 76b4a3e..35810ec 100644 --- a/example-opa-policies/entity_checker.rego +++ b/docs/opa-entity-checker/example-entity-checker-policy.md @@ -1,3 +1,8 @@ +# Example Entity Checker Policy + +This is an example policy for the OPA Entity Checker plugin. This policy is used to check if an entity has the correct metadata set. This could be used as a starting point for your own policies. + +```rego package entity_checker import rego.v1 @@ -45,3 +50,4 @@ violation contains {"check_title": entity_check, "message": msg, "level": "error entity_check := "Type" msg := "Incorrect component type!" } +``` diff --git a/docs/opa-entity-checker/introduction.md b/docs/opa-entity-checker/introduction.md new file mode 100644 index 0000000..e07d012 --- /dev/null +++ b/docs/opa-entity-checker/introduction.md @@ -0,0 +1,40 @@ +![NPM Version](https://img.shields.io/npm/v/%40parsifal-m%2Fplugin-opa-entity-checker?logo=npm) ![NPM Downloads](https://img.shields.io/npm/dw/%40parsifal-m%2Fplugin-opa-entity-checker) + +# Keep Your Entity Data In Check With OPA Entity Checker + +Welcome to a smarter way to ensure data quality! The opa-entity-checker plugin, powered by [OPA](https://github.com/open-policy-agent/opa), offers a straightforward solution to validate your entities against custom policies. It's a great tool for maintaining high data standards in your Backstage instance. And keeps teams on their toes to ensure data quality is always good. + +## How It Works + +With opa-entity-checker, you can automatically verify if your entities comply with the policies you've set. It displays a clear, concise card on the entity page, indicating the compliance status: + +- **Compliant Entities:** A clean card signifies everything is in order. + + ![MetaData Card No Violations](../assets/card2.png) + +- **Non-Compliant Entities:** A detailed card highlights what needs attention. + + ![MetaData Card Violations](../assets/card1.png) + +For more details, check out: + +- [Quick-start Guide](/opa-entity-checker/quick-start.md) +- [Local Development Guide](/opa-entity-checker/local-development.md) + +## Community + +This project is part of the Backstage and Open Policy Agent communities. For more information, please visit: + +- [Backstage](https://backstage.io) +- [Open Policy Agent](https://www.openpolicyagent.org) +- [Styra](https://www.styra.com) +- [OPA Slack](https://slack.openpolicyagent.org/) +- [Backstage Discord](https://discord.com/invite/MUpMjP2) + +## Get Involved + +Your contributions can make opa-entity-checker even better. Fork the repository, make your changes, and submit a PR. If you have questions or ideas, reach out on [Mastodon](https://hachyderm.io/@parcifal). + +## License + +This project is licensed under the Apache 2.0 License. diff --git a/docs/opa-entity-checker/local-development.md b/docs/opa-entity-checker/local-development.md new file mode 100644 index 0000000..a606587 --- /dev/null +++ b/docs/opa-entity-checker/local-development.md @@ -0,0 +1,139 @@ +# Using The Plugin In Local Development + +If you are using this plugin and want to know how to use it in local development, you can follow the steps below, note that this is not the only way to do it, but how its done in this repository. + +## Pre-requisites + +- Install the [OPA Backend](../opa-backend/quick-start.md) plugin. +- Install the [OPA Entity Checker](../opa-entity-checker/quick-start.md) plugin. +- This assumes you are using `Postgres` as your database in your `app-config.yaml` file, although this is not mandatory. + +## Installing The OPA Entity Checker Plugin And The OPA Backend Plugin + +Run the following command to install the OPA Entity Checker plugin in your Backstage project. + +```bash +yarn add --cwd packages/app @parsifal-m/plugin-opa-entity-checker && yarn add --cwd packages/backend @parsifal-m/plugin-opa-backend +``` + +Then make the following changes to the `packages/backend/src/index.ts` file in your Backstage project. + +```diff +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +// ..... other plugins ++ backend.add(import('@parsifal-m/plugin-opa-backend')); +``` + +## Add The OPA Entity Checker Plugin To Your Frontend + +Add the following to your `EntityPage.tsx` file: + +```tsx +import { OpaMetadataAnalysisCard } from '@parsifal-m/plugin-opa-entity-checker'; + +//... + +const overviewContent = ( + //... + + + + //... +); +``` + +## Configuration + +The OPA client requires configuration to connect to the OPA server. You need to provide a `baseUrl` and an `entrypoint` for the OPA server in your Backstage app-config.yaml, based on the example above we would have the following configuration: + +```yaml +opaClient: + baseUrl: 'http://localhost:8181' + policies: + entityChecker: # Entity checker plugin + entrypoint: 'entity_checker/violation' +``` + +## Docker Compose + +You can create a `docker-compose.yaml` file in the root of the repository with the following content: + +```yaml +services: + postgres: + image: postgres:15.5-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: devPostgres + ports: + - 5432:5432 + opa: + image: openpolicyagent/opa:0.60.0-static + command: + - 'run' + - '--server' + - '--watch' + - '--log-format=json-pretty' + - '--set=decision_logs.console=true' + - '/policies/entity_checker.rego' + ports: + - 8181:8181 + volumes: + - ./policies:/policies +``` + +Then you'll need to make sure you have a `policies` folder in the root of the repository with the following content: + +```rego +package entity_checker + +import rego.v1 + +# By default we assume the entity is bad :) +default good_entity := false + +# Its a good entity if there are no error violations +good_entity if { + count({v | some v in violation; v.level == "error"}) == 0 +} + +# We check if the entity has a system set +is_system_present if { + input.spec.system +} + +# In each rule we check for certain entity metadata and if it is not present we add a violation +# In this one, we check if the entity has tags set, if it does not we add a warning violation +violation contains {"check_title": entity_check, "message": msg, "level": "warning"} if { + not input.metadata.tags + entity_check := "Tags" + msg := "You do not have any tags set!" +} + +# In this example, we check the lifecycle of the entity and if it is not one of the valid ones we add an error violation +violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + valid_lifecycles = {"production", "development", "experimental"} + not valid_lifecycles[input.spec.lifecycle] + entity_check := "Lifecycle" + msg := "Incorrect lifecycle, should be one of production or development, experimental!" +} + +# Here we check if the entity has a system set, if it does not we add an error violation +violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + not is_system_present + entity_check := "System" + msg := "System is missing!" +} + +# Lastly here, we check if the entity type is one of the valid ones, if it is not we add an error violation +violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + valid_types = {"website", "library", "service"} + not valid_types[input.spec.type] + entity_check := "Type" + msg := "Incorrect component type!" +} +``` diff --git a/docs/opa-entity-checker/quick-start.md b/docs/opa-entity-checker/quick-start.md new file mode 100644 index 0000000..fe41cfc --- /dev/null +++ b/docs/opa-entity-checker/quick-start.md @@ -0,0 +1,159 @@ +# Quick Start + +This guide will help you get started with the OPA Entity Checker module for Backstage. + +## Pre-requisites + +- You have deployed OPA, kindly see how to do that [here](https://www.openpolicyagent.org/docs/latest/deployments/), or see below. +- You have installed the [OPA Backend Plugin](../opa-backend/introduction.md) in your Backstage instance. + +## Deploying OPA + +There are many ways to deploy OPA, and there is no one size fits all. A good way is to deploy OPA as a sidecar to your Backstage instance. This way, you can ensure that OPA is always available when your Backstage instance is running. + +Here is an example of how you could update your Backstage `k8s` deployment to include OPA, this would be an extension of the `k8s` deployment that you are using for your Backstage instance. + +```yaml +#... Backstage deployment configuration with OPA +spec: + containers: + - name: backstage + image: your-backstage-image + ports: + - name: http + containerPort: 7007 + - name: opa + image: openpolicyagent/opa:0.65.0 # Pin a version of your choice + ports: + - name: http + containerPort: 8181 + args: + - 'run' + - '--server' + - '--log-format=json-pretty' + - '--set=decision_logs.console=true' + - '--ignore=.*' + - '--watch' # Watch for policy changes, this allows updating the policy without restarting OPA + - '/policies' + volumeMounts: + - readOnly: true + name: opa-policy + mountPath: /policies + volumes: + - name: opa-policy + configMap: + name: opa-policy +``` + +For simplicity you can then create a policy in a `ConfigMap` and mount it into the OPA container. + +```yaml +# opa-policy.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: opa-policy +data: + entity_checker.rego: | + package entity_checker + + import rego.v1 + + # By default we assume the entity is bad :) + default good_entity := false + + # Its a good entity if there are no error violations + good_entity if { + count({v | some v in violation; v.level == "error"}) == 0 + } + + # We check if the entity has a system set + is_system_present if { + input.spec.system + } + + # In each rule we check for certain entity metadata and if it is not present we add a violation + # In this one, we check if the entity has tags set, if it does not we add a warning violation + violation contains {"check_title": entity_check, "message": msg, "level": "warning"} if { + not input.metadata.tags + entity_check := "Tags" + msg := "You do not have any tags set!" + } + + # In this example, we check the lifecycle of the entity and if it is not one of the valid ones we add an error violation + violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + valid_lifecycles = {"production", "development", "experimental"} + not valid_lifecycles[input.spec.lifecycle] + entity_check := "Lifecycle" + msg := "Incorrect lifecycle, should be one of production, development, or experimental!" + } + + # Here we check if the entity has a system set, if it does not we add an error violation + violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + not is_system_present + entity_check := "System" + msg := "System is missing!" + } + + # Lastly here, we check if the entity type is one of the valid ones, if it is not we add an error violation + violation contains {"check_title": entity_check, "message": msg, "level": "error"} if { + valid_types = {"website", "library", "service"} + not valid_types[input.spec.type] + entity_check := "Type" + msg := "Incorrect component type!" + } +``` + +## Installing The OPA Entity Checker Plugin And The OPA Backend Plugin + +Run the following command to install the OPA Entity Checker plugin in your Backstage project. + +```bash +yarn add --cwd packages/app @parsifal-m/plugin-opa-entity-checker && yarn add --cwd packages/backend @parsifal-m/plugin-opa-backend +``` + +Then make the following changes to the `packages/backend/src/index.ts` file in your Backstage project. + +```diff +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +// ..... other plugins ++ backend.add(import('@parsifal-m/plugin-opa-backend')); +``` + +## Add The OPA Entity Checker Plugin To Your Frontend + +Add the following to your `EntityPage.tsx` file: + +```tsx +import { OpaMetadataAnalysisCard } from '@parsifal-m/plugin-opa-entity-checker'; + +//... + +const overviewContent = ( + //... + + + + //... +); +``` + +## Configuration + +The OPA client requires configuration to connect to the OPA server. You need to provide a `baseUrl` and an `entrypoint` for the OPA server in your Backstage app-config.yaml, based on the example above we would have the following configuration: + +```yaml +opaClient: + baseUrl: 'http://localhost:8181' + policies: + entityChecker: # Entity checker plugin + entrypoint: 'entity_checker/violation' +``` + +## Recommendations + +I recommend using [Regal: A linter and language server for Rego](https://github.com/StyraInc/regal) to help you write your policies. It provides syntax highlighting, linting, and type checking for Rego files. diff --git a/docs/opa-permissions-wrapper-module/catalog-rules.md b/docs/opa-permissions-wrapper-module/catalog-rules.md new file mode 100644 index 0000000..4dc4a13 --- /dev/null +++ b/docs/opa-permissions-wrapper-module/catalog-rules.md @@ -0,0 +1,71 @@ +# Catalog Rules + +Here are some helpful rules that can be used in the catalog to build conditional rules and some examples of how they can be used. Keep in mind you can also construct your own rules using the documentation found [here](https://backstage.io/docs/permissions/custom-rules) and use them in the same way below. + +## HAS_ANNOTATION + +This rule checks if a given annotation exists on a given entity. + +```rego +# Conditional based on annotations + +decision := conditional("catalog", "catalog-entity", {"anyOf": [{ + "resourceType": "catalog-entity", + "rule": "HAS_ANNOTATION", + "params": {"annotation": "your-annotation", "value": "your-value"}, +}]}) if { + permission == "catalog.entity.read" +} +``` + +## HAS_LABEL + +This rule checks if a given label exists on a given entity. + +```rego +# Conditional based on labels + +decision := conditional("catalog", "catalog-entity", {"anyOf": [{ + "resourceType": "catalog-entity", + "rule": "HAS_LABEL", + "params": {"label": "your-label"}, +}]}) if { + permission == "catalog.entity.read" +} +``` + +## IS_ENTITY_OWNER + +This rule checks if the user is the owner of the entity. + +```rego +# Conditional based on claims (groups a user belongs to) + +decision := conditional("catalog", "catalog-entity", {"anyOf": [{ + "resourceType": "catalog-entity", + "rule": "IS_ENTITY_OWNER", + "params": {"claims": claims}, +}]}) if { + permission == "catalog.entity.delete" +} +``` + +## IS_ENTITY_KIND + +This rule checks if the entity is of a given kind. (e.g. API, Component, Template, Group, etc.) + +```rego +# Allow all users to read API and Component entities + +decision := conditional("catalog", "catalog-entity", {"anyOf": [{ + "resourceType": "catalog-entity", + "rule": "IS_ENTITY_KIND", + "params": {"kinds": ["API", "Component"]}, +}]}) if { + permission == "catalog.entity.read" +} +``` + +## Want To Add More Examples? + +Please feel free to contribute to this documentation by submitting a PR with your examples. We would love to see how you are using these rules in your Backstage instance! diff --git a/example-opa-policies/rbac_policy.rego b/docs/opa-permissions-wrapper-module/example-rbac-policy.md similarity index 89% rename from example-opa-policies/rbac_policy.rego rename to docs/opa-permissions-wrapper-module/example-rbac-policy.md index b4ea0cb..c401df9 100644 --- a/example-opa-policies/rbac_policy.rego +++ b/docs/opa-permissions-wrapper-module/example-rbac-policy.md @@ -1,3 +1,8 @@ +# Example Permissions (RBAC) Policy + +The below rego is an example of what a policy might look like for your Backstage application. You could use this as a starting point and modify it to fit your needs! + +```rego package rbac_policy import rego.v1 @@ -20,7 +25,7 @@ permission := input.permission.name claims := input.identity.claims # An example of setting the is_admin flag based on the claims -is_admin if "[:][/]" in claims +is_admin if "kind:namespace:name" in claims # Catalog Permission: Allow users to only delete entities they claim ownership of. # Allow admins to delete any entity regardless of ownership. @@ -68,3 +73,4 @@ decision := conditional("scaffolder", "scaffolder-action", {"not": {"anyOf": [{ permission == "scaffolder.action.execute" not is_admin } +``` diff --git a/docs/opa-permissions-wrapper-module/introduction.md b/docs/opa-permissions-wrapper-module/introduction.md index d86d3ad..1381816 100644 --- a/docs/opa-permissions-wrapper-module/introduction.md +++ b/docs/opa-permissions-wrapper-module/introduction.md @@ -1,49 +1,42 @@ ![NPM Version](https://img.shields.io/npm/v/%40parsifal-m%2Fplugin-permission-backend-module-opa-wrapper) ![NPM Downloads](https://img.shields.io/npm/dw/%40parsifal-m%2Fplugin-permission-backend-module-opa-wrapper) -# OPA Permissions Wrapper Module for Backstage +# Simplify Permissions with OPA in Backstage -This project is an [Open Policy Agent (OPA)](https://github.com/open-policy-agent/opa) wrapper for the [Backstage Permission Framework](https://backstage.io/docs/permissions/overview). +Integrate dynamic policy management into your Backstage instance with the OPA Permissions Wrapper Module. This tool leverages [Open Policy Agent (OPA)](https://github.com/open-policy-agent/opa) for flexible, easy-to-update permissions management within the [Backstage Permission Framework](https://backstage.io/docs/permissions/overview). -- Instead of coding policies directly into your Backstage instance with TypeScript, create, edit and manage your policies with OPA! +- **Dynamic Policy Management:** Use OPA's [Rego language](https://www.openpolicyagent.org/docs/latest/policy-language/) for creating and managing policies without hardcoding them. +- **Instant Updates:** Modify your OPA policies on the fly without needing to redeploy your Backstage instance. +- **Empower Teams:** Allow teams to manage their own policies easily, without deep knowledge of TypeScript or Backstage internals. -- Manage your policies in a more flexible way, you can use OPA's [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) language to write your policies. +For more details, check out: -- No need to redeploy your Backstage instance to update policies, simply update your OPA policies and you are good to go! - -- Enable teams to manage their own policies, without needing to know TypeScript or the Backstage codebase! - -See the following pages for more information: - -- [OPA Permissions Quickstart](/opa-permissions-wrapper-module/quick-start.md) -- [OPA Permissions Local Development](/opa-permissions-wrapper-module/local-development.md) +- [Quick-start Guide](/opa-permissions-wrapper-module/quick-start.md) +- [Local Development Guide](/opa-permissions-wrapper-module/local-development.md) ## How It Works -This plugin wraps around the Backstage Permission Framework and uses the OPA client to evaluate policies. It will send a request to OPA with the permission and identity information, OPA will then evaluate the policy and return a decision, which is then passed back to the Permission Framework. +The module enhances the Backstage Permission Framework by integrating with OPA for policy evaluation. It simplifies the process of permission checks: -- Permissions are created in the plugin in which they need to be enforced. -- The plugin will send a request to the Permission Framework backend with the permission and identity information. -- The Permission Framework backend will then forward the request to OPA with the permission and identity information. -- OPA will evaluate the the information against the policy and return a decision. +1. Define permissions within the plugin. +2. The plugin sends permission and identity information to the Permission Framework backend. +3. The backend forwards this information to OPA. +4. OPA evaluates the request against your policies and returns a decision. -## Community +## Join The Community -This project is part of the Backstage and Open Policy Agent communities. For more information, please visit: +This project is a part of the broader Backstage and Open Policy Agent ecosystems. Explore more about these communities: -- [Backstage](https://backstage.io) -- [Open Policy Agent](https://www.openpolicyagent.org) +- [Backstage Community](https://backstage.io) +- [Open Policy Agent Community](https://www.openpolicyagent.org) - [Styra](https://www.styra.com) -- [OPA Slack](https://slack.openpolicyagent.org/) +- [Join OPA on Slack](https://slack.openpolicyagent.org/) - [Backstage Discord](https://discord.com/invite/MUpMjP2) -## Blog Posts - -- [Going Backstage with OPA](https://www.styra.com/blog/going-backstage-with-opa/) - -## Talks +### Learn More -- [Can It Be Done? Building Fine-Grained Access Control for Backstage with OPA](https://www.youtube.com/watch?v=N0n_czYo_kE&list=PLj6h78yzYM2P4KPyeDFexAVm6ZvfAWMU8&index=15&ab_channel=CNCF%5BCloudNativeComputingFoundation%5D) +- [Blog: Going Backstage with OPA](https://www.styra.com/blog/going-backstage-with-opa/) +- [Talk: Building Fine-Grained Access Control for Backstage with OPA](https://www.youtube.com/watch?v=N0n_czYo_kE&list=PLj6h78yzYM2P4KPyeDFexAVm6ZvfAWMU8&index=15&ab_channel=CNCF%5BCloudNativeComputingFoundation%5D) ## License -This project is released under the Apache 2.0 License. +Licensed under the Apache 2.0 License. diff --git a/docs/opa-permissions-wrapper-module/quick-start.md b/docs/opa-permissions-wrapper-module/quick-start.md index cfc3d39..787ce45 100644 --- a/docs/opa-permissions-wrapper-module/quick-start.md +++ b/docs/opa-permissions-wrapper-module/quick-start.md @@ -122,3 +122,7 @@ opaClient: ``` The `baseUrl` is the URL of the OPA server, and the `entrypoint` is the entrypoint of the policy you want to evaluate. + +## Recommendations + +I recommend using [Regal: A linter and language server for Rego](https://github.com/StyraInc/regal) to help you write your policies. It provides syntax highlighting, linting, and type checking for Rego files. diff --git a/docs/opa-permissions-wrapper-module/scaffolder-rules.md b/docs/opa-permissions-wrapper-module/scaffolder-rules.md new file mode 100644 index 0000000..9bc0244 --- /dev/null +++ b/docs/opa-permissions-wrapper-module/scaffolder-rules.md @@ -0,0 +1,59 @@ +# Scaffolder Rules + +Here are some helpful rules that can be used in the scaffolder to build conditional rules and some examples of how they can be used. Keep in mind you can also construct your own rules using the documentation found [here](https://backstage.io/docs/permissions/custom-rules) and use them in the same way below. + +## HAS_TAG + +Prevent non-admin users from being able to see a template parameter based on a tag. + +```rego +# Conditional based on scaffolder template tags unless they are an admin +decision := conditional("scaffolder", "scaffolder-template", {"not": { + "anyOf": [{ + "resourceType": "scaffolder-template", + "rule": "HAS_TAG", + "params": {"tag": "admin"}, + }] +}}) if { + permission == "scaffolder.template.parameter.read" + not is_admin +} +``` + +## HAS_ACTION_ID + +Prevent non-admin users from being able to trigger/execute certain actions based on the action ID, in this case debug:log. + +```rego +decision := conditional("scaffolder", "scaffolder-action", {"not": { + "anyOf": [{ + "resourceType": "scaffolder-action", + "rule": "HAS_ACTION_ID", + "params": {"actionId": "debug:log"}, + }] +}}) if { + permission == "scaffolder.action.execute" + not is_admin +} +``` + +## HAS_PROPERTY + +Has property can also be `HAS_BOOLEAN_PROPERTY`, `HAS_NUMBER_PROPERTY`, `HAS_STRING_PROPERTY`. This rule prevents actions with the specified property. In this case, non-admin users cannot read templates with the `admin` property. + +```rego +decision := conditional("scaffolder", "scaffolder-action", {"not": { + "anyOf": [{ + "resourceType": "scaffolder-action", + "rule": "HAS_PROPERTY", # OR HAS_BOOLEAN_PROPERTY, HAS_NUMBER_PROPERTY, HAS_STRING_PROPERTY + "params": {"property": "admin"}, + }] +}}) if { + permission == "scaffolder.template.parameter.read" + not is_admin +} +``` + +## Want To Add More Examples? + +Please feel free to contribute to this documentation by submitting a PR with your examples. We would love to see how you are using these rules in your Backstage instance! diff --git a/docs/opa-policies/introduction.md b/docs/opa-policies/introduction.md new file mode 100644 index 0000000..51bb546 --- /dev/null +++ b/docs/opa-policies/introduction.md @@ -0,0 +1,31 @@ +![NPM Version](https://img.shields.io/npm/v/%40parsifal-m%2Fplugin-opa-policies) ![NPM Downloads](https://img.shields.io/npm/dw/%40parsifal-m%2Fplugin-opa-policies) + +# OPA Policies Plugin Overview + +The OPA Policies Plugin is designed to enhance visibility and understanding of the policies applied to entities within the Backstage catalog. By fetching and displaying the specific OPA Policy associated with an entity, this plugin makes it straightforward for users to see at a glance which policies are in effect for any given entity. + +This functionality is particularly useful for teams looking to maintain compliance and governance standards across their services, as it provides a clear, accessible view of policy application directly on the entity page in Backstage. + +![OPA Policy](../assets/opa-policies-plugin.png) + +To quickly get started with this plugin, follow the steps below. + +- [Quick-start Guide](./quick-start.md) + +## Community + +This project is part of the Backstage and Open Policy Agent communities. For more information, please visit: + +- [Backstage](https://backstage.io) +- [Open Policy Agent](https://www.openpolicyagent.org) +- [Styra](https://www.styra.com) +- [OPA Slack](https://slack.openpolicyagent.org/) +- [Backstage Discord](https://discord.com/invite/MUpMjP2) + +## Get Involved + +Your contributions can make opa-entity-checker even better. Fork the repository, make your changes, and submit a PR. If you have questions or ideas, reach out on [Mastodon](https://hachyderm.io/@parcifal). + +## License + +This project is licensed under the Apache 2.0 License. diff --git a/docs/opa-policies/quick-start.md b/docs/opa-policies/quick-start.md new file mode 100644 index 0000000..3858249 --- /dev/null +++ b/docs/opa-policies/quick-start.md @@ -0,0 +1,80 @@ +# Quick Start + +This guide will help you get started with the OPA Policies plugin for Backstage. + +## Pre-requisites + +- You have installed the [OPA Backend Plugin](../opa-backend/introduction.md) in your Backstage instance. + +## Installing The OPA Policies Plugin + +Run the following command to install the OPA Backend Plugin in your Backstage project. + +```bash +yarn add --cwd packages/backend @parsifal-m/plugin-opa-backend && yarn add --cwd packages/app @parsifal-m/plugin-opa-policies +``` + +Then make the following changes to the `packages/backend/src/index.ts` file in your Backstage project. + +```diff +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +// ..... other plugins ++ backend.add(import('@parsifal-m/plugin-opa-backend')); +``` + +## Add The OPA Policies Plugin To Your Frontend + +You can then add it to your entity pages in `packages/app/src/components/catalog/EntityPage.tsx`: + +```tsx +import { + OpaPolicyPage, + isOpaPoliciesEnabled, +} from '@parsifal-m/plugin-opa-policies'; + +const websiteEntityPage = ( + + + {overviewContent} + + // Other routes... + + + + + {techdocsContent} + + +); +``` + +> Note: Using `isOpaPoliciesEnabled` will then only display the OPA Policy tab if the entity has the annotation set. + +## Configuration + +You need to add the following annotation to the entity you want to display the OPA Policy for: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-testing-grounds + description: An example of a Backstage application. + annotations: + # Add the OPA Policy URL here + open-policy-agent/policy: https://github.com/Parsifal-M/backstage-testing-grounds/blob/main/rbac.rego +spec: + type: website + owner: john@example.com + lifecycle: experimental +``` + +You need to provide the full URL to the OPA Policy file as above in order for the plugin to fetch and display it. diff --git a/example-opa-policies/README.md b/example-opa-policies/README.md deleted file mode 100644 index 7c90790..0000000 --- a/example-opa-policies/README.md +++ /dev/null @@ -1,127 +0,0 @@ -## This Directory Contains Example OPA Policies - -You could use these policies as a starting point for your own policies. The policies are written in Rego, the policy language used by OPA. - -## Catalog Rules - -Here are some helpful rules that can be used in the catalog to build conditional rules and some examples of how they can be used. Keep in mind you can also construct your own rules using the documentation found [here](https://backstage.io/docs/permissions/custom-rules) and use them in the same way below. - -### HAS_ANNOTATION - -This rule checks if a given annotation exists on a given entity. - -```rego -# Conditional based on annotations - -decision := conditional("catalog", "catalog-entity", {"anyOf": [{ - "resourceType": "catalog-entity", - "rule": "HAS_ANNOTATION", - "params": {"annotation": "your-annotation", "value": "your-value"}, -}]}) if { - permission == "catalog.entity.read" -} -``` - -### HAS_LABEL - -This rule checks if a given label exists on a given entity. - -```rego -# Conditional based on labels - -decision := conditional("catalog", "catalog-entity", {"anyOf": [{ - "resourceType": "catalog-entity", - "rule": "HAS_LABEL", - "params": {"label": "your-label"}, -}]}) if { - permission == "catalog.entity.read" -} -``` - -### IS_ENTITY_OWNER - -This rule checks if the user is the owner of the entity. - -```rego -# Conditional based on claims (groups a user belongs to) - -decision := conditional("catalog", "catalog-entity", {"anyOf": [{ - "resourceType": "catalog-entity", - "rule": "IS_ENTITY_OWNER", - "params": {"claims": claims}, -}]}) if { - permission == "catalog.entity.delete" -} -``` - -### IS_ENTITY_KIND - -This rule checks if the entity is of a given kind. (e.g. API, Component, Template, Group, etc.) - -```rego -# Allow all users to read API and Component entities - -decision := conditional("catalog", "catalog-entity", {"anyOf": [{ - "resourceType": "catalog-entity", - "rule": "IS_ENTITY_KIND", - "params": {"kinds": ["API", "Component"]}, -}]}) if { - permission == "catalog.entity.read" -} -``` - -## Scaffolder Rules - -Similarly, permissions can be set for the scaffolder. Here are some examples of how to use the rules in the scaffolder. - -### HAS_TAG - -Prevent non-admin users from being able to see a template parameter based on a tag. - -```rego -# Conditional based on scaffolder template tags unless they are an admin -decision := conditional("scaffolder", "scaffolder-template", {"not": { - "anyOf": [{ - "resourceType": "scaffolder-template", - "rule": "HAS_TAG", - "params": {"tag": "admin"}, - }] -}}) if { - permission == "scaffolder.template.parameter.read" - not is_admin -} -``` - -### HAS_ACTION_ID - -Prevent non-admin users from being able to trigger/execute certain actions based on the action ID, in this case debug:log. - -```rego -decision := conditional("scaffolder", "scaffolder-action", {"not": { - "anyOf": [{ - "resourceType": "scaffolder-action", - "rule": "HAS_ACTION_ID", - "params": {"actionId": "debug:log"}, - }] -}}) if { - permission == "scaffolder.action.execute" - not is_admin -} -``` - -### HAS_PROPERTY - -Has property can also be `HAS_BOOLEAN_PROPERTY`, `HAS_NUMBER_PROPERTY`, `HAS_STRING_PROPERTY`. This rule prevents actions with the specified property. In this case, non-admin users cannot read templates with the `admin` property. - -```rego -decision := conditional("scaffolder", "scaffolder-action", {"not": { - "anyOf": [{ - "resourceType": "scaffolder-action", - "rule": "HAS_PROPERTY", # OR HAS_BOOLEAN_PROPERTY, HAS_NUMBER_PROPERTY, HAS_STRING_PROPERTY - "params": {"property": "admin"}, - }] -}}) if { - permission == "scaffolder.template.parameter.read" - not is_admin -} -```