From a873a32a1f9a4fd4e6d701212552cb7ebb7816ad Mon Sep 17 00:00:00 2001 From: ivgo Date: Mon, 2 Oct 2023 15:46:54 +0200 Subject: [PATCH] Add support to the new backend system, including customization Signed-off-by: ivgo --- .changeset/great-baboons-allow.md | 33 +++++++++ plugins/vault-backend/README.md | 25 ++++++- plugins/vault-backend/api-report.md | 10 ++- plugins/vault-backend/config.d.ts | 7 ++ plugins/vault-backend/package.json | 2 + plugins/vault-backend/src/index.ts | 1 + .../vault-backend/src/service/VaultBuilder.ts | 40 +++++++--- .../vault-backend/src/service/plugin.test.tsx | 22 ++++++ plugins/vault-backend/src/service/plugin.ts | 74 +++++++++++++++++++ plugins/vault-backend/src/service/vaultApi.ts | 2 + yarn.lock | 1 + 11 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 .changeset/great-baboons-allow.md create mode 100644 plugins/vault-backend/src/service/plugin.test.tsx create mode 100644 plugins/vault-backend/src/service/plugin.ts diff --git a/.changeset/great-baboons-allow.md b/.changeset/great-baboons-allow.md new file mode 100644 index 0000000000..47e410d8a4 --- /dev/null +++ b/.changeset/great-baboons-allow.md @@ -0,0 +1,33 @@ +--- +'@backstage/plugin-vault-backend': minor +--- + +Added support for the [new backend system](https://backstage.io/docs/backend-system/). + +In your `packages/backend/src/index.ts` make the following changes: + +```diff + import { createBackend } from '@backstage/backend-defaults'; + const backend = createBackend(); + // ... other feature additions ++ backend.add(import('@backstage/plugin-vault-backend'); + backend.start(); +``` + +If you use the new backend system, the token renewal task can be defined via configuration file: + +```diff +vault: + baseUrl: + token: + schedule: ++ frequency: ... ++ timeout: ... ++ # Other schedule options, such as scope or initialDelay +``` + +If the `schedule` is omitted or set to `false` no token renewal task will be scheduled. +If the value of `schedule` is set to `true` the renew will be scheduled hourly (the default). +In other cases (like in the diff above), the defined schedule will be used. + +**DEPRECATIONS**: The interface `VaultApi` and the type `VaultSecret` are now deprecated. Import them from `@backstage/plugin-vault-node`. diff --git a/plugins/vault-backend/README.md b/plugins/vault-backend/README.md index 8c70cfd24a..117bcaacbb 100644 --- a/plugins/vault-backend/README.md +++ b/plugins/vault-backend/README.md @@ -68,6 +68,9 @@ To get started, first you need a running instance of Vault. You can follow [this token: secretEngine: 'customSecretEngine' # Optional. By default it uses 'secrets'. Can be overwritten by the annotation of the entity kvVersion: # Optional. The K/V version that your instance is using. The available options are '1' or '2' + schedule: # Optional. If the token renewal is enabled this schedule will be used instead of the hourly one + frequency: { hours: 1 } + timeout: { hours: 1 } ``` 4. Get a `VAULT_TOKEN` with **LIST** permissions, as it's enough for the plugin. You can check [this tutorial](https://learn.hashicorp.com/tutorials/vault/tokens) for more info. @@ -80,6 +83,24 @@ To get started, first you need a running instance of Vault. You can follow [this } ``` +## New Backend System + +The Vault backend plugin has support for the [new backend system](https://backstage.io/docs/backend-system/), here's how you can set that up: + +In your `packages/backend/src/index.ts` make the following changes: + +```diff + import { createBackend } from '@backstage/backend-defaults'; + const backend = createBackend(); + // ... other feature additions ++ backend.add(import('@backstage/plugin-vault-backend'); + backend.start(); +``` + +The token renewal is enabled automatically in the new backend system depending on the `app-config.yaml`. If the `schedule` is not defined there, no +task will be executed. If you want to use the default renewal scheduler (which runs hourly), set `schedule: true`. In case you want a custom schedule +just use a configuration like the one set above. + ## Integration with the Catalog The plugin can be integrated into each Component in the catalog. To allow listing the available secrets a new annotation must be added to the `catalog-info.yaml`: @@ -122,7 +143,7 @@ That will overwrite the default secret engine from the configuration. ## Renew token -In a secure Vault instance, it's usual that the tokens are refreshed after some time. In order to always have a valid token to fetch the secrets, it might be necessary to execute a renew action after some time. By default this is deactivated, but it can be easily activated and configured to be executed periodically (hourly by default, but customizable by the user). In order to do that, modify your `src/plugins/vault.ts` file to look like this one: +In a secure Vault instance, it's usual that the tokens are refreshed after some time. In order to always have a valid token to fetch the secrets, it might be necessary to execute a renew action after some time. By default this is deactivated, but it can be easily activated and configured to be executed periodically (hourly by default, but customizable by the user within the app-config.yaml file). In order to do that, modify your `src/plugins/vault.ts` file to look like this one: ```typescript import { VaultBuilder } from '@backstage/plugin-vault-backend'; @@ -149,6 +170,8 @@ export default async function createPlugin( } ``` +If the `taskRunner` is not set when calling the `enableTokenRenew`, the plugin will automatically check what is set in the `app-config.yaml` file. Refer to [the new backend system setup](#new-backend-system) for more information about it. + ## Features - List the secrets present in a certain path diff --git a/plugins/vault-backend/api-report.md b/plugins/vault-backend/api-report.md index ebf9057b98..4002db5084 100644 --- a/plugins/vault-backend/api-report.md +++ b/plugins/vault-backend/api-report.md @@ -3,9 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -22,7 +24,7 @@ export interface RouterOptions { scheduler: PluginTaskScheduler; } -// @public +// @public @deprecated export interface VaultApi { getFrontendSecretsUrl(): string; listSecrets( @@ -69,12 +71,16 @@ export interface VaultEnvironment { // (undocumented) config: Config; // (undocumented) - logger: Logger; + logger: LoggerService; // (undocumented) scheduler: PluginTaskScheduler; } // @public +const vaultPlugin: () => BackendFeature; +export default vaultPlugin; + +// @public @deprecated export type VaultSecret = { name: string; path: string; diff --git a/plugins/vault-backend/config.d.ts b/plugins/vault-backend/config.d.ts index a30999b0cb..58369e638d 100644 --- a/plugins/vault-backend/config.d.ts +++ b/plugins/vault-backend/config.d.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; /** Configuration for the Vault plugin */ export interface Config { @@ -44,5 +45,11 @@ export interface Config { * The version of the K/V API. Defaults to `2`. */ kvVersion?: 1 | 2; + + /** + * If set to true, the default schedule (hourly) will be used. If a + * different schedule is set, this will be used instead. + * */ + schedule?: TaskScheduleDefinitionConfig | boolean; }; } diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index 6906b8124d..2159ff7bfc 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -34,9 +34,11 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-vault-node": "workspace:^", "@types/express": "*", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/plugins/vault-backend/src/index.ts b/plugins/vault-backend/src/index.ts index 4c74fd051f..c5fea00642 100644 --- a/plugins/vault-backend/src/index.ts +++ b/plugins/vault-backend/src/index.ts @@ -17,3 +17,4 @@ export * from './service/router'; export * from './service/VaultBuilder'; export * from './service/vaultApi'; +export { vaultPlugin as default } from './service/plugin'; diff --git a/plugins/vault-backend/src/service/VaultBuilder.ts b/plugins/vault-backend/src/service/VaultBuilder.ts index 9856476c20..471064392f 100644 --- a/plugins/vault-backend/src/service/VaultBuilder.ts +++ b/plugins/vault-backend/src/service/VaultBuilder.ts @@ -16,11 +16,17 @@ import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import express from 'express'; import Router from 'express-promise-router'; import { VaultApi, VaultClient } from './vaultApi'; -import { TaskRunner, PluginTaskScheduler } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskRunner, + TaskScheduleDefinition, + TaskScheduleDefinitionConfig, + readTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-tasks'; import { errorHandler } from '@backstage/backend-common'; /** @@ -28,7 +34,7 @@ import { errorHandler } from '@backstage/backend-common'; * @public */ export interface VaultEnvironment { - logger: Logger; + logger: LoggerService; config: Config; scheduler: PluginTaskScheduler; } @@ -101,18 +107,16 @@ export class VaultBuilder { } /** - * Enables the token renewal for Vault. + * Enables the token renewal for Vault. The schedule if configured in the app-config.yaml file. + * If not set, the renewal is executed hourly * - * @param schedule - The TaskRunner used to schedule the renewal, if not set, renewing hourly * @returns */ public async enableTokenRenew(schedule?: TaskRunner) { const taskRunner = schedule ? schedule - : this.env.scheduler.createScheduledTaskRunner({ - frequency: { hours: 1 }, - timeout: { hours: 1 }, - }); + : this.env.scheduler.createScheduledTaskRunner(this.getConfigSchedule()); + await taskRunner.run({ id: 'refresh-vault-token', fn: async () => { @@ -124,6 +128,24 @@ export class VaultBuilder { return this; } + private getConfigSchedule(): TaskScheduleDefinition { + const schedule = this.env.config.getOptional< + TaskScheduleDefinitionConfig | boolean + >('vault.schedule'); + + const scheduleCfg = + schedule !== undefined && schedule !== false + ? { + frequency: { hours: 1 }, + timeout: { hours: 1 }, + } + : readTaskScheduleDefinitionFromConfig( + this.env.config.getConfig('vault.schedule'), + ); + + return scheduleCfg; + } + /** * Builds the backend routes for Vault. * diff --git a/plugins/vault-backend/src/service/plugin.test.tsx b/plugins/vault-backend/src/service/plugin.test.tsx new file mode 100644 index 0000000000..cc9fdaa0a7 --- /dev/null +++ b/plugins/vault-backend/src/service/plugin.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vaultPlugin } from './plugin'; + +describe('vault', () => { + it('should export the vault plugin', () => { + expect(vaultPlugin).toBeDefined(); + }); +}); diff --git a/plugins/vault-backend/src/service/plugin.ts b/plugins/vault-backend/src/service/plugin.ts new file mode 100644 index 0000000000..cf9d2cf87b --- /dev/null +++ b/plugins/vault-backend/src/service/plugin.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { VaultApi, vaultExtensionPoint } from '@backstage/plugin-vault-node'; +import { VaultBuilder } from './VaultBuilder'; +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + +/** + * Vault backend plugin + * + * @public + */ +export const vaultPlugin = createBackendPlugin({ + pluginId: 'vault', + register(env) { + let client: VaultApi | undefined; + + env.registerExtensionPoint(vaultExtensionPoint, { + setClient(vaultClient) { + if (client) { + throw new Error('The vault client has been already set'); + } + client = vaultClient; + }, + }); + + env.registerInit({ + deps: { + logger: coreServices.logger, + config: coreServices.rootConfig, + scheduler: coreServices.scheduler, + httpRouter: coreServices.httpRouter, + }, + async init({ logger, config, scheduler, httpRouter }) { + let builder = VaultBuilder.createBuilder({ + logger, + config, + scheduler, + }); + + if (client) { + builder = builder.setVaultClient(client); + } + + const scheduleCfg = config.getOptional< + boolean | TaskScheduleDefinitionConfig + >('vault.schedule'); + if (scheduleCfg !== undefined && scheduleCfg !== false) { + builder = await builder.enableTokenRenew(); + } + + const { router } = builder.build(); + httpRouter.use(router); + }, + }); + }, +}); diff --git a/plugins/vault-backend/src/service/vaultApi.ts b/plugins/vault-backend/src/service/vaultApi.ts index 7c3ccc1d00..789cdeeda5 100644 --- a/plugins/vault-backend/src/service/vaultApi.ts +++ b/plugins/vault-backend/src/service/vaultApi.ts @@ -33,6 +33,7 @@ export type VaultSecretList = { /** * Object containing the secret name and some links * @public + * @deprecated Use the interface from `@backstage/plugin-vault-node` */ export type VaultSecret = { name: string; @@ -53,6 +54,7 @@ type RenewTokenResponse = { /** * Interface for the Vault API * @public + * @deprecated Use the interface from `@backstage/plugin-vault-node` */ export interface VaultApi { /** diff --git a/yarn.lock b/yarn.lock index 0ac22cafa6..c56c608a47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9904,6 +9904,7 @@ __metadata: resolution: "@backstage/plugin-vault-backend@workspace:plugins/vault-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^"