Merge branch 'master' into vault-display-full-secret-name

This commit is contained in:
df11
2022-06-23 15:53:47 +02:00
committed by GitHub
503 changed files with 8187 additions and 4268 deletions
+13
View File
@@ -1,5 +1,18 @@
# @backstage/plugin-vault-backend
## 0.2.0-next.0
### Minor Changes
- 5ebf2c7023: Throw exceptions instead of swallow them, remove some exported types from the `api-report`, small changes in the API responses & expose the vault `baseUrl` to the frontend as well
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.14.1-next.0
- @backstage/backend-tasks@0.3.3-next.0
- @backstage/backend-test-utils@0.1.26-next.0
## 0.1.0
### Minor Changes
+2 -17
View File
@@ -12,13 +12,6 @@ import { TaskRunner } from '@backstage/backend-tasks';
// @public
export function createRouter(options: RouterOptions): express.Router;
// @public
export type RenewTokenResponse = {
auth: {
client_token: string;
};
};
// @public
export interface RouterOptions {
// (undocumented)
@@ -33,7 +26,7 @@ export interface RouterOptions {
export interface VaultApi {
getFrontendSecretsUrl(): string;
listSecrets(secretPath: string): Promise<VaultSecret[]>;
renewToken?(): Promise<boolean>;
renewToken?(): Promise<void>;
}
// @public
@@ -45,7 +38,6 @@ export class VaultBuilder {
enableTokenRenew(schedule?: TaskRunner): Promise<this>;
// (undocumented)
protected readonly env: VaultEnvironment;
protected renewToken(vaultClient: VaultClient): Promise<void>;
setVaultClient(vaultClient: VaultClient): this;
}
@@ -62,7 +54,7 @@ export class VaultClient implements VaultApi {
// (undocumented)
listSecrets(secretPath: string): Promise<VaultSecret[]>;
// (undocumented)
renewToken(): Promise<boolean>;
renewToken(): Promise<void>;
}
// @public
@@ -82,12 +74,5 @@ export type VaultSecret = {
editUrl: string;
};
// @public
export type VaultSecretList = {
data: {
keys: string[];
};
};
// (No @packageDocumentation comment for this package)
```
+1
View File
@@ -19,6 +19,7 @@ export interface Config {
vault?: {
/**
* The baseUrl for your Vault instance.
* @visibility frontend
*/
baseUrl: string;
+7 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-vault-backend",
"description": "A Backstage backend plugin that integrates towards Vault",
"version": "0.1.0",
"version": "0.2.0-next.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -34,9 +34,9 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-common": "^0.14.0",
"@backstage/backend-tasks": "^0.3.2",
"@backstage/backend-test-utils": "^0.1.25",
"@backstage/backend-common": "^0.14.1-next.0",
"@backstage/backend-tasks": "^0.3.3-next.0",
"@backstage/backend-test-utils": "^0.1.26-next.0",
"@backstage/config": "^1.0.1",
"@backstage/errors": "^1.0.0",
"@types/express": "*",
@@ -46,15 +46,16 @@
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"helmet": "^5.0.2",
"p-limit": "^3.1.0",
"winston": "^3.7.2",
"yn": "^5.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.2",
"@backstage/cli": "^0.17.3-next.0",
"@types/compression": "^1.7.2",
"@types/supertest": "^2.0.8",
"msw": "^0.42.0",
"supertest": "^4.0.2"
"supertest": "^6.1.6"
},
"files": [
"dist",
@@ -45,9 +45,6 @@ export type VaultBuilderReturn = {
* @public
*/
export class VaultBuilder {
// private vaultTokenRefreshInterval: Duration = Duration.fromObject({
// minutes: 60,
// });
private vaultClient?: VaultClient;
/**
@@ -118,26 +115,12 @@ export class VaultBuilder {
fn: async () => {
this.env.logger.info('Renewing Vault token');
const vaultClient = this.vaultClient ?? new VaultClient(this.env);
await this.renewToken(vaultClient);
await vaultClient.renewToken();
},
});
return this;
}
/**
* Renews the token for vault using a defined client.
*
* @param vaultClient - The vault client used to renew the token
*/
protected async renewToken(vaultClient: VaultClient) {
const result = await vaultClient.renewToken();
if (!result) {
this.env.logger.warn('Error renewing vault token');
} else {
this.env.logger.info('Vault token renewed');
}
}
/**
* Builds the backend routes for Vault.
*
@@ -159,7 +142,7 @@ export class VaultBuilder {
}
const secrets = await vaultClient.listSecrets(path);
res.json(secrets);
res.json({ items: secrets });
});
return router;
@@ -95,8 +95,7 @@ describe('VaultApi', () => {
it('should return success token renew', async () => {
setupHandlers();
const api = new VaultClient({ config });
const apiRenew = await api.renewToken();
expect(apiRenew).toBeTruthy();
expect(await api.renewToken()).toBe(undefined);
});
it('should render frontend url', () => {
+24 -25
View File
@@ -15,12 +15,14 @@
*/
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import fetch from 'cross-fetch';
import plimit from 'p-limit';
import { getVaultConfig, VaultConfig } from '../config';
/**
* Object received as a response from the Vault API when fetching secrets
* @public
* @internal
*/
export type VaultSecretList = {
data: {
@@ -41,9 +43,8 @@ export type VaultSecret = {
/**
* Object received as response when the token is renewed using the Vault API
* @public
*/
export type RenewTokenResponse = {
type RenewTokenResponse = {
auth: {
client_token: string;
};
@@ -64,10 +65,10 @@ export interface VaultApi {
*/
listSecrets(secretPath: string): Promise<VaultSecret[]>;
/**
* Optional, to renew the token used to list the secrets. Returns true
* if the action was successfull, false otherwise.
* Optional, to renew the token used to list the secrets. Throws an
* error if the token renewal went wrong.
*/
renewToken?(): Promise<boolean>;
renewToken?(): Promise<void>;
}
/**
@@ -76,6 +77,7 @@ export interface VaultApi {
*/
export class VaultClient implements VaultApi {
private vaultConfig: VaultConfig;
private readonly limit = plimit(5);
constructor({ config }: { config: Config }) {
this.vaultConfig = getVaultConfig(config);
@@ -85,7 +87,7 @@ export class VaultClient implements VaultApi {
path: string,
query: { [key in string]: any },
method: string = 'GET',
): Promise<T | undefined> {
): Promise<T> {
const url = new URL(path, this.vaultConfig.baseUrl);
const response = await fetch(
`${url.toString()}?${new URLSearchParams(query).toString()}`,
@@ -97,15 +99,14 @@ export class VaultClient implements VaultApi {
},
},
);
if (response.status === 200) {
if (response.ok) {
return (await response.json()) as T;
} else if (response.status === 404) {
throw new NotFoundError(`No secrets found in path '${path}'`);
}
return undefined;
}
private isFolder(secretName: string): boolean {
const regex = /^.*\/$/gm;
return regex.test(secretName);
throw new Error(
`Unexpected error while fetching secrets from path '${path}'`,
);
}
getFrontendSecretsUrl(): string {
@@ -117,17 +118,19 @@ export class VaultClient implements VaultApi {
this.vaultConfig.kvVersion === 2
? `v1/${this.vaultConfig.secretEngine}/metadata/${secretPath}`
: `v1/${this.vaultConfig.secretEngine}/${secretPath}`;
const result = await this.callApi<VaultSecretList>(listUrl, { list: true });
if (!result) {
return [];
}
const result = await this.limit(() =>
this.callApi<VaultSecretList>(listUrl, { list: true }),
);
const secrets: VaultSecret[] = [];
await Promise.all(
result.data.keys.map(async secret => {
if (this.isFolder(secret)) {
if (secret.endsWith('/')) {
secrets.push(
...(await this.listSecrets(`${secretPath}/${secret.slice(0, -1)}`)),
...(await this.limit(() =>
this.listSecrets(`${secretPath}/${secret.slice(0, -1)}`),
)),
);
} else {
secrets.push({
@@ -143,17 +146,13 @@ export class VaultClient implements VaultApi {
return secrets;
}
async renewToken(): Promise<boolean> {
async renewToken(): Promise<void> {
const result = await this.callApi<RenewTokenResponse>(
'v1/auth/token/renew-self',
{},
'POST',
);
if (!result) {
return false;
}
this.vaultConfig.token = result.auth.client_token;
return true;
}
}