Merge branch 'master' into HACKATHON_SB_1/scaffolder_docs

Signed-off-by: Steven Billington <39339008+green2jello@users.noreply.github.com>
This commit is contained in:
Steven Billington
2024-05-31 14:28:44 -04:00
committed by GitHub
1452 changed files with 56584 additions and 20495 deletions
+4 -1
View File
@@ -47,7 +47,10 @@ If you want to use the Lighthouse CLI and run the checks based on the config you
yarn dlx @lhci/cli@0.11.x autorun
```
> Note: running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed.
:::note Note
Running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed.
:::
### Use Lighthouse Github Action on your own repo
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

+8 -4
View File
@@ -4,10 +4,14 @@ title: Contributing New Providers
description: Documentation on adding new authentication providers
---
> NOTE: The primary audience for this documentation are contributors to the main
> Backstage project that want to add support for new authentication providers.
> While you can follow it to implement your own custom providers it is much
> more advanced than using our built-in providers.
:::note Note
The primary audience for this documentation are contributors to the main
Backstage project that want to add support for new authentication providers.
While you can follow it to implement your own custom providers it is much
more advanced than using our built-in providers.
:::
## How Does Authentication Work?
+22
View File
@@ -47,6 +47,12 @@ auth:
clientId: ${AUTH_ATLASSIAN_CLIENT_ID}
clientSecret: ${AUTH_ATLASSIAN_CLIENT_SECRET}
scope: ${AUTH_ATLASSIAN_SCOPES}
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: usernameMatchingUserEntityName
```
The Atlassian provider is a structure with three configuration keys:
@@ -57,6 +63,22 @@ The Atlassian provider is a structure with three configuration keys:
**NOTE:** the scopes `offline_access`, `read:jira-work`, and `read:jira-user` are provided by default.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `atlassianAuthApi` reference and
+1 -2
View File
@@ -170,6 +170,5 @@ backend.add(customAuth);
The body of the sign-in resolver is up to you to write! The example code above
is just a copy of what `emailMatchingUserEntityProfileEmail` does. The `info`
parameter contains all of the results of the sign-in attempt so far. The `ctx`
context [has several useful
functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
for issuing tokens in various ways.
+25 -1
View File
@@ -41,9 +41,14 @@ auth:
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
## uncomment if using GitHub Enterprise
# enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
signIn:
resolvers:
# Matches the GitHub username with the Backstage user entity name.
# See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers.
- resolver: usernameMatchingUserEntityName
```
The GitHub provider is a structure with three configuration keys:
The GitHub provider is a structure with these configuration keys:
- `clientId`: The client ID that you generated on GitHub, e.g.
`b59241722e3c3b4816e2`
@@ -54,6 +59,25 @@ The GitHub provider is a structure with three configuration keys:
initiating an OAuth flow, e.g.
`https://your-intermediate-service.com/handler`. Only needed if Backstage is
not the immediate receiver (e.g. one OAuth app for many backstage instances).
- `signIn`: The configuration for the sign-in process, including the **resolvers**
that should be used to match the user from the auth provider with the user
entity in the Backstage catalog (typically a single resolver is sufficient).
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
:::note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
+22
View File
@@ -43,6 +43,12 @@ auth:
# audience: https://gitlab.company.com
## uncomment if using a custom redirect URI
# callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: usernameMatchingUserEntityName
```
The GitLab provider is a structure with three configuration keys:
@@ -56,6 +62,22 @@ The GitLab provider is a structure with three configuration keys:
`https://$backstage.acme.corp/api/auth/gitlab/handler/frame`
Note: Due to a peculiarity with GitLab OAuth, ensure there is no trailing `/` after 'frame' in the URL.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `gitlabAuthApi` reference and
+46 -3
View File
@@ -27,6 +27,12 @@ auth:
gcp-iap:
audience: '/projects/<project number>/global/backendServices/<backend service id>'
jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: emailMatchingUserEntityAnnotation
```
The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following:
@@ -36,11 +42,48 @@ The full `audience` value can be obtained by visiting your [Identity-Aware Proxy
This config section must be in place for the provider to load at all. Now let's
add the provider itself.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Changes
This provider is not enabled by default in the auth backend code, because
besides the config section above, it also needs to be given one or more
callbacks in actual code as well as described below.
There is a module for this provider that you will need to add to your backend.
First you'll want to run this command to add the module:
```sh
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider
```
Then you will need to add this to your backend:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider'));
/* highlight-add-end */
```
### Legacy Backend Changes
If you are still using the legacy backend you will need to make the changes outlined here.
This provider is not enabled by default in the auth backend code, because besides the config section above, it also needs to be given one or more callbacks in actual code as well as described below.
Add a `providerFactories` entry to the router in
`packages/backend/src/plugins/auth.ts`.
+22
View File
@@ -42,6 +42,12 @@ auth:
development:
clientId: ${AUTH_GOOGLE_CLIENT_ID}
clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: emailMatchingUserEntityAnnotation
```
The Google provider is a structure with two configuration keys:
@@ -50,6 +56,22 @@ The Google provider is a structure with two configuration keys:
`10023341500512-beui241gjwwkrdkr2eh7dprewj2pp1q.apps.googleusercontent.com`
- `clientSecret`: The client secret tied to the generated client ID.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `googleAuthApi` reference and
+5 -10
View File
@@ -5,8 +5,7 @@ description: An introduction to Backstage user identities and sign-in resolvers
---
:::info
This documentation is written for [the new backend
system](../backend-system/index.md) which is the default since Backstage
This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./identity-resolver--old.md)
instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
@@ -31,15 +30,13 @@ testing purposes and quickly getting started locally, but is not safe for use in
production and that particular provider will refuse to work there.
Because of this, one of the early things you want to do when standing up your
Backstage instance is to choose a production ready auth provider. See [the auth
overview page](./index.md) for a full list of providers and how to install and
Backstage instance is to choose a production ready auth provider. See [the auth overview page](./index.md) for a full list of providers and how to install and
configure them.
## Backstage User Identity
A user identity within Backstage is built up from two main pieces of
information: a user [entity
reference](../features/software-catalog/references.md), and a set of ownership
information: a user [entity reference](../features/software-catalog/references.md), and a set of ownership
references. When a user signs in, a Backstage token is generated which is then
used to identify the user within the Backstage ecosystem.
@@ -194,8 +191,7 @@ backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
backend.add(customAuth);
```
Check out [the naming patterns
article](../backend-system/architecture/07-naming-patterns.md) for what rules
Check out [the naming patterns article](../backend-system/architecture/07-naming-patterns.md) for what rules
apply regarding how to form valid IDs. In this example we also put the module
declaration directly in `packages/backend/src/index.ts` but that's just for
simplicity. You can place it anywhere you like, including in other packages, and
@@ -244,8 +240,7 @@ async signInResolver(info, ctx) {
If you throw an error in the sign in resolver function, the sign in attempt is
immediately rejected, and the error details are presented in the user interface.
The `ctx` context [has several useful
functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
The `ctx` context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
for issuing tokens in various ways.
### Custom Ownership Resolution
+16 -8
View File
@@ -10,12 +10,16 @@ configure Backstage to have any number of authentication providers, but only
one of these will typically be used for sign-in, with the rest being used to provide
access to external resources.
> NOTE: Identity management and the Sign-In page in Backstage is NOT a method for blocking
> access for unauthorized users. The identity system only serves to provide a personalized
> experience and access to a Backstage Identity Token, which can be passed to backend plugins.
> This also means that your Backstage backend APIs are by default unauthenticated.
> Thus, if your Backstage instance is exposed to the Internet, anyone can access
> information in the Backstage. You can learn more [here](../overview/threat-model.md#integrator-responsibilities).
:::note Note
Identity management and the Sign-In page in Backstage is NOT a method for blocking
access for unauthorized users. The identity system only serves to provide a personalized
experience and access to a Backstage Identity Token, which can be passed to backend plugins.
This also means that your Backstage backend APIs are by default unauthenticated.
Thus, if your Backstage instance is exposed to the Internet, anyone can access
information in the Backstage. You can learn more [here](../overview/threat-model.md#integrator-responsibilities).
:::
## Built-in Authentication Providers
@@ -141,8 +145,12 @@ const app = createApp({
});
```
> NOTE: You can configure sign-in to use a redirect flow with no pop-up by adding
> `enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`
:::note Note
You can configure sign-in to use a redirect flow with no pop-up by adding
`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml`
:::
## Sign-In with Proxy Providers
+23 -2
View File
@@ -57,6 +57,12 @@ auth:
domainHint: ${AZURE_TENANT_ID}
additionalScopes:
- Mail.Send
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: emailMatchingUserEntityAnnotation
```
The Microsoft provider is a structure with three mandatory configuration keys:
@@ -70,6 +76,22 @@ The Microsoft provider is a structure with three mandatory configuration keys:
For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy)
- `additionalScopes` (optional): List of scopes for the App Registration. The default and mandatory value is ['user.read'].
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `microsoft.com/email` annotation matches. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `microsoftAuthApiRef` reference and
@@ -85,6 +107,5 @@ hosts:
- `login.microsoftonline.com`, to get and exchange authorization codes and access
tokens
- `graph.microsoft.com`, to fetch user profile information (as seen
in [this source
code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)).
in [this source code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)).
If this host is unreachable, users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in.
+17 -31
View File
@@ -22,44 +22,30 @@ The provider configuration can be added to your `app-config.yaml` under the root
auth:
environment: development
providers:
oauth2Proxy: {}
oauth2Proxy:
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: forwardedUserMatchingUserEntityName
```
Right now no configuration options are supported, but the empty object is needed
to enable the provider in the auth backend.
### Resolvers
To use the `oauth2Proxy` provider you must also configure it with a sign-in resolver.
For more information about the sign-in process in general, see the
[Sign-in Identities and Resolvers](../identity-resolver.md) documentation.
This provider includes several resolvers out of the box that you can use:
For the `oauth2Proxy` provider, the sign-in result is quite different than other providers.
Because it's a proxy provider that can be configured to forward information through
arbitrary headers, the auth result simply just gives you access to the HTTP headers
of the incoming request. Using these you can either extract the information directly,
or grab ID or access tokens to look up additional information and/or validate the request.
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `forwardedUserMatchingUserEntityName`: Matches the value in the `x-forwarded-user` header from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
A simple sign-in resolver might for example look like this:
:::note Note
```ts
providerFactories: {
...defaultAuthProviderFactories,
oauth2Proxy: providers.oauth2Proxy.create({
signIn: {
async resolver({ result }, ctx) {
const name = result.getHeader('x-forwarded-user');
if (!name) {
throw new Error('Request did not contain a user')
}
return ctx.signInWithCatalogUser({
entityRef: { name },
});
},
},
}),
},
```
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
[An example on how to sign a user in without a matching user](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/auth.ts)
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
+23 -1
View File
@@ -29,7 +29,7 @@ To add Okta authentication, you must create an Application from Okta:
The configuration examples provided above are suitable for local development. For a production deployment, substitute `http://localhost:7007` with the url that your Backstage instance is available at.
# Configuration
## Configuration
The provider configuration can then be added to your `app-config.yaml` under the
root `auth` configuration:
@@ -47,6 +47,12 @@ auth:
idp: ${AUTH_OKTA_IDP} # Optional
# https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned
additionalScopes: ${AUTH_OKTA_ADDITIONAL_SCOPES} # Optional
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: emailMatchingUserEntityAnnotation
```
The values referenced are found on the Application page on your Okta site.
@@ -61,6 +67,22 @@ The values referenced are found on the Application page on your Okta site.
`additionalScopes` is an optional value, a string of space separated scopes, that will be combined with the default `scope` value of `openid profile email offline_access` to adjust the `scope` sent to Okta during OAuth. This will have an impact on [the dependent claims returned](https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned). For example, setting the `additionalScopes` value to `groups` will result in the claim returning a list of the groups that the user is a member of that also match the ID token group filter of the client app.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `okta.com/email` annotation matches. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `oktaAuthApi` reference and
+52 -4
View File
@@ -6,8 +6,7 @@ description: This section describes service to service authentication works, bot
---
:::info
This documentation is written for [the new backend
system](../backend-system/index.md) which is the default since Backstage
This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./service-to-service-auth--old.md)
instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
@@ -81,6 +80,51 @@ header:
Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW
```
## JWKS Token Auth
This access method allows for external caller token authentication using configured
JSON Web Key Sets (JWKS). This is useful for callers that are authenticating to our
instance of Backstage with third-party tools, such as Auth0.
You can configure this access method by adding one or more entries of type `jwks`
to the `backend.auth.externalAccess` app-config key:
```yaml title="in e.g. app-config.production.yaml"
backend:
auth:
externalAccess:
- type: jwks
options:
url: https://example.com/.well-known/jwks.json
issuer: https://example.com
algorithm: RS256
audience: example, other-example
subjectPrefix: custom-prefix
- type: jwks
options:
url: https://another-example.com/.well-known/jwks.json
issuer: https://example.com
```
The URL should point at an unauthenticated endpoint that returns the JWKS.
`issuer` specifies the issuer(s) of the JWT that the authenticating app will accept.
Passed JWTs must have an `iss` claim which matches one of the specified issuers.
`algorithm` specifies the algorithm(s) that are used to verify the JWT. The passed JWTs
must have been signed using one of the listed algorithms.
`audience` specifies the intended audience(s) of the JWT. The passed JWTs must have an "aud"
claim that matches one of the audiences specified, or have no audience specified.
For additional details regarding the JWKS configuration, please consult your authentication
provider's documentation.
The subject returned from the token verification will become part of the
credentials object that the request recipient plugins get. All subjects will have the prefix
`external:`, but you can also provide a custom subjectPrefix which will get appended before the
subject returned from your JWKS service (ex. `external:custom-prefix:sub`).
## Legacy Tokens
Plugins and backends that are _not_ on the new backend system use a legacy token
@@ -157,8 +201,12 @@ payload:
- `sub`: the exact string "backstage-server"
- `exp`: one hour from the time it was generated, in epoch seconds
> NOTE: The JWT must encode the `alg` header as a protected header, such as with
> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
:::note Note
The JWT must encode the `alg` header as a protected header, such as with
[setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
:::
The caller then passes along the JWT token with requests in the `Authorization`
header:
+28 -14
View File
@@ -12,14 +12,11 @@ Cloud Console and within a Backstage app required to enable this capability.
## Create an OAuth App in the VMware Cloud Console
1. Log in to the [VMware Cloud Console](https://console.cloud.vmware.com).
1. Navigate to [Identity & Access Management > OAuth
Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps)
and click the [Owned
Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps/owned-apps/view)
1. Navigate to [Identity & Access Management > OAuth Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps)
and click the [Owned Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps/owned-apps/view)
tab -- if you are not an Organization Owner or Administrator but only a
Member, you will not see this nav entry unless the **Developer** check box is
selected for your role (see the [Organization roles and
permissions](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-C11D3AAC-267C-4F16-A0E3-3EDF286EBE53.html#organization-roles-and-permissions-0)
selected for your role (see the [Organization roles and permissions](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-C11D3AAC-267C-4F16-A0E3-3EDF286EBE53.html#organization-roles-and-permissions-0)
docs for details).
1. Click **Create App**, choose 'Web/Mobile app' and click **Continue**.
1. Use default settings except:
@@ -43,7 +40,7 @@ Cloud Console and within a Backstage app required to enable this capability.
Apps using the [new backend system](../../backend-system/index.md),
can enable the VMware Cloud provider with a small modification like:
```ts title="packages/backend-next/src/index.ts"
```ts title="packages/backend/src/index.ts"
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
@@ -107,10 +104,6 @@ export default async function createPlugin(
In the above, `commonSignInResolvers.emailLocalPartMatchingUserEntityName()`
can be replaced with a more suitable resolver for the app in question.
## Configure Sign-in Resolution
See [Sign-in Identities and Resolvers](../identity-resolver.md) for details.
## Add to Sign-in Page
See the [Sign-In Configuration](../index.md#sign-in-configuration) docs for
@@ -156,11 +149,16 @@ auth:
development:
clientId: ${APP_ID}
organizationId: ${ORG_ID}
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: vmwareCloudSignInResolvers
```
where `APP_ID` refers to the ID retrieved when creating the OAuth App, and
`ORG_ID` is the [long ID of the
Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1)
Where `APP_ID` refers to the ID retrieved when creating the OAuth App, and
`ORG_ID` is the [long ID of the Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1)
in VMware Cloud for which you wish to enable sign-in.
Note that VMware Cloud requires OAuth Apps to use
@@ -169,3 +167,19 @@ library used by this provider requires the use of Express session middleware to
do this. Therefore the value `your session secret` under `auth.session.secret`
should be replaced with a long, complex and unique string which will act as a
key for signing session cookies set by Backstage.
### Resolvers
This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`.
- `vmwareCloudSignInResolvers`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will sign in the user without associating with a catalog user.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
@@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`.
## Root Scoped Services
If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factor for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it.
@@ -157,7 +157,7 @@ export const fooServiceFactory = createServiceFactory({
});
```
Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
Whatever value is returned by the `createRootContext` function will be shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
## Default Service Factories
@@ -78,4 +78,4 @@ Plugins must always be designed to be horizontally scalable. This means that you
### Isolated
Plugins must never communicate with each other directly through code, they may only communicate over the network. Plugins that wish to expose an external interface for other plugins and modules to use are recommended to do so through a [node-library](../../local-dev/cli-build-system.md#package-roles) package. The library should export an API client service to make calls to your plugin, or similar construct.
Plugins must never communicate with each other directly through code, they may only communicate over the network. Plugins that wish to expose an external interface for other plugins and modules to use are recommended to do so through a [node-library](../../tooling/cli/02-build-system.md#package-roles) package. The library should export an API client service to make calls to your plugin, or similar construct.
@@ -6,8 +6,12 @@ sidebar_label: Overview
description: Building backends using the new backend system
---
> NOTE: If you have an existing backend that is not yet using the new backend
> system, see [migrating](./08-migrating.md).
:::note Note
If you have an existing backend that is not yet using the new backend
system, see [migrating](./08-migrating.md).
:::
This section covers how to set up and customize your own Backstage backend. It covers some aspects of how backend instances fit into the larger system, but for a more in-depth explanation of the role of backends in the backend system, see [the architecture section](../architecture/02-backends.md).
@@ -201,10 +201,14 @@ const legacyPlugin = makeLegacyPlugin(
After this, your backend will know how to instantiate your thing on demand and
place it in the legacy plugin environment.
> NOTE: If you happen to be dealing with a service ref that does NOT have a
> default implementation, but rather has a separate service factory, then you
> will also need to import that factory and pass it to the `services` array
> argument of `createBackend`.
:::note Note
If you happen to be dealing with a service ref that does NOT have a
default implementation, but rather has a separate service factory, then you
will also need to import that factory and pass it to the `services` array
argument of `createBackend`.
:::
## Cleaning Up the Plugins Folder
@@ -216,10 +220,14 @@ maintained by the Backstage maintainers, you may find that they have already
been migrated to the new backend system. This section describes some specific
such migrations you can make.
> NOTE: For each of these, note that your backend still needs to have a
> dependency (e.g. in `packages/backend/package.json`) to those plugin packages,
> and they still need to be configured properly in your app-config. Those
> mechanisms still work just the same as they used to in the old backend system.
:::note Note
For each of these, note that your backend still needs to have a
dependency (e.g. in `packages/backend/package.json`) to those plugin packages,
and they still need to be configured properly in your app-config. Those
mechanisms still work just the same as they used to in the old backend system.
:::
### The App Plugin
@@ -452,7 +460,7 @@ catalog:
/* highlight-add-end */
```
To migrate `GithubMultiOrgEntityProvider` and `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`.
To migrate `GithubMultiOrgEntityProvider` or `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
@@ -461,20 +469,79 @@ backend.add(import('@backstage/plugin-catalog-backend-module-github-org'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Github configuration in `app-config.yaml` remains the same.
##### GithubOrgEntityProvider
If you were using `GithubOrgEntityProvider` you might have been configured in code like this:
```ts title="packages/backend/src/plugins/catalog.ts"
// The org URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
```
This now needs to be set via configuration. The options defined above are now set in `app-config.yaml` instead as shown below:
```yaml title="app-config.yaml"
catalog:
/* highlight-add-start */
providers:
githubOrg:
yourProviderId:
# ...
/* highlight-add-start */
- id: production
githubUrl: 'https://github.com',
orgs: ['backstage']
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
timeout: PT15M
/* highlight-add-end */
```
##### GithubMultiOrgEntityProvider
If you were using `GithubMultiOrgEntityProvider` you might have been configured in code like this:
```ts title="packages/backend/src/plugins/catalog.ts"
// The GitHub URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubMultiOrgEntityProvider.fromConfig(env.config, {
id: 'production',
githubUrl: 'https://github.com',
// Set the following to list the GitHub orgs you wish to ingest from. You can
// also omit this option to ingest all orgs accessible by your GitHub integration
orgs: ['org-a', 'org-b'],
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
```
This now needs to be set via configuration. The options defined above are now set in `app-config.yaml` instead as shown below:
```yaml title="app-config.yaml"
catalog:
/* highlight-add-start */
providers:
githubOrg:
- id: production
githubUrl: 'https://github.com',
orgs: ['org-a', 'org-b'],
schedule:
frequency: PT30M
timeout: PT15M
/* highlight-add-end */
```
If you were providing transformers, these can be configured by extending `githubOrgEntityProviderTransformsExtensionPoint`
@@ -816,12 +883,16 @@ auth:
tenantId: ${AZURE_TENANT_ID}
signIn:
resolvers:
- resolver: emailMatchingUserEntityAnnotation
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: emailMatchingUserEntityAnnotation
```
> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
#### Auth Plugin Modules and Their Resolvers
@@ -848,7 +919,7 @@ Additional resolvers:
- [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts#L33C16-L33C46)
##### GCP IAM
##### GCP IAP (Google Identity-Aware Proxy)
Setup:
@@ -1078,7 +1149,11 @@ backend.add(import('@backstage/plugin-search-backend/alpha'));
/* highlight-add-end */
```
> Note: this will use the Lunr search engine which stores its index in memory
:::note Note
This will use the Lunr search engine which stores its index in memory.
:::
#### Search Engines
@@ -1167,7 +1242,11 @@ backend.add(
/* highlight-add-end */
```
> Note: The above example includes a default allow-all policy. If that is not what you want, do not add the second line and instead investigate one of the options below.
:::note Note
The above example includes a default allow-all policy. If that is not what you want, do not add the second line and instead investigate one of the options below.
:::
#### Custom Permission Policy
@@ -1290,7 +1369,7 @@ The vast majority of the backend plugins that currently live in the Backstage Re
| @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) |
| @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) |
| @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) |
| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
| @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) |
| @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) |
| @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) |
@@ -73,8 +73,7 @@ items.
Backend modules are used to extend [plugins](../architecture/04-plugins.md) or other modules with
additional features or change existing behavior. They must always be installed
in the same backend instance as the plugin or module that they extend, and may only extend a single plugin and modules from that plugin at a time.
Modules interact with their target plugin or module using the [extension
points](../architecture/05-extension-points.md) registered by the plugin, while also being
Modules interact with their target plugin or module using the [extension points](../architecture/05-extension-points.md) registered by the plugin, while also being
able to depend on the [services](../architecture/03-services.md) of the target plugin.
That last point is worth reiterating: injected `plugin` scoped services will be
the exact
@@ -157,8 +156,7 @@ the database. They will run on the same logical database instance as the target
plugin, so care must be taken to choose table names that do not risk colliding
with those of the plugin. A recommended naming pattern is `<package
name>__<table name>`, for example the `@backstage/backend-tasks` package creates
tables named `backstage_backend_tasks__<table>`. If you use the default [`Knex`
migration facilities](https://knexjs.org/guide/migrations.html), you will also
tables named `backstage_backend_tasks__<table>`. If you use the default [`Knex` migration facilities](https://knexjs.org/guide/migrations.html), you will also
want to make sure that it uses similarly prefixed migration state tables for its
internal bookkeeping needs, so they do not collide with the main ones used by
the plugin itself. You can do this as follows:
@@ -179,8 +177,7 @@ There are several ways of configuring and customizing plugins and modules.
Whenever you want to allow modules to configure your plugin dynamically, for
example in the way that the catalog backend lets catalog modules inject
additional entity providers, you can use the extension points mechanism. This is
described in detail with code examples in [the extension points architecture
article](../architecture/05-extension-points.md), while the following is a more
described in detail with code examples in [the extension points architecture article](../architecture/05-extension-points.md), while the following is a more
slim example of how to implement an extension point for a plugin:
```ts
@@ -249,7 +246,5 @@ export const examplePlugin = createBackendPlugin({
});
```
Before adding custom configuration options, make sure to read [the configuration
docs](../../conf/index.md), in particular the section on [defining configuration
for your own plugins](../../conf/defining.md) which explains how to establish a
Before adding custom configuration options, make sure to read [the configuration docs](../../conf/index.md), in particular the section on [defining configuration for your own plugins](../../conf/defining.md) which explains how to establish a
configuration schema for your specific plugin.
@@ -21,8 +21,7 @@ collective term for backend [plugins](../architecture/04-plugins.md) and
The function returns an HTTP server instance which can be used together with
e.g. `supertest` to easily test the actual REST service surfaces of plugins who
register routes with [the HTTP router service
API](../core-services/01-index.md).
register routes with [the HTTP router service API](../core-services/01-index.md).
```ts
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
@@ -214,3 +214,44 @@ The above module can then be installed by the integrator alongside the kubernete
backend.add(import('@backstage/plugin-kubernetes-backend'));
backend.add(import('@internal/gke-cluster-supplier'));
```
### Dev Server
Follow the steps below to run your migrated plugin on a local development server:
1. First, delete the `src/run.ts` and `src/service/standaloneServer.ts` files in case they exist (the `backstage-cli` previously used these files to run legacy backend plugins locally, but they are no longer required).
2. Next, create a new development backend in the `dev/index.ts` file. The dev server is a lite version of a backend app that is mainly used to run your plugin locally, so a simple `kubernetes` backend local development server would look like this:
```ts title="in dev/index.js"
// This package should be installed as a `dev` dependency
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// Path to the file where the plugin is export as default
backend.add(import('../src'));
backend.start();
```
The development server created above will be automatically configured with the default dependency factories, but if you need to mock some of the services your plugin relies on, such as the `rootConfig` service, you can use one of the `mockServices` factories:
```ts title="in dev/index.js"
//...
// This package should be installed as `devDependecies`
import { mockServices } from '@backstage/backend-test-utils';
const backend = createBackend();
// ...
backend.add(
mockServices.rootConfig.factory({
data: {
// your config mocked values goes here
},
}),
);
// ...
```
Checkout the [custom service implementations](https://backstage.io/docs/backend-system/building-backends/index#custom-service-implementations) documentation and also the [core service configurations](https://backstage.io/docs/backend-system/core-services/index) page in case you'd like to create your own custom mock factory for one or more services.
3. Now you can finally start your plugin locally by running `yarn start` from the root folder of your plugin.
+1 -1
View File
@@ -10,4 +10,4 @@ description: The Backend System
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
You can find an example backend setup in [the `backend` package](https://github.com/backstage/backstage/tree/master/packages/backend).
+1 -1
View File
@@ -145,7 +145,7 @@ from `@backstage/core-plugin-api`.
In the old backend system plugins, the configuration is passed in via options from the main
backend package. See for example
[packages/backend/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23).
[packages/backend-legacy/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23).
### New Backend System
+17 -9
View File
@@ -101,7 +101,7 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml"]
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
file works, see the
[`backend:bundle` command docs](../local-dev/cli-commands.md#backendbundle).
[`backend:bundle` command docs](../tooling/cli/03-commands.md#backendbundle).
The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be
executed with the root of the repo as the build context, in order to get access
@@ -141,8 +141,12 @@ browser at `http://localhost:7007`
## Multi-stage Build
> NOTE: The `.dockerignore` is different in this setup, read on for more
> details.
:::note Note
The `.dockerignore` is different in this setup, read on for more
details.
:::
This section describes how to set up a multi-stage Docker build that builds the
entire project within Docker. This is typically slower than a host build, but is
@@ -293,10 +297,14 @@ browser at `http://localhost:7007`
## Separate Frontend
> NOTE: This is an optional step, and you will lose out on the features of the
> `@backstage/plugin-app-backend` plugin. Most notably the frontend configuration
> will no longer be injected by the backend, you will instead need to use the
> correct configuration when building the frontend bundle.
:::note Note
This is an optional step, and you will lose out on the features of the
`@backstage/plugin-app-backend` plugin. Most notably the frontend configuration
will no longer be injected by the backend, you will instead need to use the
correct configuration when building the frontend bundle.
:::
It is sometimes desirable to serve the frontend separately from the backend,
either from a separate image or for example a static file serving provider. The
@@ -345,8 +353,8 @@ The `Dockerfile` mentioned above located in `packages/backend` is maintained by
### Minimal Hardened Image
A contributed `Dockerfile` exists within the directory of `contrib/docker/minimal-harded-image` which uses the [`wolfi-base`](https://github.com/wolfi-dev) image to reduce vulnerabilities. When this was contributed, this alternative `Dockerfile` reduced 98.2% of vulnerabilities in the built Backstage docker image when compared with the image built from `packages/backend/Dockerfile`.
A contributed `Dockerfile` exists within the directory of `contrib/docker/minimal-hardened-image` which uses the [`wolfi-base`](https://github.com/wolfi-dev) image to reduce vulnerabilities. When this was contributed, this alternative `Dockerfile` reduced 98.2% of vulnerabilities in the built Backstage docker image when compared with the image built from `packages/backend/Dockerfile`.
To reduce maintenance, the digest of the image has been removed from the `contrib/docker/minimal-harded-image/Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. Please do a docker pull on the image to get the latest digest. Using the digest allows tools such as Dependabot or Renovate to know exactly which image digest is being utilized and allows for Pull Requests to be triggered when a new digest is available.
To reduce maintenance, the digest of the image has been removed from the `contrib/docker/minimal-hardened-image/Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. Please do a docker pull on the image to get the latest digest. Using the digest allows tools such as Dependabot or Renovate to know exactly which image digest is being utilized and allows for Pull Requests to be triggered when a new digest is available.
It is suggested to setup Dependabot/Renovate or a similar tool to ensure the image is kept up to date so that vulnerability fixes that have been addressed are pulled in frequently.
+6 -2
View File
@@ -13,8 +13,12 @@ This documentation shows common examples that may be useful when deploying
Backstage for the first time, or for those without established deployment
practices.
> Note: The _easiest_ way to explore Backstage is to visit the
> [live demo site](https://demo.backstage.io).
:::note Note
The _easiest_ way to explore Backstage is to visit the
[live demo site](https://demo.backstage.io).
:::
At Spotify, we deploy software generally by:
+8 -4
View File
@@ -107,10 +107,14 @@ $ echo -n "backstage" | base64
YmFja3N0YWdl
```
> Note: Secrets are base64-encoded, but not encrypted. Be sure to enable
> [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
> for the cluster. For storing secrets in Git, consider
> [SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git).
:::note Note
Secrets are base64-encoded, but not encrypted. Be sure to enable
[Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
for the cluster. For storing secrets in Git, consider
[SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git).
:::
The secrets can now be applied to the Kubernetes cluster:
+1 -1
View File
@@ -37,7 +37,7 @@ more, read our blog post,
Yes, we've already started releasing open source versions of some of the plugins
we use here, and we'll continue to do so.
[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of
[Plugins](technical.md#what-is-a-plugin-in-backstage) are the building blocks of
functionality in Backstage. We have over 120 plugins inside Spotify — many of
those are specialized for our use, so will remain internal and proprietary to
us. But we estimate that about a third of our existing plugins make good open
+1 -1
View File
@@ -154,7 +154,7 @@ maintains Backstage in your own environment.
For more information, see our
[Owners](https://github.com/backstage/backstage/blob/master/OWNERS.md) and
[Governance](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md).
[Governance](https://github.com/backstage/community/blob/main/GOVERNANCE.md).
### Does Spotify provide a managed version of Backstage?
+3 -6
View File
@@ -198,8 +198,7 @@ in namespace `NAMESPACE` and it has adequate
[permissions](#role-based-access-control), here are some sample procedures to
procure a long-lived service account token for use with this provider:
- On versions of Kubernetes [prior to
1.24](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade-1),
- On versions of Kubernetes [prior to 1.24](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade-1),
you could get an (automatically-generated) token for a service account with:
```sh
@@ -209,8 +208,7 @@ procure a long-lived service account token for use with this provider:
| base64 --decode
```
- For Kubernetes 1.24+, as described in [this
guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets),
- For Kubernetes 1.24+, as described in [this guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets),
you can obtain a long-lived token by creating a secret:
```sh
@@ -235,8 +233,7 @@ procure a long-lived service account token for use with this provider:
If a cluster has `authProvider: serviceAccount` and the `serviceAccountToken`
field is omitted, Backstage will ignore the configured URL and certificate data,
instead attempting to access the Kubernetes API via an in-cluster client as in
[this
example](https://github.com/kubernetes-client/javascript/blob/master/examples/in-cluster.js).
[this example](https://github.com/kubernetes-client/javascript/blob/master/examples/in-cluster.js).
##### `clusters.\*.oidcTokenProvider` (optional)
+5 -1
View File
@@ -236,7 +236,11 @@ backend.add(kubernetesModuleCustomClusterDiscovery);
backend.start();
```
> Note: this example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery)
:::note Note
This example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery)
:::
## Configuration
+6 -14
View File
@@ -7,12 +7,10 @@ description: Interacting with the Kubernetes API in Backstage plugins
[Contributors](https://backstage.io/docs/overview/glossary#backstage-user-profiles) wanting to
create developer portal experiences based on data from Kubernetes (e.g. for
interacting with [Custom
Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
interacting with [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
beyond the default behaviors of the existing Kubernetes plugin) can leverage the
Kubernetes backend plugin's proxy endpoint to allow them to make arbitrary
requests to the [REST
API](https://kubernetes.io/docs/reference/using-api/api-concepts/).
requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/).
Here is a snippet fetching namespaces using the `KubernetesBackendClient` library
@@ -31,8 +29,7 @@ await kubernetesApi.proxy(CLUSTER_NAME, '/api/v1/namespaces');
The proxy will interpret the
[`Backstage-Kubernetes-Cluster`](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster)
header as the name of the cluster to target. This name will be compared to each cluster
returned by all the configured [cluster
locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods)
returned by all the configured [cluster locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods)
-- the first cluster whose [`name` field](https://backstage.io/docs/features/kubernetes/configuration#clustersname) matches
the value in the header will be targeted.
@@ -48,12 +45,10 @@ The proxy expects a `KubernetesAuthTranslator` to be provided that is used to de
## Authentication
The proxy has no provisions for mTLS, so it cannot be used to connect to
clusters using the [x509 Client
Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs)
clusters using the [x509 Client Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs)
authentication strategy.\
The current `/proxy` Implementation expects a
[Bearer
token](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request)
[Bearer token](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request)
to be provided as a `Backstage-Kubernetes-Authorization` header for a target cluster. This token will be used as the `Authorization` header when forwarding a request to a target cluster.
## How to disable the proxy endpoint via PermissionPolicy
@@ -64,8 +59,6 @@ This feature assumes your backstage instance has enabled the [permissions framew
A sample policy like:
[packages/backend/src/plugins/permissions.ts](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/permission.ts)
```typescript
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
@@ -106,8 +99,7 @@ even if a valid ID token was attached that a cluster would authorize.
## Other known limitations
The proxy as it was released in [Backstage
1.9](https://github.com/backstage/backstage/blob/master/docs/releases/v1.9.0-changelog.md#patch-changes-15)
The proxy as it was released in [Backstage 1.9](../../releases/v1.9.0-changelog.md#patch-changes-15)
has a known bug:
- [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot
+1 -1
View File
@@ -393,7 +393,7 @@ There are other more specific search results layout components that also accept
Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up.
In "packages/backend-next/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
```ts
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';
+8 -4
View File
@@ -34,10 +34,14 @@ const searchEngine = new LunrSearchEngine({ logger: env.logger });
const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine });
```
> Note: Lunr is appropriate as a zero-config search engine when developing
> other parts of Backstage locally, however its use is highly discouraged when
> running Backstage in production. When deploying Backstage, use one of the
> other search engines instead.
:::note Note
Lunr is appropriate as a zero-config search engine when developing
other parts of Backstage locally, however its use is highly discouraged when
running Backstage in production. When deploying Backstage, use one of the
other search engines instead.
:::
## Postgres
@@ -95,7 +95,11 @@ const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
<Route path="/catalog" element={<CatalogIndexPage columns={myColumnsFunc} />} />
```
> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
:::note Note
The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
:::
## Customize Actions
@@ -162,7 +166,11 @@ const customActions: TableProps<CatalogTableRow>['actions'] = [
<Route path="/catalog" element={<CatalogIndexPage actions={customActions} />} />
```
> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
:::note Note
The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
:::
The above customization will override the existing actions. Currently the only way to keep them and add your own is to also include the existing actions in your array by copying them from the [`defaultActions`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L113-L168).
@@ -400,7 +408,11 @@ export const CustomCatalogPage = () => {
The above is a very basic version of a fully custom `CatalogIndexPage`, you'll want to explore the various props to see what you can all do with them. This was built off the building blocks seen in the [`DefaultCatalogPage`](https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx)
> Note: The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
:::note Note
The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
:::
To use this custom `CatalogIndexPage` which we called `CustomCatalogPage`, you'll need to make the following change:
@@ -0,0 +1,604 @@
---
id: extending-the-model--old
title: Extending the model
# prettier-ignore
description: Documentation on extending the catalog model
---
The Backstage catalog [entity data model](descriptor-format.md) is based on the
[Kubernetes objects format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/),
and borrows a lot of its semantics as well. This page describes those semantics
at a higher level and how to extend them to fit your organization.
Backstage comes with a number of catalog concepts out of the box:
- There are a number of builtin versioned _kinds_, such as `Component`, `User`
etc. These encapsulate the high level concept of an entity, and define the
schema for its entity definition data.
- An entity has both a _metadata_ object and a _spec_ object at the root.
- Each kind may or may not have a _type_. For example, there are several well
known types of component, such as `service` and `website`. These clarify the
more detailed nature of the entity, and may affect what features are exposed
in the interface.
- Entities may have a number of _[annotations](well-known-annotations.md)_ on
them. These can be added either by humans into the descriptor files, or added
by automated processes when the entity is ingested into the catalog.
- Entities may have a number of _labels_ on them.
- Entities may have a number of _relations_, expressing how they relate to each
other in different ways.
We'll list different possibilities for extending this below.
## Adding a New apiVersion of an Existing Kind
Example intents:
> "I want to evolve this core kind, tweaking the semantics a bit so I will bump
> the apiVersion a step"
> "This core kind is a decent fit but we want to evolve it at will so we'll move
> it to our own company's apiVersion space and use that instead of
> `backstage.io`."
The `backstage.io` apiVersion space is reserved for use by the Backstage
maintainers. Please do not change or add versions within that space.
If you add an [apiVersion](descriptor-format.md#apiversion-and-kind-required)
space of your own, you are effectively branching out from the underlying kind
and making your own. An entity kind is identified by the apiVersion + kind pair,
so even though the resulting entity may be similar to the core one, there will
be no guarantees that plugins will be able to parse or understand its data. See
below about adding a new kind.
## Adding a New Kind
Example intents:
> "The kinds that come with the package are lacking. I want to model this other
> thing that is a poor fit for either of the builtins."
> "This core kind is a decent fit but we want to evolve it at will so we'll move
> it to our own company's apiVersion space and use that instead of
> `backstage.io`."
A [kind](descriptor-format.md#apiversion-and-kind-required) is an overarching
family, or an idea if you will, of entities that also share a schema. Backstage
comes with a number of builtin ones that we believe are useful for a large
variety of needs that one may want to model in Backstage. The primary ambition
is to map things to these kinds, but sometimes you may want or need to extend
beyond them.
Introducing a new apiVersion is basically the same as adding a new kind. Bear in
mind that most plugins will be compiled against the builtin
`@backstage/catalog-model` package and have expectations that kinds align with
that.
The catalog backend itself, from a storage and API standpoint, does not care
about the kind of entities it stores. Extending with new kinds is mainly a
matter of permitting them to pass validation when building the backend catalog
using the `CatalogBuilder`, and then to make plugins be able to understand the
new kind.
For the consuming side, it's a different story. Adding a kind has a very large
impact. The very foundation of Backstage is to attach behavior and views and
functionality to entities that we ascribe some meaning to. There will be many
places where code checks `if (kind === 'X')` for some hard coded `X`, and casts
it to a concrete type that it imported from a package such as
`@backstage/catalog-model`.
If you want to model something that doesn't feel like a fit for either of the
builtin kinds, feel free to reach out to the Backstage maintainers to discuss
how to best proceed.
If you end up adding that new kind, you must namespace its `apiVersion`
accordingly with a prefix that makes sense, typically based on your organization
name - e.g. `my-company.net/v1`. Also do pick a new `kind` identifier that does
not collide with the builtin kinds.
## Adding a New Type of an Existing Kind
Example intents:
> "This is clearly a component, but it's of a type that doesn't quite fit with
> the ones I've seen before."
> "We don't call our teams "team", can't we put "flock" as the group type?"
Some entity kinds have a `type` field in its spec. This is where an organization
are free to express the variety of entities within a kind. This field is
expected to follow some taxonomy that makes sense for yourself. The chosen value
may affect what operations and views are enabled in Backstage for that entity.
Inside Spotify our model has grown significantly over the years, and our
component types now include ML models, apps, data pipelines and many more.
It might be tempting to put software that doesn't fit into any of the existing
types into an Other catch-all type. There are a few reasons why we advise
against this; firstly, we have found that it is preferred to match the
conceptual model that your engineers have when describing your software.
Secondly, Backstage helps your engineers manage their software by integrating
the infrastructure tooling through plugins. Different plugins are used for
managing different types of components.
For example, the
[Lighthouse plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse)
only makes sense for Websites. The more specific you can be in how you model
your software, the easier it is to provide plugins that are contextual.
Adding a new type takes relatively little effort and carries little risk. Any
type value is accepted by the catalog backend, but plugins may have to be
updated if you want particular behaviors attached to that new type.
## Changing the Validation Rules for The Entity Envelope or Metadata Fields
Example intents:
> "We want to import our old catalog but the default set of allowed characters
> for a metadata.name are too strict."
> "I want to change the rules for annotations so that I'm allowed to store any
> data in annotation values, not just strings."
After pieces of raw entity data have been read from a location, they are passed
through a field format validation step. This ensures that the types and syntax
of the base envelope and metadata make sense - in short, things that aren't
entity-kind-specific. Some or all of these validators can be replaced when
building the backend using the catalog's dedicated `catalogModelExtensionPoint`
(or directly on the `CatalogBuilder` if you are still using the old backend
system).
The risk and impact of this type of extension varies, based on what it is that
you want to do. For example, extending the valid character set for kinds,
namespaces and names can be fairly harmless, with a few notable exceptions -
there is code that expects these to never ever contain a colon or slash, for
example, and introducing URL-unsafe characters risks breaking plugins that
aren't careful about encoding arguments. Supporting non-strings in annotations
may be possible but has not yet been tried out in the real world - there is
likely to be some level of plugin breakage that can be hard to predict.
You must also be careful about not making the rules _more strict_ than they used
to be after populating the catalog with data. This risks making previously valid
entities start having processing errors and fail to update.
Before making this kind of extension, we recommend that you contact the
Backstage maintainers or a support partner to discuss your use case.
This is an example of relaxing the format rules of the `metadata.name` field:
```ts
import { createBackend } from '@backstage/backend-defaults';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
const myCatalogCustomizations = createBackendModule({
pluginId: 'catalog',
moduleId: 'catalog-customization',
register(reg) {
reg.registerInit({
deps: {
catalogModel: catalogModelExtensionPoint,
},
async init({ catalogModel }) {
catalogModel.setFieldValidators({
// This is only one of many methods that you can pass into
// setFieldValidators; your editor of choice should help you
// find the others. The length checks and regexp inside are
// just examples and can be adjusted as needed, but take care
// to test your changes thoroughly to ensure that you get
// them right.
isValidEntityName(value) {
return (
typeof value === 'string' &&
value.length >= 1 &&
value.length <= 63 &&
/^[A-Za-z0-9@+_.-]+$/.test(value)
);
},
});
},
});
},
});
const backend = createBackend();
// ... add other backend features and the catalog backend itself here ...
backend.add(myCatalogCustomizations);
backend.start();
```
## Changing the Validation Rules for Core Entity Fields
Example intent:
> "I don't like that the owner is mandatory. I'd like it to be optional."
After reading and policy-checked entity data from a location, it is sent through
the processor chain looking for processors that implement the
`validateEntityKind` step, to see that the data is of a known kind and abides by
its schema. There is a builtin processor that implements this for all known core
kinds and matches the data against their fixed validation schema. This processor
can be replaced when building the backend catalog using the `CatalogBuilder`,
with a processor of your own that validates the data differently.
This replacement processor must have a name that matches the builtin processor, `BuiltinKindsEntityProcessor`.
This type of extension is high risk, and may have high impact across the
ecosystem depending on the type of change that is made. It is therefore not
recommended in normal cases. There will be a large number of plugins and
processors - and even the core itself - that make assumptions about the shape of
the data and import the typescript data type from the `@backstage/catalog-model`
package.
## Adding New Fields to the Metadata Object
Example intent:
> "Our entities have this auxiliary property that I would like to express for
> several entity kinds and it doesn't really fit as a spec field."
The metadata object is currently left open for extension. Any unknown fields
found in the metadata will just be stored verbatim in the catalog. However we
want to caution against extending the metadata excessively. Firstly, you run the
risk of colliding with future extensions to the model. Secondly, it is common
that this type of extension lives more comfortably elsewhere - primarily in the
metadata labels or annotations, but sometimes you even may want to make a new
component type or similar instead.
There are some situations where metadata can be the right place. If you feel
that you have run into such a case and that it would apply to others, do feel
free to contact the Backstage maintainers or a support partner to discuss your
use case. Maybe we can extend the core model to benefit both you and others.
## Adding New Fields to the Spec Object of an Existing Kind
Example intent:
> "The builtin Component kind is fine but we want to add an additional field to
> the spec for describing whether it's in prod or staging."
A kind's schema validation typically doesn't forbid "unknown" fields in an
entity `spec`, and the catalog will happily store whatever is in it. So doing
this will usually work from the catalog's point of view.
Adding fields like this is subject to the same risks as mentioned about metadata
extensions above. Firstly, you run the risk of colliding with future extensions
to the model. Secondly, it is common that this type of extension lives more
comfortably elsewhere - primarily in the metadata labels or annotations, but
sometimes you even may want to make a new component type or similar instead.
There are some situations where the spec can be the right place. If you feel
that you have run into such a case and that it would apply to others, do feel
free to contact the Backstage maintainers or a support partner to discuss your
use case. Maybe we can extend the core model to benefit both you and others.
## Adding a New Annotation
Example intents:
> "Our custom made build system has the concept of a named pipeline-set, and we
> want to associate individual components with their corresponding pipeline-sets
> so we can show their build status."
> "We have an alerting system that automatically monitors service health, and
> there's this integration key that binds the service to an alerts pool. We want
> to be able to show the ongoing alerts for our services in Backstage so it'd be
> nice to attach that integration key to the entity somehow."
Annotations are mainly intended to be consumed by plugins, for feature detection
or linking into external systems. Sometimes they are added by humans, but often
they are automatically generated at ingestion time by processors. There is a set
of [well-known annotations](well-known-annotations.md), but you are free to add
additional ones. This carries no risk or impact to other systems as long as you
abide by the following naming rules.
- The `backstage.io` annotation prefix is reserved for use by the Backstage
maintainers. Reach out to us if you feel that you would like to make an
addition to that prefix.
- Annotations that pertain to a well known third party system should ideally be
prefixed with a domain, in a way that makes sense to a reader and connects it
clearly to the system (or the maker of the system). For example, you might use
a `pagerduty.com` prefix for pagerduty related annotations, but maybe not
`ldap.com` for LDAP annotations since it's not directly affiliated with or
owned by an LDAP foundation/company/similar.
- Annotations that have no prefix at all, are considered local to your Backstage
instance and can be used freely as such, but you should not make use of them
outside of your organization. For example, if you were to open source a plugin
that generates or consumes annotations, then those annotations must be
properly prefixed with your company domain or a domain that pertains to the
annotation at hand.
## Adding a New Label
Example intents:
> "Our process reaping system wants to periodically scrape for components that
> have a certain property."
> "It'd be nice if our service owners could just tag their components somehow to
> let the CD system know to automatically generate SRV records or not for that
> service."
Labels are mainly intended to be used for filtering of entities, by external
systems that want to find entities that have some certain property. This is
sometimes used for feature detection / selection. An example could be to add a
label `deployments.my-company.net/register-srv: "true"`.
At the time of writing this, the use of labels is very limited and we are still
settling together with the community on how to best use them. If you feel that
your use case fits the labels best, we would appreciate if you let the Backstage
maintainers know.
You are free to add labels. This carries no risk or impact to other systems as
long as you abide by the following naming rules.
- The `backstage.io` label prefix is reserved for use by the Backstage
maintainers. Reach out to us if you feel that you would like to make an
addition to that prefix.
- Labels that pertain to a well known third party system should ideally be
prefixed with a domain, in a way that makes sense to a reader and connects it
clearly to the system (or the maker of the system). For example, you might use
a `pagerduty.com` prefix for pagerduty related labels, but maybe not
`ldap.com` for LDAP labels since it's not directly affiliated with or owned by
an LDAP foundation/company/similar.
- Labels that have no prefix at all, are considered local to your Backstage
instance and can be used freely as such, but you should not make use of them
outside of your organization. For example, if you were to open source a plugin
that generates or consumes labels, then those labels must be properly prefixed
with your company domain or a domain that pertains to the label at hand.
## Adding a New Relation Type
Example intents:
> "We have this concept of service maintainership, separate from ownership, that
> we would like to make relations to individual users for."
> "We feel that we want to explicitly model the team-to-global-department
> mapping as a relation, because it is core to our org setup and we frequently
> query for it."
Any processor can emit relations for entities as they are being processed, and
new processors can be added when building the backend catalog using the
`CatalogBuilder`. They can emit relations based on the entity data itself, or
based on information gathered from elsewhere. Relations are directed and go from
a source entity to a target entity. They are also tied to the entity that
originated them - the one that was subject to processing when the relation was
emitted. Relations may be dangling (referencing something that does not actually
exist by that name in the catalog), and callers need to be aware of that.
There is a set of [well-known relations](well-known-relations.md), but you are
free to emit your own as well. You cannot change the fact that they are directed
and have a source and target that have to be an
[entity reference](references.md), but you can invent your own types. You do not
have to make any changes to the catalog backend in order to accept new relation
types.
At the time of writing this, we do not have any namespacing/prefixing scheme for
relation types. The type is also not validated to contain only some particular
set of characters. Until rules for this are settled, you should stick to using
only letters, dashes and digits, and to avoid collisions with future core
relation types, you may want to prefix the type somehow. For example:
`myCompany-maintainerOf` + `myCompany-maintainedBy`.
If you have a suggestion for a relation type to be elevated to the core
offering, reach out to the Backstage maintainers or a support partner.
## Using a Well-Known Relation Type for a New Purpose
Example intents:
> "The ownerOf/ownedBy relation types sound like a good fit for expressing how
> users are technical owners of our company specific ServiceAccount kind, and we
> want to reuse those relation types for that."
At the time of writing, this is uncharted territory. If the documented use of a
relation states that one end of the relation commonly is a User or a Group, for
example, then consumers are likely to have conditional statements on the form
`if (x.kind === 'User') {} else {}`, which get confused when an unexpected kind
appears.
If you want to extend the use of an established relation type in a way that has
an effect outside of your organization, reach out to the Backstage maintainers
or a support partner to discuss risk/impact. It may even be that one end of the
relation could be considered for addition to the core.
## Adding a New Status field
Example intent:
> "We would like to convey entity statuses through the catalog in a generic way,
> as an integration layer. Our monitoring and alerting system has a plugin with
> Backstage, and it would be useful if the entity's status field contained the
> current alert state close to the actual entity data for anyone to consume. We
> find the `status.items` semantics a poor fit, so we would prefer to make our
> own custom field under `status` for these purposes."
We have not yet ventured to define any generic semantics for the `status`
object. We recommend sticking with the `status.items` mechanism where possible
(see below), since third party consumers will not be able to consume your status
information otherwise. Please reach out to the maintainers on Discord or by
making a GitHub issue describing your use case if you are interested in this
topic.
## Adding a New Status Item Type
Example intent:
> "The semantics of the entity `status.items` field are fine for our needs, but
> we want to contribute our own type of status into that array instead of the
> catalog specific one."
This is a simple, low risk way of adding your own status information to
entities. Consumers will be able to easily track and display the status together
with other types / sources.
We recommend that any status type that are not strictly private within the
organization be namespaced to avoid collisions. Statuses emitted by Backstage
core processes will for example be prefixed with `backstage.io/`, your
organization may prefix with `my-org.net/`, and `pagerduty.com/active-alerts`
could be a sensible complete status item type for that particular external
system.
The mechanics for how to emit custom statuses is not in place yet, so if this is
of interest to you, you might consider contacting the maintainers on Discord or
my making a GitHub issue describing your use case.
[This issue](https://github.com/backstage/backstage/issues/2292) also contains
more context.
## Referencing different environments with the model
Example intent:
> "I have multiple versions of my API deployed in different environments so I
> want to have `mytool-dev` and `mytool-prod` as different entities."
While it's possible to have different versions of the same thing represented as
separate entities, it's something we generally recommend against. We believe
that a developer should be able to just find for example one `Component`
representing a service, and to be able to see the different code versions that
are deployed throughout your stack within its view. This reasoning works
similarly for other kinds as well, such as `API`.
That being said - sometimes the differences between versions are so large, that
they represent what is for all intents and purposes an entirely new entity as
seen from the consumer's point of view. This can happen for example for
different _significant_ major versions of an API, and in particular if the two
major versions coexist in the ecosystem for some time. In those cases, it can be
motivated to have one `my-api-v2` and one `my-api-v3` named entity. This matches
the end user's expectations when searching for the API, and matches the desire
to maybe have separate documentation for the two and similar. But use this
sparingly - only do it if the extra modelling burden is outweighed by any
potential better clarity for users.
When writing your custom plugins, we encourage designing them such that they can
show all the different variations through environments etc under one canonical
reference to your software in the catalog. For example for a continuous
deployment plugin, a user is likely to be greatly helped by being able to see
the entity's versions deployed in all different environments next to each other
in one view. That is also where they might be offered the ability to promote
from one environment to the other, do rollbacks, see their relative performance
metrics, and similar. This coherency and collection of tooling in one place is
where something like Backstage can offer the most value and effectiveness of
use. Splitting your entities apart into small islands makes this harder.
## Implementing custom model extensions
This section walks you through the steps involved extending the catalog model
with a new Entity type.
### Creating a custom entity definition
The first step of introducing a custom entity is to define what shape and schema
it has. We do this using a TypeScript type, as well as a JSONSchema schema.
Most of the time you will want to have at least the TypeScript type of your
extension available in both frontend and backend code, which means you likely
want to have an isomorphic package that houses these types. Within the Backstage
main repo the package naming pattern of `<plugin>-common` is used for isomorphic
packages, and you may choose to adopt this pattern as well.
You can generate an isomorphic plugin package by running:`yarn new --select plugin-common`
or you can run `yarn new` and then select "plugin-common" from the list of options
There's at this point no existing templates for generating isomorphic plugins
using the `@backstage/cli`. Perhaps the simplest way to get started right now is
to copy the contents of one of the existing packages in the main repository,
such as `plugins/scaffolder-common`, and rename the folder and file contents to
the desired name. This example uses _foobar_ as the plugin name so the plugin
will be named _foobar-common_.
Once you have a common package in place you can start adding your own entity
definitions. For the exact details on how to do that we defer to getting
inspired by the existing
[scaffolder-common](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-common/src/index.ts)
package. But in short you will need to declare a TypeScript type and a
JSONSchema for the new entity kind.
### Building a custom processor for the entity
The next step is to create a custom processor for your new entity kind. This
will be used within the catalog to make sure that it's able to ingest and
validate entities of our new kind. Just like with the definition package, you
can find inspiration in for example the existing
[ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts).
We also provide a high-level example of what a catalog process for a custom
entity might look like:
```ts
import { CatalogProcessor, CatalogProcessorEmit, processingResult } from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common'
import { Entity, entityKindSchemaValidator } from '@backstage/catalog-model';
// For an example of the JSONSchema format and how to use $ref markers to the
// base definitions, see:
// https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
import { foobarEntityV1alpha1Schema } from '@internal/catalog-model';
export class FoobarEntitiesProcessor implements CatalogProcessor {
// You often end up wanting to support multiple versions of your kind as you
// iterate on the definition, so we keep each version inside this array as a
// convenient pattern.
private readonly validators = [
// This is where we use the JSONSchema that we export from our isomorphic
// package
entityKindSchemaValidator(foobarEntityV1alpha1Schema),
];
// Return processor name
getProcessorName(): string {
return 'FoobarEntitiesProcessor'
}
// validateEntityKind is responsible for signaling to the catalog processing
// engine that this entity is valid and should therefore be submitted for
// further processing.
async validateEntityKind(entity: Entity): Promise<boolean> {
for (const validator of this.validators) {
// If the validator throws an exception, the entity will be marked as
// invalid.
if (validator(entity)) {
return true;
}
}
// Returning false signals that we don't know what this is, passing the
// responsibility to other processors to try to validate it instead.
return false;
}
async postProcessEntity(
entity: Entity,
_location: LocationSpec,
emit: CatalogProcessorEmit,
): Promise<Entity> {
if (
entity.apiVersion === 'example.com/v1alpha1' &&
entity.kind === 'Foobar'
) {
const foobarEntity = entity as FoobarEntityV1alpha1;
// Typically you will want to emit any relations associated with the
// entity here.
emit(processingResult.relation({ ... }))
}
return entity;
}
}
```
Once the processor is created it can be wired up to the catalog via the
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-next-line */
builder.addProcessor(new FoobarEntitiesProcessor());
const { processingEngine, router } = await builder.build();
// ..
}
```
@@ -519,6 +519,9 @@ will be used within the catalog to make sure that it's able to ingest and
validate entities of our new kind. Just like with the definition package, you
can find inspiration in for example the existing
[ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts).
The custom processor should be created as a separate module for the catalog plugin. For information on how to set that up, see the [plugin docs](../../plugins/backend-plugin.md#creating-a-backend-plugin). Use `yarn new --select backend-module` instead to create a module. For our case, the module ID will be `foobar` and the plugin ID will be `catalog`.
We also provide a high-level example of what a catalog process for a custom
entity might look like:
@@ -585,20 +588,43 @@ export class FoobarEntitiesProcessor implements CatalogProcessor {
}
```
Once the processor is created it can be wired up to the catalog via the
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
#### New Backend
```ts title="packages/backend/src/plugins/catalog.ts"
To use your custom processor, you'll need to add the module to your backend as well as integrate your module with the catalog plugin.
```ts title="plugins/catalog-backend-module-foobar/src/index.ts"
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
/* highlight-add-next-line */
import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
import { FoobarEntitiesProcessor } from './providers';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-next-line */
builder.addProcessor(new FoobarEntitiesProcessor());
const { processingEngine, router } = await builder.build();
// ..
}
export const catalogModuleFoobarEntitiesProcessor = createBackendModule({
pluginId: 'catalog',
moduleId: 'foobar',
register(env) {
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
},
async init({ catalog }) {
catalog.addProcessor(new FoobarEntitiesProcessor());
},
});
},
});
export default catalogModuleFoobarEntitiesProcessor;
```
This module can then be installed to your backend like so,
```ts
backend.add(import('@internal/plugin-catalog-backend-module-foobar'));
```
#### Legacy Backend
Look through the [legacy documentation](./extending-the-model--old.md).
@@ -55,7 +55,7 @@ Some defining traits of entity providers:
The recommended way of instantiating the catalog backend classes is to use the
`CatalogBuilder`, as illustrated in the
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts).
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts).
We will create a new
[`EntityProvider`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/provider.ts)
subclass that can be added to this catalog builder.
@@ -531,7 +531,7 @@ does so!
The recommended way of instantiating the catalog backend classes is to use the
`CatalogBuilder`, as illustrated in the
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts).
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts).
We will create a new
[`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/processor.ts)
subclass that can be added to this catalog builder.
+1 -2
View File
@@ -40,8 +40,7 @@ browse the catalog at `http://localhost:3000`.
## Adding components to the catalog
The source of truth for the components in your software catalog are [metadata
YAML files](descriptor-format.md) stored in source control (GitHub, GitHub
The source of truth for the components in your software catalog are [metadata YAML files](descriptor-format.md) stored in source control (GitHub, GitHub
Enterprise, GitLab, ...). Repositories can include one or multiple metadata
files. Usually the metadata file is located in the repository root. This is not
a formal requirement & metadata files can be placed anywhere in the repository.
@@ -162,9 +162,13 @@ steps and merging them into the final object which is what is visible from the
catalog API. As the final entity itself gets updated, the stitcher makes sure
that the search table gets refreshed accordingly as well.
> Note: The search table mentioned here is not related to the core Search
> feature of Backstage. It's rather the table that backs the ability to filter
> catalog API query results.
:::note Note
The search table mentioned here is not related to the core Search
feature of Backstage. It's rather the table that backs the ability to filter
catalog API query results.
:::
![Stitching overview](../../assets/features/catalog/life-of-an-entity_stitching.svg)
@@ -120,6 +120,9 @@ product or use-case, share the same entity types in their APIs, and integrate
well with each other. Other domains could be “Content Ingestion”, “Ads” or
“Search”.
In case of a large organization, it might make sense to further group domains
in a hierarchy, where a domain can be a subdomain of another domain.
## Other
### Location
@@ -86,12 +86,17 @@ contains more information about the required fields.
Once we have a `template.yaml` ready, we can then add it to the software catalog
for use by the scaffolder.
> Note: When you add or modify a template, you will need to refresh the location entity.
> Otherwise, Backstage won't display the template in the available templates,
> or it will keep showing the old template. You can refresh the location instance by
> going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct location entity.
> From there, you can click on the refresh icon representing "Scheduled entity refresh" action.
> Afterwards, you should see your template updated.
:::note Note
When you add or modify a template, you will need to refresh the location entity.
Otherwise, Backstage won't display the template in the available templates,
or it will keep showing the old template. You can refresh the location instance by
going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct
location entity.
From there, you can click on the refresh icon representing "Scheduled entity refresh" action.
Afterwards, you should see your template updated.
:::
You can add the template files to the catalog through
[static location configuration](../software-catalog/configuration.md#static-location-configuration),
@@ -1,10 +1,10 @@
---
id: authorizing-parameters-steps-and-actions
title: 'Authorizing parameters, steps and actions'
description: How to authorize part of a template
id: authorizing-scaffolder-template-details
title: 'Authorizing scaffolder tasks, parameters, steps, and actions'
description: How to authorize parts of a template and authorize scaffolder task access
---
The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template.
The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks.
### Authorizing parameters and steps
@@ -174,7 +174,64 @@ class ExamplePermissionPolicy implements PermissionPolicy {
}
```
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex cases.
### 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.
```ts title="packages/src/backend/plugins/permissions.ts"
/* highlight-add-start */
import {
taskCancelPermission,
taskCreatePermission,
taskReadPermission,
} from '@backstage/plugin-scaffolder-common/alpha';
/* highlight-add-end */
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, taskCreatePermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
if (isPermission(request.permission, taskCancelPermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
if (isPermission(request.permission, taskReadPermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
/* highlight-add-end */
return {
result: AuthorizeResult.DENY,
};
}
}
```
In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources:
- Read all scaffolder tasks and their associated events/logs.
- Cancel any ongoing scaffolder tasks.
- Trigger software templates, which effectively creates new scaffolder tasks.
Any other user would be denied access to these actions/resources.
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases.
### Authorizing in the New Backend System
@@ -229,4 +286,8 @@ backend.add(customPermissionBackendModule);
/* highlight-add-end */
```
> Note: the `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them.
:::note Note
The `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them.
:::
@@ -57,7 +57,11 @@ backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.start();
```
> Note: This is a simplified example of what your backend may look like, you may have more code in here then this.
:::note Note
This is a simplified example of what your backend may look like, you may have more code in here then this.
:::
## Listing Actions
@@ -12,7 +12,11 @@ This is done in your `app-config.yaml` by adding
[Backstage integrations](https://backstage.io/docs/integrations/) for the
appropriate source code repository for your organization.
> Note: Integrations may already be set up as part of your `app-config.yaml`.
:::note Note
Integrations may already be set up as part of your `app-config.yaml`.
:::
The next step is to [add templates](http://backstage.io/docs/features/software-templates/adding-templates)
to your Backstage app.
+22 -4
View File
@@ -20,10 +20,14 @@ locations like GitHub or GitLab.
> Be sure to have covered
> [Getting Started with Backstage](../../getting-started) before proceeding.
> Note: if you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to
> use the templates feature.
> One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage:
> `export NODE_OPTIONS=--no-node-snapshot`
:::note Note
If you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to
use the templates feature.
One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage:
`export NODE_OPTIONS=--no-node-snapshot`
:::
The Software Templates are available under `/create`. For local development you
should be able to reach them at `http://localhost:3000/create`.
@@ -103,3 +107,17 @@ from `backstage/packages/app/src/App.tsx`:
```
After the change, you should no longer see the button.
## Previewing and Executing Previous Template Tasks
Each execution of a template is treated as a unique task, identifiable by its own unique ID. To view a list of previously executed template tasks, navigate to the "Create" page and access the "Task List" from the context menu (represented by the vertical ellipsis, or 'kebab menu', icon in the upper right corner).
![Template Task List](../../assets/software-templates/template-task-list.png)
If you wish to re-run a previously executed template, navigate to the template tasks page. Locate the desired task and select the "Start Over" option from the context menu.
![Template Start Over](../../assets/software-templates/template-start-over.png)
This action will initiate a new execution of the selected template, pre-populated with the same parameters as the previous run, but these parameters can be edited before re-execution.
In the event of a failed template execution, the "Start Over" option can be used to re-execute the template. The parameters from the original run will be pre-filled, but they can be adjusted as needed before retrying the template.
@@ -5,7 +5,11 @@ title: 'Migrating to react-jsonschema-form@v5'
description: Docs on migrating to `react-jsonschema-form`@v5 and the new designs
---
> Note: If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc.
:::note Note
If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc.
:::
## What's `react-jsonschema-form`?
@@ -8,10 +8,14 @@ If you want to extend the functionality of the Scaffolder, you can do so
by writing custom actions which can be used alongside our
[built-in actions](./builtin-actions.md).
> Note: When adding custom actions, the actions array will **replace the
> built-in actions too**. Meaning, you will no longer be able to use them.
> If you want to continue using the builtin actions, include them in the actions
> array when registering your custom actions, as seen below.
:::note Note
When adding custom actions, the actions array will **replace the
built-in actions too**. Meaning, you will no longer be able to use them.
If you want to continue using the builtin actions, include them in the actions
array when registering your custom actions, as seen below.
:::
## Writing your Custom Action
@@ -681,6 +681,8 @@ how to use them in the Scaffolder templates. It's important to mention that Back
native filters from the Nunjucks library. For a complete list of these native filters and their usage,
refer to the [Nunjucks documentation](https://mozilla.github.io/nunjucks/templating.html#builtin-filters).
To create your own custom filters, look to the section [Custom Filters](#custom-filters) hereafter.
### parseRepoUrl
The `parseRepoUrl` filter parse a repository URL into
@@ -765,3 +767,150 @@ The `projectSlug` filter generates a project slug from a repository URL
- **Input**: `github.com?repo=backstage&org=backstage`
- **Output**: `backstage/backstage`
## Custom Filters
Whenever it is needed to extend the built-in filters with yours `${{ parameters.name | my-filter1 | my-filter2 | etc }}`, then you can add them
using the property `additionalTemplateFilters`.
The `additionalTemplateFilters` property accepts as type a `Record`
```ts title="plugins/scaffolder-backend/src/service/Router.ts"
additionalTemplateFilters?: Record<string, TemplateFilter>;
```
where the first parameter is the name of the filter and the second receives a list of `JSON value` arguments. The `templateFilter()` function must return a JsonValue which is either a Json array, object or primitive.
```ts title="plugins/scaffolder-node/src/types.ts"
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
```
From a practical coding point of view, you will translate that into the following snippet code handling 2 filters:
```ts"
...
additionalTemplateFilters: {
base64: (...args: JsonValue[]) => btoa(args.join("")),
betterFilter: (...args: JsonValue[]) => { return `This is a much better string than "${args}", don't you think?` }
}
```
And within your template, you will be able to use the filters using a parameter and the filter passed using the pipe symbol
```yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: test
title: Test
spec:
owner: user:guest
type: service
parameters:
- title: Test custom filters
properties:
userName:
title: Name of the user
type: string
steps:
- id: debug
name: debug
action: debug:log
input:
message: ${{ parameters.userName | betterFilter | base64 }}
```
Next, you will have to register the property `addTemplateFilters` using the `scaffolderTemplatingExtensionPoint` of a new `BackendModule` [created](../../backend-system/architecture/06-modules.md).
Here is a very simplified example of how to do that:
```ts title="packages/backend-next/src/index.ts"
/* highlight-add-start */
import { scaffolderTemplatingExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-end */
/* highlight-add-start */
const scaffolderModuleCustomFilters = createBackendModule({
pluginId: 'scaffolder', // name of the plugin that the module is targeting
moduleId: 'custom-filters',
register(env) {
env.registerInit({
deps: {
scaffolder: scaffolderTemplatingExtensionPoint,
// ... and other dependencies as needed
},
async init({ scaffolder /* ..., other dependencies */ }) {
scaffolder.addTemplateFilters({
base64: (...args: JsonValue[]) => btoa(args.join('')),
betterFilter: (...args: JsonValue[]) => {
return `This is a much better string than "${args}", don't you think?`;
},
});
},
});
},
});
/* highlight-add-end */
const backend = createBackend();
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
/* highlight-add-next-line */
backend.add(scaffolderModuleCustomFilters());
```
If you still use the legacy backend system, then you will use the `createRouter()` function of the `Scaffolder plugin`
```ts title="packages/backend/src/plugins/scaffolder.ts"
export default async function createPlugin({
logger,
config,
}: PluginEnvironment): Promise<Router> {
...
return await createRouter({
logger,
config,
additionalTemplateFilters: {
<YOUR_FILTERS>
}
});
```
## Template Editor
Writing template is most of the times an iterative process. You will need to test your template to make sure it has a good user experience and that it works as expected. To help on this process the scaffolder comes with a build in template editor that allows you to test your template in a real environment for querying data and execute the actions on dry-run mode to see the results of those one.
To access to the template editor you can go to the templates page and select "Template Editor" from the context menu or navigate to the `{scaffolder-path}/edit` url. (i.e. the default route would be `/create/edit`)
![Context menu](../../assets/software-templates/context-menu.png)
The template editor has 3 main sections:
1. **Load Template Directory**: Load a local template directory, allowing you to both edit and try executing your own template.
2. **Edit Template Form**: Preview and edit a template form, either using a sample template or by loading a template from the catalog.
3. **Custom Field Explorer**: View and play around with available installed custom field extensions.
### Load Template Directory
Allow to load a directory on your local file system that contains a template and editing the files in it while previewing the form and executing the template.
![template editor load dir](../../assets/software-templates/template-editor-load-dir.png)
If you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen.
Here we could find all the file system results of the template execution as well as the logs of each action that was executed.
![dry run drawer example](../../assets/software-templates/template-editor-dry-run.png)
### Edit Template Form
This is a reduced version of the template editor that allows you to select any template from the catalog and do some modifications on the form presented to the user to test some changes.
Have in mind that changes in this form will not be saved on the template and is meant to test out changes to replicate them manually on the template file after.
### Custom Field Explorer
The custom filed explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations.
+8 -3
View File
@@ -13,9 +13,14 @@ out-of-the box experience.
![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg)
> Note: See below for our recommended deployment architecture which takes care
> of stability, scalability and speed. Also look at the
> [HOW TO migrate guide](how-to-guides.md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
:::note Note
See below for our recommended deployment architecture which takes care
of stability, scalability and speed. Also look at the
[HOW TO migrate guide](how-to-guides
md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
:::
When you open a TechDocs site in Backstage, the
[TechDocs Reader](./concepts.md#techdocs-reader) makes a request to
+5 -1
View File
@@ -219,7 +219,11 @@ backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
backend.start();
```
> Note: The above is a very simplified example, you may have more content then this in your version.
:::note Note
The above is a very simplified example, you may have more content then this in your version.
:::
## Setting the configuration
+26 -10
View File
@@ -499,8 +499,12 @@ Start writing your documentation by adding more markdown (.md) files to this
folder (/docs) or replace the content in this file.
```
> Note: The values of `site_name`, `component_id` and `site_description` depends
> on how you have configured your `template.yaml`
:::note Note
The values of `site_name`, `component_id` and `site_description` depends
on how you have configured your `template.yaml`.
:::
Done! You now have support for TechDocs in your own software template!
@@ -514,7 +518,11 @@ theme:
font: false
```
> Note: The addition `name: material` is necessary. Otherwise it will not work
:::note Note
The addition `name: material` is necessary. Otherwise it will not work
:::
## How to enable iframes in TechDocs
@@ -623,12 +631,16 @@ plugins:
- kroki
```
> Note: you will very likely want to set a `kroki` `ServerURL` configuration in your
> `mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If
> you have sensitive information in your organization's diagrams, you should set
> up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it
> instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config)
> for more plugin configuration details.
:::note Note
You will very likely want to set a `kroki` `ServerURL` configuration in your
`mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If
you have sensitive information in your organization's diagrams, you should set
up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it
instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config)
for more plugin configuration details.
:::
4. **Add mermaid code into TechDocs:**
@@ -766,7 +778,11 @@ backend.add(techdocsCustomBuildStrategy());
backend.start();
```
> Note: You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already.
:::note Note
You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already.
:::
## How to use other mkdocs plugins?
+15 -11
View File
@@ -169,17 +169,21 @@ permissions to:
- `s3:ListBucket` - To retrieve bucket metadata
- `s3:GetObject` - To retrieve files from the bucket
> Note: If you need to migrate documentation objects from an older-style path
> format including case-sensitive entity metadata, you will need to add some
> additional permissions to be able to perform the migration, including:
>
> - `s3:PutBucketAcl` (for copying files,
> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html))
> - `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files,
> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html))
>
> ...And you will need to ensure the permissions apply to the bucket itself, as
> well as all resources under the bucket. See the example policy below.
:::note Note
If you need to migrate documentation objects from an older-style path
format including case-sensitive entity metadata, you will need to add some
additional permissions to be able to perform the migration, including:
- `s3:PutBucketAcl` (for copying files,
[more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html))
- `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files,
[more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html))
...And you will need to ensure the permissions apply to the bucket itself, as
well as all resources under the bucket. See the example policy below.
:::
```json
{
@@ -22,10 +22,10 @@ In order to override an app extension, you must create a new extension and add i
In the example below, we create a file that exports custom extensions for the app's `light` and `dark` themes:
```tsx title="packages/app/src/themes.ts"
```tsx title="packages/app/src/themes.tsx"
import {
createThemeExtension,
createExtensionOverrides
createExtensionOverrides,
} from '@backstage/frontend-plugin-api';
import { apertureThemes } from './themes';
import { ApertureLightIcon, ApertureDarkIcon } from './icons';
@@ -59,8 +59,8 @@ const apertureDarkTheme = createThemeExtension({
});
// Creating an extension overrides preset
export createExtensionOverrides({
extensions: [apertureLightTheme, apertureDarkTheme]
export default createExtensionOverrides({
extensions: [apertureLightTheme, apertureDarkTheme],
});
```
@@ -96,8 +96,11 @@ We recommend that plugin developers share the extension IDs in their plugin docu
Imagine you have a plugin with the ID `'search'`, and the plugin provides a page extension that you want to fully override with your own custom component. To do so, you need to create your page extension with an explicit `namespace` option that matches that of the plugin that you want to override, in this case `'search'`. If the existing extension also has an explicit `name` you'd need to set the `name` of your override extension to the same value as well.
```tsx title="packages/app/src/search.ts"
import { createPageExtension } from '@backstage/frontend-plugin-api';
```tsx title="packages/app/src/search.tsx"
import {
createPageExtension,
createExtensionOverrides,
} from '@backstage/frontend-plugin-api';
// Creating a custom search page extension
const customSearchPage = createPageExtension({
@@ -108,7 +111,7 @@ const customSearchPage = createPageExtension({
loader: () => import('./SearchPage').then(m => m.<SearchPage/>),
});
export createExtensionOverrides({
export default createExtensionOverrides({
extensions: [customSearchPage]
});
```
@@ -137,7 +140,7 @@ Sometimes you just need to quickly create a new extension and not overwrite an a
Imagine you want to create a page that is currently only used by your application, like an Institutional page, for example. You can use overrides to extend the Backstage app to render it. To do so, simply create a page extension and pass it to the app as an override:
```tsx title="packages/app/src/App.ts"
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
import {
createPageExtension,
@@ -6,4 +6,50 @@ sidebar_label: Configuring Extensions
description: Documentation for how to configure extensions in a Backstage app
---
TODO
All extensions in a Backstage app can be configured through static configuration. This configuration is all done under the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md).
## Extension Configuration Schema
This section focuses on the format of the `app.extensions` configuration and the various shorthands that are available.
The most complete and verbose format for configuring an individual extensions is as follows:
```yaml
app:
extensions:
- <id>:
attachTo:
id: <parent-id>
input: <input-name>
disabled: <true/false>
config: <extension-specific-config->
```
All of the top-level fields are optional: `attachTo`, `disabled`, and `config`. Every extension implementation must provide defaults for all of these fields that will be used if they are not provided in the configuration.
Note that `app.extensions` is always an array rather than an object. For example, the following is invalid:
```yaml title="INVALID"
app:
extensions:
<id>: # Invalid, this should be an array item, `app.extensions` is now an object
config: ...
```
In addition to this schema, there are a number of shorthands available:
Rather than a full object, you can specify just the ID of the extension as a string. This is equivalent to setting `disabled` to `false`:
```yaml
app:
extensions:
- <id>
```
You can enable/disable individual extension by ID, in this case the value is a boolean:
```yaml
app:
extensions:
- <id>: <true/false>
```
@@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p
[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md).
For example, the following API configuration:
For example, the following `apis` configuration:
```ts
const app = createApp({
@@ -151,15 +151,75 @@ Icons are currently installed through the usual options to `createApp`, but will
Plugins are now passed through the `features` options instead.
For example, the following `plugins` configuration:
```tsx
import { homePlugin } from '@backstage/plugin-home';
createApp({
// ...
plugins: [homePlugin],
// ...
});
```
Can be converted to the following `features` configuration:
```tsx
// plugins are now default exported via alpha subpath
import homePlugin from '@backstage/plugin-home/alpha';
createApp({
// ...
features: [homePlugin],
// ...
});
```
Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled.
```yaml title="in app-config.yaml"
app:
# Enabling plugin and override features discovery
experimental: 'all'
```
### `featureFlags`
Declaring features flags in the app is no longer supported, move these declarations to the appropriate plugins instead.
For example, the following app feature flags configuration:
```tsx
createApp({
// ...
featureFlags: [
{
pluginId: '',
name: 'tech-radar',
description: 'Enables the tech radar plugin',
},
],
// ...
});
```
Can be converted to the following plugin configuration:
```tsx
createPlugin({
id: 'tech-radar',
// ...
featureFlags: [{ name: 'tech-radar' }],
// ...
});
```
### `components`
Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information.
The `Router` component is now a built-in extension that you can override using `createRouterExtension`.
The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`.
The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead.
@@ -277,6 +337,35 @@ const app = createApp({
Translations are now installed as extensions, using `createTranslationExtension`.
For example, the following translations configuration:
```tsx
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
createApp({
// ...
__experimentalTranslations: {
resources: [
createTranslationMessages({
ref: catalogTranslationRef,
catalog_page_create_button_title: 'Create Software',
}),
],
},
// ...
});
```
Can be converted to the following extension:
```tsx
createTranslationExtension({
resource: createTranslationMessages({
ref: catalogTranslationRef,
catalog_page_create_button_title: 'Create Software',
}),
});
```
## Gradual Migration
After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673).
@@ -368,7 +457,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog`
New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box.
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an extension override:
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md):
```tsx
const nav = createExtension({
@@ -435,3 +524,24 @@ export default app.createRoot(
```
Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper.
Here is an example converting the `CustomAppBarrier` into extension:
```tsx
createApp({
// ...
features: [
createExtensionOverrides({
extensions: [
createAppRootWrapperExtension({
name: 'CustomAppBarrier',
// Whenever your component uses legacy core packages, wrap it with "compatWrapper"
// e.g. props => compatWrapper(<CustomAppBarrier {...props} />)
Component: CustomAppBarrier,
}),
],
}),
],
// ...
});
```
+10 -2
View File
@@ -28,7 +28,11 @@ export const myTheme = createUnifiedTheme({
});
```
> Note: we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized.
:::note Note
we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized.
:::
You can also create a theme from scratch that matches the `BackstageTheme` type exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the
[Material UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done.
@@ -504,7 +508,11 @@ You can add more icons, if the [default icons](https://github.com/backstage/back
You might want to use this method if you have an icon you want to use in several locations.
Note: If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon`
:::note Note
If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon`
:::
## Custom Sidebar
@@ -13,7 +13,7 @@ starting point that's meant to be evolved.
The Backstage CLI has a command to bump all `@backstage` packages and
dependencies you're using to the latest versions:
[versions:bump](https://backstage.io/docs/local-dev/cli-commands#versionsbump).
[versions:bump](https://backstage.io/docs/tooling/cli/03-commands#versionsbump).
```bash
yarn backstage-cli versions:bump
@@ -70,7 +70,7 @@ example, depends on global referential equality. This can cause problems in
Backstage with API lookup, or config loading.
To help resolve these situations, the Backstage CLI has
[versions:check](https://backstage.io/docs/local-dev/cli-commands#versionscheck). This
[versions:check](https://backstage.io/docs/tooling/cli/03-commands#versionscheck). This
will validate versions of `@backstage` packages in your app to check for
duplicate definitions:
+187
View File
@@ -0,0 +1,187 @@
---
id: discovery--old
title: Azure DevOps Discovery
sidebar_label: Discovery
# prettier-ignore
description: Automatically discovering catalog entities from repositories in an Azure DevOps organization
---
:::info
This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Azure DevOps integration has a special entity provider for discovering
catalog entities within an Azure DevOps. The provider will crawl your Azure
DevOps organization and register entities matching the configured path. This can
be useful as an alternative to static locations or manually adding things to the
catalog.
This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor.
## Dependencies
### Code Search Feature
Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure
DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure
DevOps Server you'll find this information in your Collection Settings.
If the Code Search extension is not listed then you can install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms.vss-code-search&targetId=f9352dac-ba6e-434e-9241-a848a510ce3f&utm_source=vstsproduct&utm_medium=SearchExtStatus).
### Azure Integration
Setup [Azure integration](locations.md) with `host` and `token`. Host must be `dev.azure.com` for Cloud users, otherwise set this to your on-premise hostname.
## Installation
At your configuration, you add one or more provider configs:
```yaml title="app-config.yaml"
catalog:
providers:
azureDevOps:
yourProviderId: # identifies your dataset / provider independent of config changes
organization: myorg
project: myproject
repository: service-* # this will match all repos starting with service-*
path: /catalog-info.yaml
schedule: # optional; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
yourSecondProviderId: # identifies your dataset / provider independent of config changes
organization: myorg
project: '*' # this will match all projects
repository: '*' # this will match all repos
path: /catalog-info.yaml
anotherProviderId: # another identifier
organization: myorg
project: myproject
repository: '*' # this will match all repos
path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder
yetAnotherProviderId: # guess, what? Another one :)
host: selfhostedazure.yourcompany.com
organization: myorg
project: myproject
branch: development
```
The parameters available are:
- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host.
- **`organization:`** Your Organization slug (or Collection for on-premise users). Required.
- **`project:`** _(required)_ Your project slug. Wildcards are supported as shown on the examples above. Using '\*' will search all projects. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`.
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml.
- **`branch:`** _(optional)_ The branch name to use.
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
The maximum amount of time that a single task invocation can take.
- **`initialDelay`** _(optional)_:
The amount of time that should pass before the first invocation happens.
- **`scope`** _(optional)_:
`'global'` or `'local'`. Sets the scope of concurrency control.
_Note:_
- The path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the [official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops).
- To use branch parameters, it is necessary that the desired branch be added to the "Searchable branches" list within Azure DevOps Repositories. To do this, follow the instructions below:
1. Access your Azure DevOps and open the repository in which you want to add the branch.
2. Click on "Settings" in the lower-left corner of the screen.
3. Select the "Options" option in the left navigation bar.
4. In the "Searchable branches" section, click on the "Add" button to add a new branch.
5. In the window that appears, enter the name of the branch you want to add and click "Add".
6. The added branch will now appear in the "Searchable branches" list.
It may take some time before the branch is indexed and searchable.
As this provider is not one of the default providers, you will first need to install
the Azure catalog plugin:
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure';
const builder = await CatalogBuilder.create(env);
/** ... other processors and/or providers ... */
/* highlight-add-start */
builder.addEntityProvider(
AzureDevOpsEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
/* highlight-add-end */
```
## Alternative Processor
As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`.
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-next-line */
builder.addProcessor(
AzureDevOpsDiscoveryProcessor.fromConfig(env.config, {
logger: env.logger,
}),
);
// ..
}
```
```yaml
catalog:
locations:
# Scan all repositories for a catalog-info.yaml in the root of the default branch
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject
# Or use a custom pattern for a subset of all repositories with default repository
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/service-*
# Or use a custom file format and location
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml
# And optionally provide a specific branch name using the version parameter
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info
```
Note the `azure-discovery` type, as this is not a regular `url` processor.
When using a custom pattern, the target is composed of these parts:
- The base instance URL, `https://dev.azure.com` in this case
- The organization name which is required, `myorg` in this case
- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to.
- The repository blob to scan, which accepts \* wildcard tokens and must be
added after `_git/`. This can simply be `*` to scan all repositories in the
project.
- The path within each repository to find the catalog YAML file. This will
usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar
variation for catalog files stored in the root directory of each repository.
- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch.
+9 -75
View File
@@ -6,6 +6,10 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from repositories in an Azure DevOps organization
---
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Azure DevOps integration has a special entity provider for discovering
catalog entities within an Azure DevOps. The provider will crawl your Azure
DevOps organization and register entities matching the configured path. This can
@@ -30,7 +34,7 @@ Setup [Azure integration](locations.md) with `host` and `token`. Host must be `d
## Installation
At your configuration, you add one or more provider configs:
In your configuration, you add one or more provider configs:
```yaml title="app-config.yaml"
catalog:
@@ -103,81 +107,11 @@ the Azure catalog plugin:
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`:
Then updated your backend by adding the following line:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure';
const builder = await CatalogBuilder.create(env);
/** ... other processors and/or providers ... */
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
builder.addEntityProvider(
AzureDevOpsEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
backend.add(import('@backstage/plugin-catalog-backend-module-azure/alpha'));
/* highlight-add-end */
```
## Alternative Processor
As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`.
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-next-line */
builder.addProcessor(
AzureDevOpsDiscoveryProcessor.fromConfig(env.config, {
logger: env.logger,
}),
);
// ..
}
```
```yaml
catalog:
locations:
# Scan all repositories for a catalog-info.yaml in the root of the default branch
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject
# Or use a custom pattern for a subset of all repositories with default repository
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/service-*
# Or use a custom file format and location
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml
# And optionally provide a specific branch name using the version parameter
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info
```
Note the `azure-discovery` type, as this is not a regular `url` processor.
When using a custom pattern, the target is composed of these parts:
- The base instance URL, `https://dev.azure.com` in this case
- The organization name which is required, `myorg` in this case
- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to.
- The repository blob to scan, which accepts \* wildcard tokens and must be
added after `_git/`. This can simply be `*` to scan all repositories in the
project.
- The path within each repository to find the catalog YAML file. This will
usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar
variation for catalog files stored in the root directory of each repository.
- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch.
+17 -11
View File
@@ -68,11 +68,15 @@ integrations:
If you do not specify the `organizations` field the credential will be used for all organizations for which no other credential is configured.
> Note: An Azure DevOps provider is added automatically at startup for
> convenience, so you only need to list it if you want to supply a
> [personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate),
> a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity),
> or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity)
:::note Note
An Azure DevOps provider is added automatically at startup for
convenience, so you only need to list it if you want to supply a
[personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate),
a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity),
or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity)
:::
The configuration is a structure with these elements:
@@ -86,9 +90,11 @@ The `credentials` element is a structure with these elements:
- `tenantId`: The tenant ID of the service principal (required for service principal)
- `personalAccessToken`: The personal access token (required for personal access token)
> Note:
>
> - You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations
> - You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations
> - You can only specify one credential per host without any organizations specified
> - The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
:::note Note
- You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations
- You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations
- You can only specify one credential per host without any organizations specified
- The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
:::
+274
View File
@@ -0,0 +1,274 @@
---
id: org--old
title: Microsoft Entra Tenant Data
sidebar_label: Org Data
# prettier-ignore
description: Importing users and groups from Microsoft Entra ID into Backstage
---
:::info
This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from a tenant in Microsoft Entra ID via the
Microsoft Graph API.
## Installation
The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package.
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph
```
Next add the basic configuration to `app-config.yaml`
```yaml title="app-config.yaml"
catalog:
providers:
microsoftGraphOrg:
default:
tenantId: ${AZURE_TENANT_ID}
user:
filter: accountEnabled eq true and userType eq 'member'
group:
filter: >
securityEnabled eq false
and mailEnabled eq true
and groupTypes/any(c:c+eq+'Unified')
schedule:
frequency: PT1H
timeout: PT50M
```
Finally, register the plugin in `catalog.ts`.
For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try.
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
builder.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
logger: env.logger,
scheduler: env.scheduler,
}),
);
/* highlight-add-end */
// ..
}
```
## Authenticating with Microsoft Graph
### Local Development
For a local dev environment, it's recommended you have the Azure CLI or Azure PowerShell installed, and are logged in to those.
Alternatively you can use VSCode with the Azure extension if you install `@azure/identity-vscode`.
When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions.
If you can't do this, you'll have to create an App Registration.
### App Registration
If none of the other authentication methods work, you can create an app registration in the azure portal.
By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph:
- `GroupMember.Read.All`
- `User.Read.All`
If your organization required Admin Consent for these permissions, that will need to be granted.
When authenticating with a ClientId/ClientSecret, you can either set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_SECRET` environment variables, or specify the values in configuration
```yaml
microsoftGraphOrg:
default:
##...
clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281
clientSecret: REDACTED
```
To authenticate with a certificate rather than a client secret, you can set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_CERTIFICATE_PATH` environments
### Managed Identity
If deploying to resources that supports Managed Identity, and has identities configured (e.g. Azure App Services, Azure Container Apps), Managed Identity should be picked up without any additional configuration.
If your app has multiple managed identities, you may need to set the `AZURE_CLIENT_ID` environment variable to tell Azure Identity which identity to use.
To grant the managed identity the same permissions as mentioned in _App Registration_ above, [please follow this guide](https://docs.microsoft.com/en-us/azure/app-service/tutorial-connect-app-access-microsoft-graph-as-app-javascript?tabs=azure-powershell)
## Filtering imported Users and Groups
By default, the plugin will import all users and groups from your directory.
This can be customized through [filters](https://learn.microsoft.com/en-us/graph/filter-query-parameter) and [search](https://learn.microsoft.com/en-us/graph/search-query-parameter) queries. Keep in mind that if you omit filters and search queries for the user or group properties, the plugin will automatically import all available users or groups.
### Groups
A smaller set of groups can be obtained by configuring a search query or a filter.
If both `filter` and `search` are provided, then groups must match both to be ingested.
```yaml
microsoftGraphOrg:
providerId:
group:
filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified')
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
In addition to these groups, one additional group will be created for your organization.
All imported groups will be a child of this group.
### Users
There are two modes for importing users - You can import all user objects matching a `filter`.
```yaml
microsoftGraphOrg:
providerId:
user:
filter: accountEnabled eq true and userType eq 'member'
```
Alternatively you can import users that are members of specific groups.
For each group matching the `search` and `filter` query, each group member will be imported.
Only direct group members will be imported, not transient users.
```yaml
microsoftGraphOrg:
providerId:
userGroupMember:
filter: "displayName eq 'Backstage Users'"
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
### User photos
By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`:
```yaml
microsoftGraphOrg:
providerId:
user:
filter: ...
loadPhotos: false
```
## Customizing Transformation
Ingested entities can be customized by providing custom transformers.
These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer`
Entities can also be excluded from backstage by returning `undefined`.
These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider`
```ts
builder.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
// ...
/* highlight-add-start */
groupTransformer: myGroupTransformer,
userTransformer: myUserTransformer,
organizationTransformer: myOrganizationTransformer,
/* highlight-add-end */
}),
);
```
When using custom transformers, you may want to customize the data returned.
Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need
```yaml
microsoftGraphOrg:
providerId:
user:
expand: manager
group:
expand: member
select: ['id', 'displayName', 'description']
```
The following provides an example of each kind of transformer
```ts
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import {
defaultGroupTransformer,
defaultUserTransformer,
defaultOrganizationTransformer,
} from '@backstage/plugin-catalog-backend-module-msgraph';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
// This group transformer completely replaces the built in logic with custom logic.
export async function myGroupTransformer(
group: MicrosoftGraph.Group,
groupPhoto?: string,
): Promise<GroupEntity | undefined> {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
metadata: {
name: group.id!,
annotations: {},
},
spec: {
type: 'Microsoft Entra ID',
children: [],
},
};
}
// This user transformer makes use of the built in logic, but also sets the description field
export async function myUserTransformer(
graphUser: MicrosoftGraph.User,
userPhoto?: string,
): Promise<UserEntity | undefined> {
const backstageUser = await defaultUserTransformer(graphUser, userPhoto);
if (backstageUser) {
backstageUser.metadata.description = 'Loaded from Microsoft Entra ID';
}
return backstageUser;
}
// Example organization transformer that removes the organization group completely
export async function myOrganizationTransformer(
graphOrganization: MicrosoftGraph.Organization,
): Promise<GroupEntity | undefined> {
return undefined;
}
```
## Troubleshooting
### No data
First check your logs for the message `Reading msgraph users and groups`.
If you don't see this, check you've registered the provider, and that the schedule is valid
If you see a log entry `Read 0 msgraph users and 0 msgraph groups`, check your search and filter arguments.
If you see the start message (`Reading msgraph users and groups`) but no end message (`Read X msgraph users and Y msgraph groups`), then it is likely the job is taking a long time due to a large volume of data.
The default behavior is to import all users and groups, which is often more data than needed.
Try importing a smaller set of data (e.g. `filter: displayName eq 'John Smith'`).
### Authentication / Token Errors
See [Troubleshooting Azure Identity Authentication Issues](https://aka.ms/azsdk/js/identity/troubleshoot)
### Error while reading users from Microsoft Graph: Authorization_RequestDenied - Insufficient privileges to complete the operation
- Make sure you've granted all the required permissions to your application registration or managed identity
- Make sure the permissions are `Application` permissions rather than `Delegated`
- If your organization has configured "Admin consent" to be required, make sure this has been granted for your application permissions
- If your group queries are returning Microsoft Teams groups, you may need to grant addition permissions (e.g. `Team.ReadBasic.All`, `TeamMember.Read.All`)
- If you've added additional `select` or `expand` fields, those may need additional permissions granted
+70 -37
View File
@@ -6,6 +6,10 @@ sidebar_label: Org Data
description: Importing users and groups from Microsoft Entra ID into Backstage
---
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from a tenant in Microsoft Entra ID via the
Microsoft Graph API.
@@ -39,29 +43,17 @@ catalog:
timeout: PT50M
```
Finally, register the plugin in `catalog.ts`.
:::note
For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try.
:::
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph';
Finally, updated your backend by adding the following line:
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
builder.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
logger: env.logger,
scheduler: env.scheduler,
}),
);
/* highlight-add-end */
// ..
}
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-msgraph/alpha'));
/* highlight-add-end */
```
## Authenticating with Microsoft Graph
@@ -146,27 +138,24 @@ microsoftGraphOrg:
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
### User photos
By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`:
```yaml
microsoftGraphOrg:
providerId:
user:
filter: ...
loadPhotos: false
```
## Customizing Transformation
Ingested entities can be customized by providing custom transformers.
These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer`
Entities can also be excluded from backstage by returning `undefined`.
These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider`
```ts
builder.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
// ...
/* highlight-add-start */
groupTransformer: myGroupTransformer,
userTransformer: myUserTransformer,
organizationTransformer: myOrganizationTransformer,
/* highlight-add-end */
}),
);
```
When using custom transformers, you may want to customize the data returned.
Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need
@@ -180,9 +169,53 @@ microsoftGraphOrg:
select: ['id', 'displayName', 'description']
```
The following provides an example of each kind of transformer
### Using Custom Transformers
```ts
Transformers can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`. Here is an example:
```ts title="packages/backend/src/index.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-msgraph/alpha';
import {
myUserTransformer,
myGroupTransformer,
myOrganizationTransformer,
} from './transformers';
backend.add(
createBackendModule({
pluginId: 'catalog',
moduleId: 'microsoft-graph-extensions',
register(env) {
env.registerInit({
deps: {
/* highlight-add-start */
microsoftGraphTransformers:
microsoftGraphOrgEntityProviderTransformExtensionPoint,
/* highlight-add-end */
},
async init({ microsoftGraphTransformers }) {
/* highlight-add-start */
microsoftGraphTransformers.setUserTransformer(myUserTransformer);
microsoftGraphTransformers.setGroupTransformer(myGroupTransformer);
microsoftGraphTransformers.setOrganizationTransformer(
myOrganizationTransformer,
);
/* highlight-add-end */
},
});
},
}),
);
```
The `myUserTransformer`, `myGroupTransformer`, and `myOrganizationTransformer` transformer functions are from the examples in the section below.
### Transformer Examples
The following provides an example of each kind of transformer. We recommend creating a `transformers.ts` file in your `packages/backend/src` folder for these.
```ts title="packages/backend/src/transformers.ts"
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
import {
defaultGroupTransformer,
@@ -27,7 +27,7 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbuck
### Installation with New Backend System
```ts
// optional if you want HTTP endpojnts to receive external events
// optional if you want HTTP endpoints to receive external events
// backend.add(import('@backstage/plugin-events-backend/alpha'));
// optional if you want to use AWS SQS instead of HTTP endpoints to receive external events
// backend.add(import('@backstage/plugin-events-backend-module-aws-sqs/alpha'));
+11 -3
View File
@@ -22,10 +22,18 @@ integrations:
appPassword: ${BITBUCKET_CLOUD_PASSWORD}
```
> Note: A public Bitbucket Cloud provider is added automatically at startup for
> convenience, so you only need to list it if you want to supply credentials.
:::note Note
> Note: The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work
A public Bitbucket Cloud provider is added automatically at startup for
convenience, so you only need to list it if you want to supply credentials.
:::
:::note Note
The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work.
:::
Directly under the `bitbucketCloud` key is a list of provider configurations, where
you can list the Bitbucket Cloud providers you want to fetch data from.
@@ -22,6 +22,8 @@ app:
applicationId: qwerty
# site: datadoghq.eu
# env: 'staging'
# sessionSampleRate: 100
# sessionReplaySampleRate: 0
```
If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually.
+376
View File
@@ -0,0 +1,376 @@
---
id: discovery--old
title: GitHub Discovery
sidebar_label: Discovery
# prettier-ignore
description: Automatically discovering catalog entities from repositories in a GitHub organization
---
:::info
This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
## GitHub Provider
The GitHub integration has a discovery provider for discovering catalog
entities within a GitHub organization. The provider will crawl the GitHub
organization and register entities matching the configured path. This can be
useful as an alternative to static locations or manually adding things to the
catalog. This is the preferred method for ingesting entities into the catalog.
## Installation without Events Support
You will have to add the provider in the catalog initialization code of your
backend. They are not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package.
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
And then add the entity provider to your catalog builder:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
builder.addEntityProvider(
GithubEntityProvider.fromConfig(env.config, {
logger: env.logger,
scheduler: env.scheduler,
}),
);
/* highlight-add-end */
// ..
}
```
## Installation with Events Support
_For the legacy backend system, please read the sub-section below._
The catalog module for GitHub comes with events support enabled.
This will make it subscribe to its relevant topics (`github.push`)
and expects these events to be published via the `EventsService`.
Additionally, you should install the
[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md)
which will route received events from the generic topic `github` to more specific ones
based on the event type (e.g., `github.push`).
In order to receive Webhook events by GitHub, you have to decide how you want them
to be ingested into Backstage and published to its `EventsService`.
You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
### Legacy Backend System
Please follow the installation instructions at
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md>
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md>
Additionally, you need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
Set up your provider
```ts title="packages/backend/src/plugins/catalog.ts"
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
/* highlight-add-next-line */
import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
/* highlight-add-start */
const githubProvider = GithubEntityProvider.fromConfig(env.config, {
events: env.events,
logger: env.logger,
scheduler: env.scheduler,
});
builder.addEntityProvider(githubProvider);
/* highlight-add-end */
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
```
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events.
## Configuration
To use the discovery provider, you'll need a GitHub integration
[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). For Personal Access Tokens you should pay attention to the [required scopes](https://backstage.io/docs/integrations/github/locations/#token-scopes), where you will need at least the `repo` scope for reading components. For GitHub Apps you will need to grant it the [required permissions](https://backstage.io/docs/integrations/github/github-apps#app-permissions) instead, where you will need at least the `Contents: Read-only` permissions for reading components.
Then you can add a `github` config to the catalog providers configuration:
```yaml
catalog:
providers:
github:
# the provider ID can be any camelCase string
providerId:
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
filters:
branch: 'main' # string
repository: '.*' # Regex
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
customProviderId:
organization: 'new-org' # string
catalogPath: '/custom/path/catalog-info.yaml' # string
filters: # optional filters
branch: 'develop' # optional string
repository: '.*' # optional Regex
wildcardProviderId:
organization: 'new-org' # string
catalogPath: '/groups/**/*.yaml' # this will search all folders for files that end in .yaml
filters: # optional filters
branch: 'develop' # optional string
repository: '.*' # optional Regex
topicProviderId:
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
filters:
branch: 'main' # string
repository: '.*' # Regex
topic: 'backstage-exclude' # optional string
topicFilterProviderId:
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
filters:
branch: 'main' # string
repository: '.*' # Regex
topic:
include: ['backstage-include'] # optional array of strings
exclude: ['experiments'] # optional array of strings
validateLocationsExist:
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
filters:
branch: 'main' # string
repository: '.*' # Regex
validateLocationsExist: true # optional boolean
visibilityProviderId:
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
filters:
visibility:
- public
- internal
enterpriseProviderId:
host: ghe.example.net
organization: 'backstage' # string
catalogPath: '/catalog-info.yaml' # string
```
This provider supports multiple organizations via unique provider IDs.
> **Note:** It is possible but certainly not recommended to skip the provider ID level.
> If you do so, `default` will be used as provider ID.
- **`catalogPath`** _(optional)_:
Default: `/catalog-info.yaml`.
Path where to look for `catalog-info.yaml` files.
You can use wildcards - `*` or `**` - to search the path and/or the filename.
Wildcards cannot be used if the `validateLocationsExist` option is set to `true`.
- **`filters`** _(optional)_:
- **`branch`** _(optional)_:
String used to filter results based on the branch name.
- **`repository`** _(optional)_:
Regular expression used to filter results based on the repository name.
- **`topic`** _(optional)_:
Both of the filters below may be used at the same time but the exclusion filter has the highest priority.
In the example above, a repository with the `backstage-include` topic would still be excluded
if it were also carrying the `experiments` topic.
- **`include`** _(optional)_:
An array of strings used to filter in results based on their associated GitHub topics.
If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested
- **`exclude`** _(optional)_:
An array of strings used to filter out results based on their associated GitHub topics.
If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested.
- **`visibility`** _(optional)_:
An array of strings used to filter results based on their visibility. Available options are `private`, `internal`, `public`. If configured (non empty), only repositories with visibility present in the filter will be ingested
- **`host`** _(optional)_:
The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md).
- **`organization`**:
Name of your organization account/workspace.
If you want to add multiple organizations, you need to add one provider config each.
- **`validateLocationsExist`** _(optional)_:
Whether to validate locations that exist before emitting them.
This option avoids generating locations for catalog info files that do not exist in the source repository.
Defaults to `false`.
Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in
conjunction with wildcards in the `catalogPath`.
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
The maximum amount of time that a single task invocation can take.
- **`initialDelay`** _(optional)_:
The amount of time that should pass before the first invocation happens.
- **`scope`** _(optional)_:
`'global'` or `'local'`. Sets the scope of concurrency control.
## GitHub API Rate Limits
GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise
accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location.
If your requests are too frequent then you may get throttled by
rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`.
```yaml
schedule:
frequency: { minutes: 35 }
timeout: { minutes: 3 }
```
More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page.
Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication
which carries a much higher rate limit at GitHub.
This is true for any method of adding GitHub entities to the catalog, but
especially easy to hit with automatic discovery.
## GitHub Processor (To Be Deprecated)
The GitHub integration has a special discovery processor for discovering catalog
entities within a GitHub organization. The processor will crawl the GitHub
organization and register entities matching the configured path. This can be
useful as an alternative to static locations or manually adding things to the
catalog.
## Installation
You will have to add the processors in the catalog initialization code of your
backend. They are not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package, plus `@backstage/integration` for the basic credentials management:
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github
```
And then add the processors to your catalog builder:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-start */
import {
GithubDiscoveryProcessor,
GithubOrgReaderProcessor,
} from '@backstage/plugin-catalog-backend-module-github';
import {
ScmIntegrations,
DefaultGithubCredentialsProvider,
} from '@backstage/integration';
/* highlight-add-end */
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
const integrations = ScmIntegrations.fromConfig(env.config);
const githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
builder.addProcessor(
GithubDiscoveryProcessor.fromConfig(env.config, {
logger: env.logger,
githubCredentialsProvider,
}),
GithubOrgReaderProcessor.fromConfig(env.config, {
logger: env.logger,
githubCredentialsProvider,
}),
);
/* highlight-add-end */
// ..
}
```
## Configuration
To use the discovery processor, you'll need a GitHub integration
[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md).
Then you can add a location target to the catalog configuration:
```yaml
catalog:
locations:
# (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch
- type: github-discovery
target: https://github.com/myorg
# Or use a custom pattern for a subset of all repositories with default repository
- type: github-discovery
target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml
# Or use a custom file format and location
- type: github-discovery
target: https://github.com/*/blob/-/docs/your-own-format.yaml
# Or use a specific branch-name
- type: github-discovery
target: https://github.com/*/blob/backstage-docs/catalog-info.yaml
```
Note the `github-discovery` type, as this is not a regular `url` processor.
When using a custom pattern, the target is composed of three parts:
- The base organization URL, `https://github.com/myorg` in this case
- The repository blob to scan, which accepts \* wildcard tokens. This can simply
be `*` to scan all repositories in the organization. This example only looks
for repositories prefixed with `service-`.
- The path within each repository to find the catalog YAML file. This will
usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or
a similar variation for catalog files stored in the root directory of each
repository. You could also use a dash (`-`) for referring to the default
branch.
## GitHub API Rate Limits
GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise
accounts). The default Backstage catalog backend refreshes data every 100
seconds, which issues an API request for each discovered location.
This means if you have more than ~140 catalog entities, you may get throttled by
rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file:
```typescript
const builder = await CatalogBuilder.create(env);
// For example, to refresh every 5 minutes (300 seconds).
builder.setProcessingIntervalSeconds(300);
```
Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication
which carries a much higher rate limit at GitHub.
This is true for any method of adding GitHub entities to the catalog, but
especially easy to hit with automatic discovery.
+15 -168
View File
@@ -6,6 +6,10 @@ sidebar_label: Discovery
description: Automatically discovering catalog entities from repositories in a GitHub organization
---
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
## GitHub Provider
The GitHub integration has a discovery provider for discovering catalog
@@ -14,10 +18,9 @@ organization and register entities matching the configured path. This can be
useful as an alternative to static locations or manually adding things to the
catalog. This is the preferred method for ingesting entities into the catalog.
## Installation without Events Support
## Installation
You will have to add the provider in the catalog initialization code of your
backend. They are not installed by default, therefore you have to add a
You will have to add the GitHub Entity provider to your backend as it is not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package.
@@ -29,13 +32,12 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
And then update your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
// github discovery
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha'));
```
## Installation with Events Support
_For the legacy backend system, please read the sub-section below._
## Events Support
The catalog module for GitHub comes with events support enabled.
This will make it subscribe to its relevant topics (`github.push`)
@@ -53,47 +55,6 @@ You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
### Legacy Backend System
Please follow the installation instructions at
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md>
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md>
Additionally, you need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
Set up your provider
```ts title="packages/backend/src/plugins/catalog.ts"
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
/* highlight-add-next-line */
import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
/* highlight-add-start */
const githubProvider = GithubEntityProvider.fromConfig(env.config, {
events: env.events,
logger: env.logger,
scheduler: env.scheduler,
});
builder.addEntityProvider(githubProvider);
/* highlight-add-end */
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
```
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events.
## Configuration
@@ -169,8 +130,12 @@ catalog:
This provider supports multiple organizations via unique provider IDs.
> **Note:** It is possible but certainly not recommended to skip the provider ID level.
> If you do so, `default` will be used as provider ID.
:::note Note
It is possible but certainly not recommended to skip the provider ID level.
If you do so, `default` will be used as provider ID.
:::
- **`catalogPath`** _(optional)_:
Default: `/catalog-info.yaml`.
@@ -236,121 +201,3 @@ which carries a much higher rate limit at GitHub.
This is true for any method of adding GitHub entities to the catalog, but
especially easy to hit with automatic discovery.
## GitHub Processor (To Be Deprecated)
The GitHub integration has a special discovery processor for discovering catalog
entities within a GitHub organization. The processor will crawl the GitHub
organization and register entities matching the configured path. This can be
useful as an alternative to static locations or manually adding things to the
catalog.
## Installation
You will have to add the processors in the catalog initialization code of your
backend. They are not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package, plus `@backstage/integration` for the basic credentials management:
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github
```
And then add the processors to your catalog builder:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-start */
import {
GithubDiscoveryProcessor,
GithubOrgReaderProcessor,
} from '@backstage/plugin-catalog-backend-module-github';
import {
ScmIntegrations,
DefaultGithubCredentialsProvider,
} from '@backstage/integration';
/* highlight-add-end */
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
const integrations = ScmIntegrations.fromConfig(env.config);
const githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
builder.addProcessor(
GithubDiscoveryProcessor.fromConfig(env.config, {
logger: env.logger,
githubCredentialsProvider,
}),
GithubOrgReaderProcessor.fromConfig(env.config, {
logger: env.logger,
githubCredentialsProvider,
}),
);
/* highlight-add-end */
// ..
}
```
## Configuration
To use the discovery processor, you'll need a GitHub integration
[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md).
Then you can add a location target to the catalog configuration:
```yaml
catalog:
locations:
# (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch
- type: github-discovery
target: https://github.com/myorg
# Or use a custom pattern for a subset of all repositories with default repository
- type: github-discovery
target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml
# Or use a custom file format and location
- type: github-discovery
target: https://github.com/*/blob/-/docs/your-own-format.yaml
# Or use a specific branch-name
- type: github-discovery
target: https://github.com/*/blob/backstage-docs/catalog-info.yaml
```
Note the `github-discovery` type, as this is not a regular `url` processor.
When using a custom pattern, the target is composed of three parts:
- The base organization URL, `https://github.com/myorg` in this case
- The repository blob to scan, which accepts \* wildcard tokens. This can simply
be `*` to scan all repositories in the organization. This example only looks
for repositories prefixed with `service-`.
- The path within each repository to find the catalog YAML file. This will
usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or
a similar variation for catalog files stored in the root directory of each
repository. You could also use a dash (`-`) for referring to the default
branch.
## GitHub API Rate Limits
GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise
accounts). The default Backstage catalog backend refreshes data every 100
seconds, which issues an API request for each discovered location.
This means if you have more than ~140 catalog entities, you may get throttled by
rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file:
```typescript
const builder = await CatalogBuilder.create(env);
// For example, to refresh every 5 minutes (300 seconds).
builder.setProcessingIntervalSeconds(300);
```
Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication
which carries a much higher rate limit at GitHub.
This is true for any method of adding GitHub entities to the catalog, but
especially easy to hit with automatic discovery.
+39 -8
View File
@@ -31,7 +31,7 @@ A GitHub app created with the cli will have read
access by default. You have to manually update the GitHub App settings in GitHub
to grant the app more permissions if needed.
### Using the CLI (public GitHub only)
## Using the CLI (public GitHub only)
You can use the `backstage-cli` to create a GitHub App using a manifest file
that we provide. This gives us a way to automate some of the work required to
@@ -42,7 +42,7 @@ yarn backstage-cli create-github-app <github org>
```
You can read more about the
[`backstage-cli create-github-app`](../../local-dev/cli-commands.md#create-github-app) command.
[`backstage-cli create-github-app`](../../tooling/cli/03-commands.md#create-github-app) command.
Once you've gone through the CLI command, it should produce a YAML file in the
root of the project which you can then use as an `include` in your
@@ -53,7 +53,7 @@ Note that the created app will have a webhook that is disabled by default and
points to `smee.io`, which is intended for local development. There's also
currently no part of Backstage that makes use of the webhook.
### GitHub Enterprise
## GitHub Enterprise
You have to create the GitHub Application manually using these
[instructions](https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app)
@@ -76,7 +76,7 @@ privateKey: |
-----END RSA PRIVATE KEY-----
```
### Including in Integrations Config
## Including in Integrations Config
Once the credentials are stored in a YAML file generated by `create-github-app`,
or manually by following the [GitHub Enterprise](#github-enterprise)
@@ -95,7 +95,25 @@ integrations:
- $include: example-backstage-app-credentials.yaml
```
### Limiting the GitHub App installations
Alternatively you can use environment variables as well:
```yaml
integrations:
github:
- host: github.com
apps:
- appId: ${AUTH_ORG_APP_ID}
clientId: ${AUTH_ORG_CLIENT_ID}
clientSecret: ${AUTH_ORG_CLIENT_SECRET}
privateKey: ${AUTH_ORG1_PRIVATE_KEY}
webhookSecret: ${AUTH_ORG_WEBHOOK_SECRET}
```
:::Note
Note that in both examples above `apps` is an array which means you can add multiple GitHub Apps using `$include` or environment variables as long as they are each for a different GitHub Org as mentioned under the [Caveats](#caveats) section
:::
## Limiting the GitHub App installations
If you want to limit the GitHub app installations visible to backstage you may
optionally include the `allowedInstallationOwners` option. If you configure
@@ -117,7 +135,7 @@ privateKey: |
This will result in backstage preventing the use of any installation that is not
within the allow list.
### App permissions
## App permissions
When creating a GitHub App, you must select permissions to define the level of
access for the app. The permissions required vary depending on your use of the
@@ -125,6 +143,7 @@ integration:
- Reading software components:
- `Contents`: `Read-only`
- `Commit statuses`: `Read-only`
- Reading organization data:
- `Members`: `Read-only`
- Publishing software templates:
@@ -135,12 +154,24 @@ integration:
- `Pull requests`: `Read & write`
- `Issues`: `Read & write`
- `Workflows`: `Read & write` (if templates include GitHub workflows)
- `Commit statuses`: `Read-only`
- `Variables`: `Read & write` (if templates include GitHub Action Repository Variables)
- `Secrets`: `Read & write` (if templates include GitHub Action Repository Secrets)
- `Environments`: `Read & write` (if templates include GitHub Environments)
### Troubleshooting
## Updating Permissions
There may be times where you need to update the permissions for your GitHub App, to easily get at the GitHub App you can find it at this URL:
```sh
https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions
```
**Please note that when you change permissions, the app owner will get an email
that must be approved first before the changes are applied.**
![email](../../assets/integrations/github/email.png)
## Troubleshooting
`HttpError: This endpoint requires you to be authenticated.`
+5 -3
View File
@@ -28,9 +28,11 @@ integrations:
token: ${GHE_TOKEN}
```
> Note: A public GitHub provider is added automatically at startup for
> convenience, so you only need to list it if you want to supply a
> [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
:::note Note
A public GitHub provider is added automatically at startup for convenience, so you only need to list it if you want to supply a [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
:::
Directly under the `github` key is a list of provider configurations, where you
can list the various GitHub-compatible providers you want to be able to fetch
+367
View File
@@ -0,0 +1,367 @@
---
id: org--old
title: GitHub Organizational Data
sidebar_label: Org Data
# prettier-ignore
description: Importing users and groups from a GitHub organization into Backstage
---
:::info
This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from an organization in GitHub or GitHub Enterprise. The result
is a hierarchy of
[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
entities that mirror your org setup.
> Note: This adds `User` and `Group` entities to the catalog, but does not
> provide authentication. See the
> [GitHub auth provider](../../auth/github/provider.md) for that.
## Installation without Events Support
This guide will use the Entity Provider method. If you for some reason prefer
the Processor method (not recommended), it is described separately below.
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-github` to your backend package.
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
> Note: When configuring to use a Provider instead of a Processor you do not
> need to add a _location_ pointing to your GitHub server/organization
Update the catalog plugin initialization in your backend to add the provider and
schedule it:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The org URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```
Alternatively, if you wish to ingest data from multiple GitHub organizations you can use
the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace
groups according to the org they originate from to avoid potential name duplicates:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The GitHub URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubMultiOrgEntityProvider.fromConfig(env.config, {
id: 'production',
githubUrl: 'https://github.com',
// Set the following to list the GitHub orgs you wish to ingest from. You can
// also omit this option to ingest all orgs accessible by your GitHub integration
orgs: ['org-a', 'org-b'],
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```
## Installation with Events Support
_For the legacy backend system, please read the subsection below._
The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`.
This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`.
Topics:
- `github.installation`
- `github.membership`
- `github.organization`
- `github.team`
Additionally, you should install the
[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md)
which will route received events from the generic topic `github` to more specific ones
based on the event type (e.g., `github.membership`).
In order to receive Webhook events by GitHub, you have to decide how you want them
to be ingested into Backstage and published to its `EventsService`.
You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
### Legacy Backend System
Please follow the installation instructions at
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md>
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md>
Additionally, you need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
Set up your provider
```ts title="packages/backend/src/plugins/catalog.ts"
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
/* highlight-add-next-line */
import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
/* highlight-add-start */
const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
events: env.events,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
});
builder.addEntityProvider(githubOrgProvider);
/* highlight-add-end */
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
```
Or, alternatively, if using the `GithubMultiOrgEntityProvider`:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The GitHub URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubMultiOrgEntityProvider.fromConfig(env.config, {
id: 'production',
githubUrl: 'https://github.com',
// Set the following to list the GitHub orgs you wish to ingest from. You can
// also omit this option to ingest all orgs accessible by your GitHub integration
orgs: ['org-a', 'org-b'],
logger: env.logger,
events: env.events,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
The webhook will need to be configured to forward `organization`,`team` and `membership` events.
## Configuration
As mentioned above, you also must have some configuration in your app-config
that describes the targets that you want to import. This lets the entity
provider know what authorization to use, and what the API endpoints are. You may
or may not have such an entry already added since before:
```yaml
integrations:
github:
# example for public github
- host: github.com
token: ${GITHUB_TOKEN}
# example for a private GitHub Enterprise instance
- host: ghe.example.net
apiBaseUrl: https://ghe.example.net/api/v3
token: ${GHE_TOKEN}
```
These examples use `${}` placeholders to reference environment variables. This
is often suitable for production setups, but also means that you will have to
supply those variables to the backend as it starts up. If you want, for local
development in particular, you can experiment first by putting the actual tokens
in a mirrored config directly in your `app-config.local.yaml` as well.
If Backstage is configured to use GitHub Apps authentication you must grant
`Read-Only` access for `Members` under `Organization` in order to ingest users
correctly. You can modify the app's permissions under the organization settings,
`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`.
![permissions](../../assets/integrations/github/permissions.png)
**Please note that when you change permissions, the app owner will get an email
that must be approved first before the changes are applied.**
![email](../../assets/integrations/github/email.png)
### Custom Transformers
You can inject your own transformation logic to help map from GH API responses
into backstage entities. You can do this on the user and team requests to
enable you to do further processing or updates to the entities.
To enable this you pass a function into the `GitHubOrgEntityProvider`. You can
pass a `UserTransformer`, `TeamTransformer` or both. The function is invoked
for each item (user or team) that is returned from the API. You can either
return an Entity (User or Group) or `undefined` if you do not want to import
that item.
There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer`.
You could use these and simply decorate the response from the default
transformation if you only need to change a few properties.
### Resolving GitHub users via organization email
When you authenticate users you should resolve them to an entity within the
catalog. Often the authentication you use could be a corporate SSO system that
provides you with email as a key. To enable you to find and resolve GitHub users
it's useful to also import the private domain verified emails into the User
entity in backstage.
The integration attempts to return `organizationVerifiedDomainEmails` from the
GitHub API and makes this available as part of the object passed to
`UserTransformer`. The GitHub API will only return emails that use a domain
that's a verified domain for your GitHub Org. It also relies on the user having
configured such an email in their own account. The API will only return these
values when using GitHub App authentication and with the correct app permission
allowing access to emails.
You can decorate the default `userTransformer` to replace the org email in the
returned identity.
```ts title="packages/backend/src/plugins/catalog.ts"
const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
/* highlight-add-start */
userTransformer: async (user, ctx) => {
const entity = await defaultUserTransformer(user, ctx);
if (entity && user.organizationVerifiedDomainEmails?.length) {
entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0];
}
return entity;
},
/* highlight-add-end */
});
```
Once you have imported the emails you can resolve users in your [sign-in resolver](../../auth/github/provider.md) using the catalog entity search via email
```typescript title="packages/backend/src/plugins/auth.ts"
ctx.signInWithCatalogUser({
filter: {
kind: ['User'],
'spec.profile.email': email as string,
},
});
```
## Using a Processor instead of a Provider
An alternative to using the Provider for ingesting organizational entities is to
use a Processor. This is the old way that's based on registering locations with
the proper type and target, triggering the processor to run.
The drawback of this method is that it will leave orphaned Group/User entities
whenever they are deleted on your GitHub server, and you cannot control the
frequency with which they are refreshed, separately from other processors.
### Processor Installation
The `GithubOrgReaderProcessor` is not registered by default, so you have to
install and register it in the catalog plugin:
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
```typescript title="packages/backend/src/plugins/catalog.ts"
import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github';
builder.addProcessor(
GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }),
);
```
### Processor Configuration
The integration section of your app-config needs to be set up in the same way as
for the Entity Provider - see above.
In addition to that, you typically want to add a few static locations to your
app-config, which reference your organizations to import. The following
configuration enables an import of the teams and users under the org
`https://github.com/my-org-name` on public GitHub.
```yaml
catalog:
locations:
- type: github-org
target: https://github.com/my-org-name
rules:
- allow: [User, Group]
```
+160 -265
View File
@@ -6,6 +6,10 @@ sidebar_label: Org Data
description: Importing users and groups from a GitHub organization into Backstage
---
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage catalog can be set up to ingest organizational data - users and
teams - directly from an organization in GitHub or GitHub Enterprise. The result
is a hierarchy of
@@ -13,99 +17,89 @@ is a hierarchy of
[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
entities that mirror your org setup.
> Note: This adds `User` and `Group` entities to the catalog, but does not
> provide authentication. See the
> [GitHub auth provider](../../auth/github/provider.md) for that.
:::note Note
## Installation without Events Support
This adds `User` and `Group` entities to the catalog, but does not
provide authentication. See the
[GitHub auth provider](../../auth/github/provider.md) for that.
This guide will use the Entity Provider method. If you for some reason prefer
the Processor method (not recommended), it is described separately below.
:::
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-github` to your backend package.
## Permissions
Prior to installing the GitHub Org provider you should confirm you have the right permissions:
- Personal Access Token permissions are listed in the [GitHub Locations](./locations.md#token-scopes) documentation
- GitHub App(s) permissions are listed in the [GitHub Apps](./github-apps.md#app-permissions) documentation
## Installation
You will have to add the GitHub Org provider to your backend as it is not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github-org` to your backend
package.
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github-org
```
> Note: When configuring to use a Provider instead of a Processor you do not
> need to add a _location_ pointing to your GitHub server/organization
Next add the basic configuration to `app-config.yaml`
Update the catalog plugin initialization in your backend to add the provider and
schedule it:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The org URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```yaml title="app-config.yaml"
catalog:
providers:
githubOrg:
id: production
githubUrl: https://github.com
orgs: ['organization-1', 'organization-2', 'organization-3']
schedule:
initialDelay: { seconds: 30 }
frequency: { hours: 1 }
timeout: { minutes: 50 }
```
Alternatively, if you wish to ingest data from multiple GitHub organizations you can use
the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace
groups according to the org they originate from to avoid potential name duplicates:
Finally, update your backend by adding the following line:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The GitHub URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubMultiOrgEntityProvider.fromConfig(env.config, {
id: 'production',
githubUrl: 'https://github.com',
// Set the following to list the GitHub orgs you wish to ingest from. You can
// also omit this option to ingest all orgs accessible by your GitHub integration
orgs: ['org-a', 'org-b'],
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-github-org'));
```
## Installation with Events Support
### Configuration Details
_For the legacy backend system, please read the subsection below._
In the installation steps above we included an simple example of the needed configuration. The section goes into more details about the various configuration options.
The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`.
```yaml title="app-config.yaml"
catalog:
providers:
githubOrg:
- id: github
githubUrl: https://github.com
orgs: ['organization-1', 'organization-2', 'organization-3']
schedule:
initialDelay: { seconds: 30 }
frequency: { hours: 1 }
timeout: { minutes: 50 }
- id: ghe
githubUrl: https://ghe.mycompany.com
orgs: ['internal-1', 'internal-2', 'internal-3']
schedule:
initialDelay: { seconds: 30 }
frequency: { hours: 1 }
timeout: { minutes: 50 }
```
Directly under the `githubOrg` is a list of configurations, each entry is a structure with the following elements:
- `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts.
- `githubUrl`: The target that this provider should consume
- `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only).
- `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/)
### Events Support
The catalog module for GitHub Org comes with events support enabled.
This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`.
Topics:
@@ -127,128 +121,10 @@ You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
### Legacy Backend System
Please follow the installation instructions at
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md>
- <https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md>
Additionally, you need to decide how you want to receive events from external sources like
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
Set up your provider
```ts title="packages/backend/src/plugins/catalog.ts"
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
/* highlight-add-next-line */
import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
/* highlight-add-start */
const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
events: env.events,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
});
builder.addEntityProvider(githubOrgProvider);
/* highlight-add-end */
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
```
Or, alternatively, if using the `GithubMultiOrgEntityProvider`:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The GitHub URL below needs to match a configured integrations.github entry
// specified in your app-config.
builder.addEntityProvider(
GithubMultiOrgEntityProvider.fromConfig(env.config, {
id: 'production',
githubUrl: 'https://github.com',
// Set the following to list the GitHub orgs you wish to ingest from. You can
// also omit this option to ingest all orgs accessible by your GitHub integration
orgs: ['org-a', 'org-b'],
logger: env.logger,
events: env.events,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
The webhook will need to be configured to forward `organization`,`team` and `membership` events.
## Configuration
As mentioned above, you also must have some configuration in your app-config
that describes the targets that you want to import. This lets the entity
provider know what authorization to use, and what the API endpoints are. You may
or may not have such an entry already added since before:
```yaml
integrations:
github:
# example for public github
- host: github.com
token: ${GITHUB_TOKEN}
# example for a private GitHub Enterprise instance
- host: ghe.example.net
apiBaseUrl: https://ghe.example.net/api/v3
token: ${GHE_TOKEN}
```
These examples use `${}` placeholders to reference environment variables. This
is often suitable for production setups, but also means that you will have to
supply those variables to the backend as it starts up. If you want, for local
development in particular, you can experiment first by putting the actual tokens
in a mirrored config directly in your `app-config.local.yaml` as well.
If Backstage is configured to use GitHub Apps authentication you must grant
`Read-Only` access for `Members` under `Organization` in order to ingest users
correctly. You can modify the app's permissions under the organization settings,
`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`.
![permissions](../../assets/integrations/github/permissions.png)
**Please note that when you change permissions, the app owner will get an email
that must be approved first before the changes are applied.**
![email](../../assets/integrations/github/email.png)
### Custom Transformers
## Custom Transformers
You can inject your own transformation logic to help map from GH API responses
into backstage entities. You can do this on the user and team requests to
@@ -264,6 +140,81 @@ There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer
You could use these and simply decorate the response from the default
transformation if you only need to change a few properties.
Here's an example of how to use the transformers:
```ts title="packages/backend/src/index.ts"
import { createBackend } from '@backstage/backend-defaults';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org';
import { myTeamTransformer, myUserTransformer } from './transformers';
const githubOrgModule = createBackendModule({
pluginId: 'catalog',
moduleId: 'github-org-extensions',
register(env) {
env.registerInit({
deps: {
githubOrg: githubOrgEntityProviderTransformsExtensionPoint,
},
async init({ githubOrg }) {
githubOrg.setTeamTransformer(myTeamTransformer);
githubOrg.setUserTransformer(myUserTransformer);
},
});
},
});
const backend = createBackend();
// Other items
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(githubOrgModule());
backend.start();
```
The `myTeamTransformer` and `myUserTransformer` transformer functions are from the examples in the section below.
### Transformer Examples
The following provides an example of each kind of transformer. We recommend creating a `transformers.ts` file in your `packages/backend/src` folder for these.
```ts title="packages/backend/src/transformers.ts"
import {
TeamTransformer,
UserTransformer,
defaultUserTransformer,
} from '@backstage/plugin-catalog-backend-module-github';
// This team transformer completely replaces the built in logic with custom logic.
export const myTeamTransformer: TeamTransformer = async team => {
return {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
metadata: {
name: team.slug,
annotations: {},
},
spec: {
type: 'GitHub Org Team',
profile: {},
children: [],
},
};
};
// This user transformer makes use of the built in logic, but also sets the description field
export const myUserTransformer: UserTransformer = async (user, ctx) => {
const backstageUser = await defaultUserTransformer(user, ctx);
if (backstageUser) {
backstageUser.metadata.description = 'Loaded from GitHub Org Data';
}
return backstageUser;
};
```
### Resolving GitHub users via organization email
When you authenticate users you should resolve them to an entity within the
@@ -283,31 +234,22 @@ allowing access to emails.
You can decorate the default `userTransformer` to replace the org email in the
returned identity.
```ts title="packages/backend/src/plugins/catalog.ts"
const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, {
id: 'production',
orgUrl: 'https://github.com/backstage',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
/* highlight-add-start */
userTransformer: async (user, ctx) => {
const entity = await defaultUserTransformer(user, ctx);
if (entity && user.organizationVerifiedDomainEmails?.length) {
entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0];
}
return entity;
},
/* highlight-add-end */
});
```ts title="packages/backend/src/transformers.ts"
export const myVerifiedUserTransformer: UserTransformer = async (user, ctx) => {
const backstageUser = await defaultUserTransformer(user, ctx);
if (backstageUser && user.organizationVerifiedDomainEmails?.length) {
backstageUser.spec.profile!.email =
user.organizationVerifiedDomainEmails[0];
}
return backstageUser;
};
```
Once you have imported the emails you can resolve users in your [sign-in
resolver](../../auth/github/provider.md) using the catalog entity search via email
This example assumes you have implemented the custom transformer following the [Custom Transformers](#custom-transformers) and [Transformer Examples](#transformer-examples) documentation in the sections above.
```typescript title="packages/backend/src/plugins/auth.ts"
Once you have imported the emails you can resolve users by building a [Custom Resolver](../../auth/identity-resolver.md#building-custom-resolvers). In this custom resolver you can then use this example to properly match the user:
```ts
ctx.signInWithCatalogUser({
filter: {
kind: ['User'],
@@ -315,50 +257,3 @@ ctx.signInWithCatalogUser({
},
});
```
## Using a Processor instead of a Provider
An alternative to using the Provider for ingesting organizational entities is to
use a Processor. This is the old way that's based on registering locations with
the proper type and target, triggering the processor to run.
The drawback of this method is that it will leave orphaned Group/User entities
whenever they are deleted on your GitHub server, and you cannot control the
frequency with which they are refreshed, separately from other processors.
### Processor Installation
The `GithubOrgReaderProcessor` is not registered by default, so you have to
install and register it in the catalog plugin:
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
```typescript title="packages/backend/src/plugins/catalog.ts"
import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github';
builder.addProcessor(
GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }),
);
```
### Processor Configuration
The integration section of your app-config needs to be set up in the same way as
for the Entity Provider - see above.
In addition to that, you typically want to add a few static locations to your
app-config, which reference your organizations to import. The following
configuration enables an import of the teams and users under the org
`https://github.com/my-org-name` on public GitHub.
```yaml
catalog:
locations:
- type: github-org
target: https://github.com/my-org-name
rules:
- allow: [User, Group]
```
+5 -1
View File
@@ -136,7 +136,11 @@ To use the discovery provider, you'll need a GitLab integration
[set up](locations.md) with a `token`. Then you can add a provider config per group
to the catalog configuration.
> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
:::note Note
If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
:::
```yaml title="app-config.yaml"
catalog:
+5 -3
View File
@@ -19,9 +19,11 @@ integrations:
token: ${GITLAB_TOKEN}
```
> Note: A public GitLab provider is added automatically at startup for
> convenience, so you only need to list it if you want to supply a
> [token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html).
:::note Note
A public GitLab provider is added automatically at startup for convenience, so you only need to list it if you want to supply a [token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html).
:::
Directly under the `gitlab` key is a list of provider configurations, where you
can list the GitLab providers you want to fetch data from. Each entry is a
+5 -1
View File
@@ -158,7 +158,11 @@ amount of data, this can take significant time and resources.
The token used must have the `read_api` scope, and the Users and Groups fetched
will be those visible to the account which provisioned the token.
> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
:::note Note
If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
:::
```yaml
catalog:
+33
View File
@@ -0,0 +1,33 @@
---
id: locations
title: Harness Locations
sidebar_label: Locations
description: Integrating source code stored in Harness Code into the Backstage catalog
---
The Harness Code integration supports loading catalog entities from a hosted repository. Entities can be added to
[static catalog configuration](../../features/software-catalog/configuration.md),
registered with the
[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import)
plugin.
## Configuration
To use this integration, add configuration to your root `app-config.yaml`:
```yaml
integrations:
harness:
- host: app.harness.io
token: ${HARNESS_CODE_BEARER_TOKEN}
apiKey: ${HARNESS_CODE_APIKEY}
```
Directly under the `harness` key is a list of provider configurations, where you
can list the Harness instances you want to be able to fetch
check out https://developer.harness.io/docs/platform/automation/api/add-and-manage-api-keys/ for more information
- `host`: The host of the Harness Code instance that you want to match on.
- `token` (optional): The password or api token to authenticate with.
- `apiKey` (optional): The apiKey to authenticate with.
+417
View File
@@ -0,0 +1,417 @@
---
id: org--old
title: LDAP Organizational Data
sidebar_label: Org Data
# prettier-ignore
description: Setting up ingestion of organizational data from LDAP
---
The Backstage catalog can be set up to ingest organizational data - users and
groups - directly from an LDAP compatible service. The result is a hierarchy of
[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
entities that mirror your org setup.
## Supported vendors
Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md).
## Installation
This guide will use the Entity Provider method. If you for some reason prefer
the Processor method (not recommended), it is described separately below.
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
```bash
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
```
:::note Note
When configuring to use a Provider instead of a Processor you do not
need to add a _location_ pointing to your LDAP server
:::
Update the catalog plugin initialization in your backend to add the provider and
schedule it:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The target parameter below needs to match the ldap.providers.target
// value specified in your app-config.
builder.addEntityProvider(
LdapOrgEntityProvider.fromConfig(env.config, {
id: 'our-ldap-master',
target: 'ldaps://ds.example.net',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```
After this, you also have to add some configuration in your app-config that
describes what you want to import for that target.
## Configuration
The following configuration is a small example of how a setup could look for
importing groups and users from a corporate LDAP server.
```yaml
ldap:
providers:
- target: ldaps://ds.example.net
bind:
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
secret: ${LDAP_SECRET}
users:
dn: ou=people,ou=example,dc=example,dc=net
options:
filter: (uid=*)
map:
description: l
set:
metadata.customField: 'hello'
groups:
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
options:
filter: (&(objectClass=some-group-class)(!(groupType=email)))
map:
description: l
set:
metadata.customField: 'hello'
```
There may be many providers, each targeting a specific `target` which is
supposed to match the `target` of a dedicated provider instance - i.e., you will
add one entity provider class instance per target to ingest from.
These config blocks have a lot of options in them, so we will describe each
"root" key within the block separately.
### target
This is the URL of the targeted server, typically on the form
`ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net`
without SSL.
#### target.tls.keys
`keys` in TLS options specifies location of a file, that contains private keys
to establish connection with your LDAP server, in PEM format. See an example
for Google Secure LDAP Service below.
#### target.tls.certs
`certs` in TLS options specifies location of a file, that contains certificate
chains to establish connection with your LDAP server, in PEM format. See an
example for Google Secure LDAP Service below.
### bind
The bind block specifies how the plugin should bind (essentially, to
authenticate) towards the server. It has the following fields.
```yaml
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
secret: ${LDAP_SECRET}
```
The `dn` is the full LDAP Distinguished Name for the user that the plugin
authenticates itself as. At this point, only regular user based authentication
is supported.
The `secret` is the password of the same user. In this example, it is given in
the form of an environment variable `LDAP_SECRET`, that has to be set when the
backend starts.
### users
The `users` block defines the settings that govern the reading and
interpretation of users. Its fields are explained in separate sections below.
#### users.dn
The DN under which users are stored, e.g.
`ou=people,ou=example,dc=example,dc=net`.
#### users.options
The search options to use when sending the query to the server, when reading all
users. All the options are shown below, with their default values, but they are
all optional.
```yaml
options:
# One of 'base', 'one', or 'sub'.
scope: one
# The filter is the one that you commonly will want to specify explicitly. It
# is a string on the standard LDAP query format. Use it to select out the set
# of users that are of actual interest to ingest. For example, you may want
# to filter out disabled users.
filter: (uid=*)
# The attribute selectors for each item, as passed to the LDAP server.
attributes: ['*', '+']
# This field is either 'false' to disable paging when reading from the
# server, or an object on the form '{ pageSize: 100, pagePause: true }' that
# specifies the details of how the paging shall work.
paged: false
```
#### users.set
This optional piece lets you specify a number of JSON paths (on a.b.c form) and
hard coded values to set on those paths. This can be useful for example if you
want to hard code a namespace or similar on the generated entities.
```yaml
set:
# Just an example; the key and value can be anything
metadata.namespace: 'ldap'
```
#### users.map
Mappings from well known entity fields, to LDAP attribute names. This is where
you are able to define how to interpret the attributes of each LDAP result item,
and to move them into the corresponding entity fields. All the options are shown
below, with their default values, but they are all optional.
If you leave out an optional mapping, it will still be copied using that default
value. For example, even if you do not put in the field `displayName` in your
config, the provider will still copy the attribute `cn` into the entity field
`spec.profile.displayName`.
```yaml
map:
# The name of the attribute that holds the relative
# distinguished name of each entry.
rdn: uid
# The name of the attribute that shall be used for the value of
# the metadata.name field of the entity.
name: uid
# The name of the attribute that shall be used for the value of
# the metadata.description field of the entity.
description: description
# The name of the attribute that shall be used for the value of
# the spec.profile.displayName field of the entity.
displayName: cn
# The name of the attribute that shall be used for the value of
# the spec.profile.email field of the entity.
email: mail
# The name of the attribute that shall be used for the value of
# the spec.profile.picture field of the entity.
picture: <nothing, left out>
# The name of the attribute that shall be used for the values of
# the spec.memberOf field of the entity.
memberOf: memberOf
```
### groups
The `groups` block defines the settings that govern the reading and
interpretation of groups. Its fields are explained in separate sections below.
#### groups.dn
The DN under which groups are stored, e.g.
`ou=people,ou=example,dc=example,dc=net`.
#### groups.options
The search options to use when sending the query to the server, when reading all
groups. All the options are shown below, with their default values, but they are
all optional.
```yaml
options:
# One of 'base', 'one', or 'sub'.
scope: one
# The filter is the one that you commonly will want to specify explicitly. It
# is a string on the standard LDAP query format. Use it to select out the set
# of groups that are of actual interest to ingest. For example, you may want
# to filter out disabled groups.
filter: (&(objectClass=some-group-class)(!(groupType=email)))
# The attribute selectors for each item, as passed to the LDAP server.
attributes: ['*', '+']
# This field is either 'false' to disable paging when reading from the
# server, or an object on the form '{ pageSize: 100, pagePause: true }' that
# specifies the details of how the paging shall work.
paged: false
```
#### groups.set
This optional piece lets you specify a number of JSON paths (on a.b.c form) and
hard coded values to set on those paths. This can be useful for example if you
want to hard code a namespace or similar on the generated entities.
```yaml
set:
# Just an example; the key and value can be anything
metadata.namespace: 'ldap'
```
#### groups.map
Mappings from well known entity fields, to LDAP attribute names. This is where
you are able to define how to interpret the attributes of each LDAP result item,
and to move them into the corresponding entity fields. All of the options are
shown below, with their default values, but they are all optional.
If you leave out an optional mapping, it will still be copied using that default
value. For example, even if you do not put in the field `displayName` in your
config, the provider will still copy the attribute `cn` into the entity field
`spec.profile.displayName`. If the target field is optional, such as the display
name, the importer will accept missing attributes and just leave the target
field unset. If the target field is mandatory, such as the name of the entity,
validation will fail if the source attribute is missing.
```yaml
map:
# The name of the attribute that holds the relative
# distinguished name of each entry. This value is copied into a
# well known annotation to be able to query by it later.
rdn: cn
# The name of the attribute that shall be used for the value of
# the metadata.name field of the entity.
name: cn
# The name of the attribute that shall be used for the value of
# the metadata.description field of the entity.
description: description
# The name of the attribute that shall be used for the value of
# the spec.type field of the entity.
type: groupType
# The name of the attribute that shall be used for the value of
# the spec.profile.displayName field of the entity.
displayName: cn
# The name of the attribute that shall be used for the value of
# the spec.profile.email field of the entity.
email: <nothing, left out>
# The name of the attribute that shall be used for the value of
# the spec.profile.picture field of the entity.
picture: <nothing, left out>
# The name of the attribute that shall be used for the values of
# the spec.parent field of the entity.
memberOf: memberOf
# The name of the attribute that shall be used for the values of
# the spec.children field of the entity.
members: member
```
## Customize the Provider
In case you want to customize the ingested entities, the provider allows to pass
transformers for users and groups. Here we will show an example of overriding
the group transformer.
1. Create a transformer:
```ts
export async function myGroupTransformer(
vendor: LdapVendor,
config: GroupConfig,
group: SearchEntry,
): Promise<GroupEntity | undefined> {
// Transformations may change namespace, change entity naming pattern, fill
// profile with more or other details...
// Create the group entity on your own, or wrap the default transformer
return await defaultGroupTransformer(vendor, config, group);
}
```
2. Configure the provider with the transformer:
```ts
const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, {
id: 'our-ldap-master',
target: 'ldaps://ds.example.net',
logger: env.logger,
groupTransformer: myGroupTransformer,
});
```
## Using a Processor instead of a Provider
An alternative to using the Provider for ingesting LDAP entries is to use a
Processor. This is the old way that's based on registering locations with the
proper type and target, triggering the processor to run.
The drawback of this method is that it will leave orphaned Group/User entities
whenever they are deleted on your LDAP server, and you cannot control the
frequency with which they are refreshed, separately from other processors.
### Processor Installation
The `LdapOrgReaderProcessor` is not registered by default, so you have to
register it in the catalog plugin:
```typescript title="packages/backend/src/plugins/catalog.ts"
builder.addProcessor(
LdapOrgReaderProcessor.fromConfig(env.config, {
logger: env.logger,
}),
);
```
### Driving LDAP Org Processor Ingestion with Locations
Locations point out the specific org(s) you want to import. The `type` of these
locations must be `ldap-org`, and the `target` must point to the exact URL
(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can
have several such location entries if you want, but typically you will have just
one.
```yaml
catalog:
locations:
- type: ldap-org
target: ldaps://ds.example.net
rules:
- allow: [User, Group]
```
### Example configurations
#### Google Secure LDAP Service
To sync Google Workspace/Cloud Identity organization data to users and groups in backstage,
you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first.
Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration,
as mentioned below. `keys` and `certs` specify the location of files that are generated
while configuring Secure LDAP Service above.
```yaml
ldap:
providers:
- target: ldaps://ldap.google.com:636
tls:
rejectUnauthorized: false
keys: '/var/secrets/tls/gldap.key'
certs: '/var/secrets/tls/gldap.crt'
users:
# users configuration comes here
groups:
# groups configuration comes here
```
+69 -150
View File
@@ -18,9 +18,6 @@ Backstage in general supports OpenLDAP compatible vendors, as well as Active Dir
## Installation
This guide will use the Entity Provider method. If you for some reason prefer
the Processor method (not recommended), it is described separately below.
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
@@ -29,43 +26,30 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
```
> Note: When configuring to use a Provider instead of a Processor you do not
> need to add a _location_ pointing to your LDAP server
Next add the basic configuration to `app-config.yaml`
Update the catalog plugin initialization in your backend to add the provider and
schedule it:
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* highlight-add-start */
// The target parameter below needs to match the ldap.providers.target
// value specified in your app-config.
builder.addEntityProvider(
LdapOrgEntityProvider.fromConfig(env.config, {
id: 'our-ldap-master',
target: 'ldaps://ds.example.net',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 60 },
timeout: { minutes: 15 },
}),
}),
);
/* highlight-add-end */
// ..
}
```yaml title="app-config.yaml"
catalog:
providers:
ldapOrg:
default:
target: ldaps://ds.example.net
bind:
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
secret: ${LDAP_SECRET}
schedule:
frequency: PT1H
timeout: PT15M
```
After this, you also have to add some configuration in your app-config that
describes what you want to import for that target.
Finally, updated your backend by adding the following line:
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-ldap'));
/* highlight-add-end */
```
## Configuration
@@ -73,34 +57,32 @@ The following configuration is a small example of how a setup could look for
importing groups and users from a corporate LDAP server.
```yaml
ldap:
catalog:
providers:
- target: ldaps://ds.example.net
bind:
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
secret: ${LDAP_SECRET}
users:
dn: ou=people,ou=example,dc=example,dc=net
options:
filter: (uid=*)
map:
description: l
set:
metadata.customField: 'hello'
groups:
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
options:
filter: (&(objectClass=some-group-class)(!(groupType=email)))
map:
description: l
set:
metadata.customField: 'hello'
ldapOrg:
default:
target: ldaps://ds.example.net
bind:
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
secret: ${LDAP_SECRET}
users:
dn: ou=people,ou=example,dc=example,dc=net
options:
filter: (uid=*)
map:
description: l
set:
metadata.customField: 'hello'
groups:
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
options:
filter: (&(objectClass=some-group-class)(!(groupType=email)))
map:
description: l
set:
metadata.customField: 'hello'
```
There may be many providers, each targeting a specific `target` which is
supposed to match the `target` of a dedicated provider instance - i.e., you will
add one entity provider class instance per target to ingest from.
These config blocks have a lot of options in them, so we will describe each
"root" key within the block separately.
@@ -317,97 +299,34 @@ map:
## Customize the Provider
In case you want to customize the ingested entities, the provider allows to pass
transformers for users and groups. Here we will show an example of overriding
the group transformer.
transformers for users and groups.
1. Create a transformer:
Transformers can be configured by extending `ldapOrgEntityProviderTransformExtensionPoint`. Here is an example:
```ts
export async function myGroupTransformer(
vendor: LdapVendor,
config: GroupConfig,
group: SearchEntry,
): Promise<GroupEntity | undefined> {
// Transformations may change namespace, change entity naming pattern, fill
// profile with more or other details...
```ts title="packages/backend/src/index.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { ldapOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-ldap';
import { myUserTransformer, myGroupTransformer } from './transformers';
// Create the group entity on your own, or wrap the default transformer
return await defaultGroupTransformer(vendor, config, group);
}
```
2. Configure the provider with the transformer:
```ts
const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, {
id: 'our-ldap-master',
target: 'ldaps://ds.example.net',
logger: env.logger,
groupTransformer: myGroupTransformer,
});
```
## Using a Processor instead of a Provider
An alternative to using the Provider for ingesting LDAP entries is to use a
Processor. This is the old way that's based on registering locations with the
proper type and target, triggering the processor to run.
The drawback of this method is that it will leave orphaned Group/User entities
whenever they are deleted on your LDAP server, and you cannot control the
frequency with which they are refreshed, separately from other processors.
### Processor Installation
The `LdapOrgReaderProcessor` is not registered by default, so you have to
register it in the catalog plugin:
```typescript title="packages/backend/src/plugins/catalog.ts"
builder.addProcessor(
LdapOrgReaderProcessor.fromConfig(env.config, {
logger: env.logger,
backend.add(
createBackendModule({
pluginId: 'catalog',
moduleId: 'ldap-extensions',
register(env) {
env.registerInit({
deps: {
/* highlight-add-start */
ldapTransformers: ldapOrgEntityProviderTransformExtensionPoint,
/* highlight-add-end */
},
async init({ ldapTransformers }) {
/* highlight-add-start */
ldapTransformers.setUserTransformer(myUserTransformer);
ldapTransformers.setGroupTransformer(myGroupTransformer);
/* highlight-add-end */
},
});
},
}),
);
```
### Driving LDAP Org Processor Ingestion with Locations
Locations point out the specific org(s) you want to import. The `type` of these
locations must be `ldap-org`, and the `target` must point to the exact URL
(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can
have several such location entries if you want, but typically you will have just
one.
```yaml
catalog:
locations:
- type: ldap-org
target: ldaps://ds.example.net
rules:
- allow: [User, Group]
```
### Example configurations
#### Google Secure LDAP Service
To sync Google Workspace/Cloud Identity organization data to users and groups in backstage,
you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first.
Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration,
as mentioned below. `keys` and `certs` specify the location of files that are generated
while configuring Secure LDAP Service above.
```yaml
ldap:
providers:
- target: ldaps://ldap.google.com:636
tls:
rejectUnauthorized: false
keys: '/var/secrets/tls/gldap.key'
certs: '/var/secrets/tls/gldap.crt'
users:
# users configuration comes here
groups:
# groups configuration comes here
```
+1 -1
View File
@@ -20,7 +20,7 @@ info:
### Generating your client
1. Run `yarn backstage-repo-tools schema openapi generate client --output-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content.
1. Run `yarn backstage-repo-tools package schema openapi generate client --client-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content.
2. You should use the generated files as follows,
- `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API.
+58 -64
View File
@@ -6,93 +6,87 @@ description: Roadmap of Backstage
## The Backstage Roadmap
Backstage is currently under rapid development. This page details the project's
public roadmap, the result of ongoing collaboration between the core maintainers
and the broader Backstage community.
Backstage is still under rapid development, and this page details the project's
public roadmap. This not a complete list of all work happening in and around the
project, it only highlights the highest priority initiatives worked on by the
core maintainers.
The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future
work"](#future-work). With "next" we mean features planned for release within
the ongoing quarter from July through September 2022. With "future" we mean
features on the radar, but not yet scheduled.
## 2024 Fall Roadmap
| [What's next](#whats-next) | [Future work](#future-work) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| [Backend Services (MVP)](#backend-services-mvp) <br/> [Backstage Security Audit](#backstage-security-audit) <br/> [Backstage Threat Model](#backstage-threat-model) <br/> [Software Catalog pagination](#software-catalog-pagination) <br/> [More SIGs](#more-sigs) | Ease of onboarding <br/> Composable Homepage 1.0 <br/> Creator experience <br/> GraphQL <br/> Telemetry |
The initiatives listed below are planned for release within the next half-year, starting in May 2024. The roadmap is updated every 6 months, and the next update is planned for November 2024.
The long-term roadmap (12 - 36 months) is not detailed in the public roadmap.
Third-party contributions are also not currently included in the roadmap. Let us
know about any ongoing developments and we're happy to include them here as
well.
### Backend System 1.0
## What's next
The goal of this initiative is the stable 1.0 release of the [new backend system](../backend-system/index.md).
This includes ensuring that all documentation is up to date, and includes API
reviews and refactoring efforts to ensure that what is released is both stable
and evolvable. You can follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/24493).
The feature set below is planned for the ongoing quarter, and grouped by theme.
The list order doesn't necessarily reflect priority, and the development/release
cycle will vary based on maintainer schedules.
As part of this initiative, there will also be an exploration on how to
simplify extension of backend services. It is not currently possible to augment
backend services through declarative integration, they are instead only
customizable through complete replacement. This also limits the ability to
modularize services and scale ownership of the implementations. The goal is to
provide a more flexible and scalable way to extend backend services.
### Backend Services (MVP)
### New Frontend System - Ready for Adoption
To better scale and maintain the Backstage instances, a backend services system
is planned to be introduced as part of the software architecture. This layer of
backend services will help in decoupling the various modules (e.g. Catalog and
Scaffolder) from the frontend experience.
The [new fronted system](../frontend-system/index.md) still needs more work, and
the next milestone is to improve it to the point where there is enough
confidence in the design to start encouraging adoption in the community. You can
follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/19545).
This milestone also includes reaching and executing [rollout phase 2](https://github.com/backstage/backstage/issues/19545#issuecomment-1766069146).
After the experimentation and design happened in the past quarter, soon we plan to release a first version to start providing the first benefits to adopters and developers.
Once the initial milestone is reached, the goal is to also build out broader
support for the new frontend system in the core plugins.
### Backstage Security Audit
This is the continuation of the initiative started in the previous quarters. This
quarter will see the publication of the report describing the outcome of the
audit, together the first fixes and the development of some of the changes
required to address the vulnerabilities.
This is the second security audit of the Backstage project. It is done together,
and with the support of the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/).
This time the audit will in particular focus on the recently introduced
[authentication system](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution),
but also cover other parts of the project.
This initiative is the first of a broader Security Strategy for Backstage. The
purpose of the Security Audit is to involve third-party companies in auditing
the platform. The benefit for the adopters is clear: we want Backstage to be as
secure as possible, and we want to make it reliable through a specific
initiative.
### Plugin Metadata
This initiative is done together with, and with the support of, the [Cloud
Native Computing Foundation (CNCF)](https://www.cncf.io/).
The goal of this initiative is to provide better machine readable metadata for
Backstage packages, available both at runtime, at build-time and as part of
package registries. We want to surface information such as what packages make up
a particular plugin, what features it provides, and more generally laying the
foundation for an evolvable plugin metadata system.
### Backstage Threat Model
### MUI v5 Green-light
This is another (relevant) initiative planned to make Backstage a secure product for the adopters. The goals of this initiative are:
Material-UI v4 is still the officially supported version of MUI in Backstage.
While we have heard that adopters have had success using MUI 5, this is still an
untested path with known bugs. The goal of this initiative is to iron out any
remaining issues or gaps, and then provide a green light for migration to MUI 5.
1. Understand where security investment and attention is needed.
2. Guide the upcoming security audit.
3. Communicate expectations to Backstage adopters and inform and attract security researchers.
### Configuration Improvements
The planned artifacts are:
This initiative aims to improve the configuration experience and reliability in
Backstage. Areas for improvement include the way that configuration schema is
loaded, the way that plugins access configuration that is not owned by them, how
plugins read configuration, and how configuration visibility is handled.
- Concise high level threat model that will be included as part of the Backstage security documentation.
- Granular threat model created in conjunction with the security audit to inform further security investment areas for Backstage.
### Versioned Documentation
### Software Catalog pagination
The goal of this initiative is to provide versioned documentation at
[backstage.io](https://backstage.io). This lets us provide documentation that is
both up-to-date while at the same time not ahead of the latest release.
Today adopters with a big catalog (with several thousands of software components) might not have an ideal end-user experience when viewing the `/catalog` page. The issue is related to how the entities are fetched by the frontend. In order to provide a better end-user experience the pagination of the catalogs entities needs to be enforced. Some experimentation is already completed but in this quarter we plan to continue, and hopefully complete, this relevant enhancement.
### Rework Pull Request & Issue Process
### More SIGs
Our current review and issue triage process is centered around either core- or
project area maintainers. The goal of this initiative is to make it simpler for
more members of the community to be involved and contribute to this process.
In the last quarter we launched the [Catalog SIG (Special Interest Group)](https://github.com/backstage/community/tree/main/sigs/sig-catalog) to better coordinate the increasing number of contributions to the project. We think that this is the proper path to follow to engage more with the contributors. For this reason we will launch other SIGs dedicated to the most interesting topics for the community.
### Catalog Observability
## Future work
The following feature list doesn't represent a commitment to develop, and the
list order doesn't reflect any priority or importance, but these features are on
the maintainers' radar, with clear interest expressed by the community.
- **Ease of onboarding:** A faster (with less development) and easier setup of
Backstage and the most relevant/adopted plugins.
- **Composable Homepage 1.0:** Driving this to 1.0 by adding some composable
components.
- **Creator experience:** Provide a better Backstage user experience through
visual guidelines and templates, especially navigation across plug-ins and
portal functionalities.
- **[GraphQL](https://graphql.org/) support:** Introduce the ability to query
Backstage backend services with a standard query language for APIs.
- **Telemetry:** To efficiently generate logging and metrics in such a way that
adopters can get insights so that Backstage can be monitored and improved.
The goal of this initiative is to provide better tools for debugging catalog
ingestion issues and to more generally reduce friction for setting up and
maintaining the software catalog.
## How to influence the roadmap
+23 -14
View File
@@ -14,21 +14,28 @@ The Backstage trust model is divided into three groups with different trust leve
An **internal user** is an authenticated user that generally belongs to the organization of a particular Backstage deployment. These users are trusted to the extent that they are not expected to compromise the availability of Backstage, but they are not trusted to not compromise data confidentiality or integrity.
An **integrator** is a user responsible for configuring and maintaining an instance of Backstage. Integrators are fully trusted, since they operate the system and database and therefore have root access to the host system. Additional measures can be taken by adopters of Backstage in order to restrict or observe the access of this group, but that falls outside of the current scope of Backstage.
An **operator** is a user responsible for configuring and maintaining an instance of Backstage. Operators are fully trusted, since they operate the system and database and therefore have root access to the host system. Additional measures can be taken by adopters of Backstage in order to restrict or observe the access of this group, but that falls outside of the current scope of Backstage.
Another group of de facto integrators is internal and external code contributors. When installing Backstage plugins you should vet them just like any other package from an external source. While its possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins.
A **builder** is an internal or external code contributor and end up having a similar level of access as operators. When installing Backstage plugins you should vet them just like any other package from an external source. While its possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins.
An **external user** is a user that does not belong to the other two groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case.
## Integrator Responsibilities
## Operator Responsibilities
As an integrator of Backstage you yourself are responsible for protecting your Backstage installation from external and unauthorized access. The sign-in system in Backstage does not exist to limit access, only to inform the system of the identity of the user. There are some plugins that have more fine-grained access control through the permissions system, but the primary purpose of that system is to restrict access to resources for internal users rather than Backstage as a whole. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWSs ALB, GCPs IAP, or Cloudflare Access.
:::info
This section assumes that you are using the
[new backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment.
:::
Other responsibilities include protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems.
Backstage is primarily designed to be deployed in a protected environment rather than being exposed to the public internet. From a confidentiality and integrity perspective, Backstage is designed to protect against unauthorized access to data and to ensure that data is not tampered with. However, Backstage does not provide more than rudimentary protection against denial of service attacks, and it is the responsibility of the operator to ensure that the Backstage deployment is protected against such attacks. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWSs ALB, GCPs IAP, or Cloudflare Access.
The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
Users that are signed-in in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary.
The integrator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
An operator is responsible for protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems.
The operator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
The operator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
## Common Backend Configuration
@@ -44,21 +51,23 @@ Note that the `UrlReader` system operates with a service context and is not inte
Backstage provides authentication of users through the `auth` plugin, which primarily acts as an authorization server for different OAuth 2.0 provider integrations. These integrations can both serve the purpose of signing users into Backstage, as well as providing delegated access to external resources, and are all subject to the common concerns of implementing secure OAuth 2.0 authorization servers. All auth provider integrations are disabled by default, and need to be enabled through configuration in order to be used. For each Backstage installation it is recommended to only enable the minimal set of providers that are in use by that instance.
It is not within scope of the `auth` backend to protect against unauthorized access, that is something that needs to be handled at a deployment level. See the [Integrator Responsibilities](#integrator-responsibilities) section for more information.
In order to use an auth provider to sign in users into Backstage, it needs to be configured with an [Identity resolver](https://backstage.io/docs/auth/identity-resolver), which is a custom callback implemented in code. The identity resolver is a sensitive part of configuring Backstage and it is important that it always resolves user identities correctly, based on information provided by the authentication provider. There are a number of built-in identity resolvers that can simplify configuration, and it is important that these all resolve users in a secure way, regardless of how they are used.
Backstage also supports authentication through an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/), where the user identity is read from the incoming proxied decorated request. The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` do not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. Its therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider.
As part of signing in with an identity resolver, a Backstage Token is issued containing the resolved user identity. The tokens are asymmetrically signed JSON Web Tokens, with the public keys available to any service that wishes to verify a token. The signing keys are rotated continuously and are unique to each installation of Backstage, meaning that Backstage Tokens are not shared across installations. The token contains claims for the user identity and ownership information, which can be used to determine what Backstage resources are owned by that user or group. It is important that this token can not be forged outside of the `auth` plugin, with the exception of other plugins deployed in the same backend service or sharing the same database. For a high-security deployment, the `auth` backend should therefore be deployed in a separate service with its own database.
The token is used to prove the identity of the user within the Backstage system, and is used throughout Backstage plugins to control access. It is important that the ownership resolution logic is consistent across the entire Backstage ecosystem, with no possibility of misinterpreting the ownership information.
For cross-backend communication, the Backstage Token is typically forwarded or, in strict backend-to-backend communication without a user party, the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation. This is something that we aim to improve in the future.
One of the claims in a user token is the User Identity Proof or `uip`. This is an additional signature of the token that allows for offline token transformation. By replacing the original signature with the `uip` the token is still proof of a user identity, but it no longer acts as a full access token and will be rejected by most plugin endpoints. Plugins can explicitly allow use of this limited token where required, but this should only be used when necessary when a full token is not available, and ideally just for read-only access. Use-cases for limited users tokens include cookie authentication of static assets, storage of user identity proofs in a database, and similar.
Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. Its therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider.
The communication across backend plugins uses a similar authentication scheme to the user authentication. Each backend plugin generates and publishes its own set of keys that it uses to sign its tokens, and the public keys are shared with all other plugins for verification. The expected location of each plugin's published JWKS is determined by the `DiscoveryService` implementation in the backend, which means that it is vital for any custom implementation of that service to be careful with user input. The tokens signed by each plugin contain both the source and target plugin ID, which means that the token can not be reused to access other plugins.
When forwarding a user identity in a call across backend plugins only the limited user token with `uip` is used, wrapped in a new service token that is signed by the calling plugin. This means that the receiving plugin can trust the user identity, but it is not able to make further calls on behalf of the user except for with the plugins that it is authorized to call. That is except for any endpoints in other plugins that accept limited user tokens, which is a reason to avoid accepting them when possible.
## Catalog
Integrators should configure [catalog rules](https://backstage.io/docs/features/software-catalog/configuration#catalog-rules) to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules.
Operators should configure [catalog rules](https://backstage.io/docs/features/software-catalog/configuration#catalog-rules) to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules.
The Catalog does not aim to protect against resource exhaustion attacks in its default setup. If you need to prevent your internal users from being able to register large amounts of entities, then it is recommended to disable entity registration and use a different approach for discovering entities. One way to mitigate any resource exhaustion attacks is to only allow the catalog to read from trusted SCM sources that have an audit trail. Catalog currently lacks limits for entity hierarchy depth and entity size, which we hope to address in the future.
@@ -68,11 +77,11 @@ By default all internal users are allowed to create and delete entities. If this
By default, Scaffolding jobs execute directly on the host machine, including any actions defined in the template. Because the Scaffolder templates are considered a more sensitive area it is recommended to control access to create and update templates to trusted parties. Template execution is intended to be secure regardless of input, but we still recommend this additional layer of protection. The string templating is executed in a [node VM sandbox](https://github.com/laverdet/isolated-vm) to mitigate the possibility of remote code execution attacks.
The Scaffolder often has elevated permissions to for example create repositories in a Github organization. The integrator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake.
The Scaffolder often has elevated permissions to for example create repositories in a Github organization. The operator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake.
One strategy that allows you to reduce the access that the Scaffolder service has is to rely on user credentials when executing actions. For example, a GitHub App integration could be configured with read-only permissions, with a separate user OAuth token used to create repositories. This requires that your users have access to create repositories in the first place.
The integrator should audit installed scaffolding actions just like any other plugin package. It is also important to verify that installed actions fall in line with your own security requirements, as some actions might be intended for more relaxed environments.
The operator should audit installed scaffolding actions just like any other plugin package. It is also important to verify that installed actions fall in line with your own security requirements, as some actions might be intended for more relaxed environments.
By default all internal users are allowed to execute templates in the scaffolder. If this does not fit your organization's needs it is recommended to enable and configure the [permission](https://backstage.io/docs/permissions/overview) system to restrict these operations.
+11 -9
View File
@@ -81,19 +81,21 @@ In order for Backstage to function properly the following versioning rules must
be followed. The rules are referring to the
[Package Architecture](https://backstage.io/docs/overview/architecture-overview#package-architecture).
- The versions of all the packages in the `Frontend App Core` must be from the
same release, and it is recommended to keep `Common Tooling` on that release
too.
- The Backstage dependencies of any given plugin should be from the same
release. This includes the packages from `Common Libraries`,
`Frontend Plugin Core`, and `Frontend Libraries`, or alternatively the
`Backend Libraries`.
- There must be no package that is from a newer release than the
`Frontend App Core` packages in the app.
- The versions of all packages for each of the "App Core" groups must be from the
same Backstage release.
- For each frontend and backend setup, the "App Core" packages must be ahead of or on the same Backstage release as the "Plugin Core" packages, including transitive dependencies of all installed plugins and modules.
- For any given plugin, the versions of all packages from the "Plugin Core" and
"Library" groups must be from the same Backstage release.
- Frontend plugins with a corresponding backend plugin should be from the same
release. The update to the backend plugin **MUST** be deployed before or
together with the update to the frontend plugin.
It is allowed and often expected that the "Plugin Core" and "Library" packages
are from older releases than the "App Core" packages. It is also allowed to have
duplicate installations of the "Plugin Core" and "Library" packages. This is all
to make sure that upgrading Backstage is as smooth as possible and allows for
more flexibility across the entire plugin ecosystem.
## Package Versioning Policy
Every individual package is versioned according to [semver](https://semver.org).
+36
View File
@@ -0,0 +1,36 @@
---
id: getting-started--new
title: Getting Started
description: How to get started with the permission framework as an integrator
---
Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others.
## Prerequisites
The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy.
### Upgrade to the latest version of Backstage
To ensure your version of Backstage has all the latest permission-related functionality, its important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that youve made all the necessary changes during the upgrade!
### Supply an identity resolver to populate group membership on sign in
**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up.
Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity.
[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in.
## Enable and test the permissions system
All you need to do now is enable the permissions system in your Backstage instance!
1. Set the property `permission.enabled` to `true` in `app-config.yaml`.
```yaml title="app-config.yaml"
permission:
enabled: true
```
Congratulations! Now that the framework is configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)!
+5 -1
View File
@@ -8,7 +8,11 @@ If you prefer to watch a video instead, you can start with this video introducti
<iframe width="560" height="315" src="https://www.youtube.com/embed/EQr9tFClgG0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases.
:::note Note
This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases.
:::
Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others.
@@ -37,7 +37,11 @@ export const todoListPermissions = [todoListCreatePermission];
For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`).
> Note: We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/local-dev/cli-build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies.
:::note Note
We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies.
:::
## Authorizing using the new permission
@@ -144,7 +144,11 @@ export const rules = { isOwner };
`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query.
> Note: To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`.
:::note Note
To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`.
:::
We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`.
Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not.
@@ -36,7 +36,11 @@ This approach will work for simple cases, but it has a downside: it forces us to
To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior.
> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format.
:::note Note
In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format.
:::
## Creating the read permission
@@ -8,7 +8,11 @@ In the previous sections, we learned how to protect our plugin's backend API rou
Take, for example, the "Add" button in our todo list application. When a user clicks this button, the frontend makes a `POST` request to the `/todos` route of our backend. If a user tries to add a todo but is not authorized, they will have no way of knowing this until they perform the action and are faced with an error. This is a poor user experience. We can do better by disabling the add button.
> Note: Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component.
:::note Note
Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component.
:::
## Using `usePermission`
+4 -5
View File
@@ -46,9 +46,9 @@ To suggest an integration, please [open an issue][add-tool] for the analytics
tool your organization uses. Or jump to [Writing Integrations][int-howto] to
learn how to contribute the integration yourself!
[ga]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md
[ga4]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga4/README.md
[newrelic-browser]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-newrelic-browser/README.md
[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md
[ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md
[newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md
[qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md
[matomo]: https://github.com/janus-idp/backstage-plugins/blob/main/plugins/analytics-module-matomo/README.md
[add-tool]: https://github.com/backstage/backstage/issues/new?assignees=&labels=plugin&template=plugin_template.md&title=%5BAnalytics+Module%5D+THE+ANALYTICS+TOOL+TO+INTEGRATE
@@ -69,8 +69,7 @@ installed, may be captured.
| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided. |
| `not-found` | The path of the resource that resulted in a not found page | Fired by at least TechDocs. |
If there is an event you'd like to see captured, please [open an
issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it
If there is an event you'd like to see captured, please [open an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it
would help you answer. Or jump to [Capturing Events](#capturing-events) to learn how
to contribute the instrumentation yourself!
+5 -1
View File
@@ -44,7 +44,11 @@ cd plugins/carmen-backend
yarn start
```
> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md)
:::note Note
This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md)
:::
This will think for a bit, and then say `Listening on :7007`. In a different
terminal window, now run
+1 -1
View File
@@ -11,7 +11,7 @@ A Backstage Plugin adds functionality to Backstage.
To create a new frontend plugin, make sure you've run `yarn install` and installed
dependencies, then run the following on your command line (a shortcut to
invoking the
[`backstage-cli new --select plugin`](../local-dev/cli-commands.md#new))
[`backstage-cli new --select plugin`](../tooling/cli/03-commands.md#new))
from the root of your project.
```bash
+16 -18
View File
@@ -6,39 +6,39 @@ description: Details the process of defining setting and reading a feature flag.
Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on.
This page describes the process of defining setting and reading a feature flag. If you are looking for using feature flags with software templates that can be found under [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags).
This page describes the process of defining, setting and reading a feature flag. If you are looking for using feature flags specifically with software templates please see [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags).
## Defining a Feature Flag
### In a plugin
Defining feature flag in a plugin is done by passing the name of the feature flag into the `featureFlags` array:
Defining a feature flag in a plugin is done by passing the name of the feature flag into the `featureFlags` array:
```ts
/* src/plugin.ts */
import { createPlugin, createRouteRef } from '@backstage/core-plugin-api';
import ExampleComponent from './components/ExampleComponent';
```ts title="src/plugin.ts"
import { createPlugin } from '@backstage/core-plugin-api';
export const examplePlugin = createPlugin({
id: 'example',
routes: {
root: rootRouteRef,
},
// ...
featureFlags: [{ name: 'show-example-feature' }],
// ...
});
```
### In the application
Defining feature flag in the application is done by adding feature flags in`featureFlags` array in
Defining a feature flag in the application is done by adding feature flags in `featureFlags` array in the
`createApp()` function call:
```ts
```ts title="packages/app/src/App.tsx"
import { createApp } from '@backstage/app-defaults';
const app = createApp({
// ...
featureFlags: [
{
pluginId: '', // pluginId is required for feature flags in plugins. It can be left blank for a feature flag leveraged in the application.
// pluginId is required for feature flags used in plugins.
// pluginId can be left blank for a feature flag used in the application and not in plugins.
pluginId: '',
name: 'tech-radar',
description: 'Enables the tech radar plugin',
},
@@ -49,11 +49,9 @@ const app = createApp({
## Enabling Feature Flags
Feature flags are defaulted to off and can be updated by individual users in the backstage interface.
Feature flags are defaulted to off and can be updated by individual users in the backstage interface. These are set by navigating to the page under `Settings` > `Feature Flags`.
These are set by navigating to the page under `Settings` > `Feature Flags`.
The users selection is saved in the users browsers local storage. Once toggled it may be required for a user to refresh the page to see any new changes.
The user's selection is saved in the user's browser local storage. Once a feature flag is toggled it may be required for a user to refresh the page to see the change.
## FeatureFlagged Component
@@ -75,7 +73,7 @@ import { FeatureFlagged } from '@backstage/core-app-api';
## Evaluating Feature Flag State
It is also possible to test the feature flag state using the [FeatureFlags Api](https://backstage.io/docs/reference/core-plugin-api.featureflagsapi).
It is also possible to query a feature flag using the [FeatureFlags Api](https://backstage.io/docs/reference/core-plugin-api.featureflagsapi).
```ts
import { useApi, featureFlagsApiRef } from '@backstage/core-plugin-api';
+1 -1
View File
@@ -8,7 +8,7 @@ description: Details of the new backend system
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
You can find an example backend setup in [the backend package](https://github.com/backstage/backstage/tree/master/packages/backend).
## Overview

Some files were not shown because too many files have changed in this diff Show More