Merge pull request #20315 from ivangonzalezacuna/vault-new-backend-system

(new-backend-system): Migrate vault to new backend system
This commit is contained in:
Patrik Oldsberg
2023-10-24 11:53:36 +02:00
committed by GitHub
22 changed files with 446 additions and 12 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-vault-node': minor
---
Initial version of the `plugin-vault-node`` package. It contains the extension point definitions
for the vault backend, as well as some types that will be deprecated in the backend plugin.
+33
View File
@@ -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: <BASE_URL>
token: <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`.
+24 -1
View File
@@ -68,6 +68,9 @@ To get started, first you need a running instance of Vault. You can follow [this
token: <VAULT_TOKEN>
secretEngine: 'customSecretEngine' # Optional. By default it uses 'secrets'. Can be overwritten by the annotation of the entity
kvVersion: <kv-version> # 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
+8 -2
View File
@@ -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;
+7
View File
@@ -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;
};
}
+2
View File
@@ -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",
+1
View File
@@ -17,3 +17,4 @@
export * from './service/router';
export * from './service/VaultBuilder';
export * from './service/vaultApi';
export { vaultPlugin as default } from './service/plugin';
@@ -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.
*
@@ -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();
});
});
@@ -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);
},
});
},
});
@@ -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 {
/**
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+3
View File
@@ -0,0 +1,3 @@
# @backstage/plugin-vault-node
Houses types and utilities for building the vault modules
+38
View File
@@ -0,0 +1,38 @@
## API Report File for "@backstage/plugin-vault-node"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ExtensionPoint } from '@backstage/backend-plugin-api';
// @public
export interface VaultApi {
getFrontendSecretsUrl(): string;
listSecrets(
secretPath: string,
options?: {
secretEngine?: string;
},
): Promise<VaultSecret[]>;
renewToken?(): Promise<void>;
}
// @public
export interface VaultExtensionPoint {
// (undocumented)
setClient(vaultClient: VaultApi): void;
}
// @public
export const vaultExtensionPoint: ExtensionPoint<VaultExtensionPoint>;
// @public
export type VaultSecret = {
name: string;
path: string;
showUrl: string;
editUrl: string;
};
// (No @packageDocumentation comment for this package)
```
+10
View File
@@ -0,0 +1,10 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-plugin-vault-node
title: '@backstage/plugin-vault-node'
description: Node.js library for the vault plugin
spec:
lifecycle: experimental
type: backstage-node-library
owner: maintainers
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@backstage/plugin-vault-node",
"description": "Node.js library for the vault plugin",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "node-library"
},
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
},
"files": [
"dist"
]
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2023 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.
*/
export * from './types';
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright 2023 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.
*/
/**
* Object containing the secret name and some links
* @public
*/
export type VaultSecret = {
name: string;
path: string;
showUrl: string;
editUrl: string;
};
/**
* Interface for the Vault API
* @public
*/
export interface VaultApi {
/**
* Returns the URL to access the Vault UI with the defined config.
*/
getFrontendSecretsUrl(): string;
/**
* Returns a list of secrets used to show in a table.
* @param secretPath - The path where the secrets are stored in Vault
* @param options - Additional options to be passed to the Vault API, allows to override vault default settings in app config file
*/
listSecrets(
secretPath: string,
options?: {
secretEngine?: string;
},
): Promise<VaultSecret[]>;
/**
* Optional, to renew the token used to list the secrets. Throws an
* error if the token renewal went wrong.
*/
renewToken?(): Promise<void>;
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2023 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 { createExtensionPoint } from '@backstage/backend-plugin-api';
import { VaultApi } from './api';
/**
* Extension point for vault.
*
* @public
*/
export interface VaultExtensionPoint {
setClient(vaultClient: VaultApi): void;
}
/**
* Extension point for vault.
*
* @public
*/
export const vaultExtensionPoint = createExtensionPoint<VaultExtensionPoint>({
id: 'vault.configuration',
});
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2023 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.
*/
export { type VaultExtensionPoint, vaultExtensionPoint } from './extensions';
export type { VaultApi, VaultSecret } from './api';
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2023 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.
*/
export {};
+11
View File
@@ -9822,11 +9822,13 @@ __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:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-vault-node": "workspace:^"
"@types/compression": ^1.7.2
"@types/express": "*"
"@types/supertest": ^2.0.8
@@ -9844,6 +9846,15 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-vault-node@workspace:^, @backstage/plugin-vault-node@workspace:plugins/vault-node":
version: 0.0.0-use.local
resolution: "@backstage/plugin-vault-node@workspace:plugins/vault-node"
dependencies:
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/cli": "workspace:^"
languageName: unknown
linkType: soft
"@backstage/plugin-vault@workspace:plugins/vault":
version: 0.0.0-use.local
resolution: "@backstage/plugin-vault@workspace:plugins/vault"