From 19c0b21a1ddb75543178ac4a250b5b7cff7fd55a Mon Sep 17 00:00:00 2001 From: Richard Simpson Date: Fri, 20 Sep 2019 17:56:08 -0500 Subject: [PATCH] feat: simplify input parameters and docs --- .github/workflows/test.yml | 8 ++--- README.md | 24 ++++++------- action.js | 60 ++++++++++++++++----------------- action.test.js | 46 ++++++++++++------------- action.yml | 12 +++---- integration/integration.test.js | 10 +++--- 6 files changed, 80 insertions(+), 80 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ecf4d9..bb9d0ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,12 +71,12 @@ jobs: env: VAULT_HOST: localhost VAULT_PORT: ${{ job.services.vault.ports[8200] }} - - name: use vault actions + - name: use vault action uses: ./ with: - vaultUrl: http://localhost:${{ job.services.vault.ports[8200] }} - vaultToken: testtoken - keys: | + url: http://localhost:${{ job.services.vault.ports[8200] }} + token: testtoken + secrets: | test secret ; test secret | NAMED_SECRET ; nested/test otherSecret ; diff --git a/README.md b/README.md index 61a82ba..73f038c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # vault-action -A helper action for retrieving vault secrets as env vars. +A helper action for easily pulling secrets from the v2 K/V backend of vault. ## Example Usage @@ -11,25 +11,25 @@ jobs: steps: # ... - name: Import Secrets - uses: richicoder1/vault-action + uses: RichiCoder1/vault-action with: - vaultUrl: https://vault.mycompany.com - vaultToken: ${{ secrets.VaultToken }} - keys: | + url: https://vault.mycompany.com:8200 + token: ${{ secrets.VaultToken }} + secrets: | ci/aws accessKey | AWS_ACCESS_KEY_ID ; ci/aws secretKey | AWS_SECRET_ACCESS_KEY ; - ci/npm token | NPM_TOKEN + ci npm_token # ... ``` ## Key Syntax -The `keys` parameter is multiple keys separated by the `;` character. +The `secrets` parameter is a set of multiple secret requests separated by the `;` character. -Each key is comprised of the `path` of they key, and optionally a [`JSONPath`](https://www.npmjs.com/package/jsonpath) expression and an output name. +Each secret request is comprised of the `path` and the `key` of the desired secret, and optionally the desired Env Var output name. ```raw -{{ Key Path }} > {{ JSONPath Query }} | {{ Output Environment Variable Name }} +{{ Secret Path }} {{ Secret Key }} | {{ Output Environment Variable Name }} ``` ### Simple Key @@ -38,7 +38,7 @@ To retrieve a key `npmToken` from path `ci` that has value `somelongtoken` from ```yaml with: - keys: ci npmToken + secrets: ci npmToken ``` `vault-action` will automatically normalize the given data key, and output: @@ -53,7 +53,7 @@ However, if you want to set it to a specific environmental variable, say `NPM_TO ```yaml with: - keys: ci npmToken | NPM_TOKEN + secrets: ci npmToken | NPM_TOKEN ``` With that, `vault-action` will now use your requested name and output: @@ -62,7 +62,7 @@ With that, `vault-action` will now use your requested name and output: NPM_TOKEN=somelongtoken ``` -### Multiple Keys +### Multiple Secrets This action can take multi-line input, so say you had your AWS keys stored in a path and wanted to retrieve both of them. You can do: diff --git a/action.js b/action.js index 3e3c433..a8c9a98 100644 --- a/action.js +++ b/action.js @@ -3,15 +3,15 @@ const command = require('@actions/core/lib/command'); const got = require('got'); async function exportSecrets() { - const vaultUrl = core.getInput('vaultUrl', { required: true }); - const vaultToken = core.getInput('vaultToken', { required: true }); + const vaultUrl = core.getInput('url', { required: true }); + const vaultToken = core.getInput('token', { required: true }); - const keysInput = core.getInput('keys', { required: true }); - const keys = parseKeysInput(keysInput); + const secretsInput = core.getInput('secrets', { required: true }); + const secrets = parseSecretsInput(secretsInput); - for (const key of keys) { - const { keyPath, outputName, dataKey } = key; - const result = await got(`${vaultUrl}/v1/secret/data/${keyPath}`, { + for (const secret of secrets) { + const { secretPath, outputName, secretKey } = secret; + const result = await got(`${vaultUrl}/v1/secret/data/${secretPath}`, { headers: { 'X-Vault-Token': vaultToken } @@ -20,37 +20,37 @@ async function exportSecrets() { const parsedResponse = JSON.parse(result.body); const vaultKeyData = parsedResponse.data; const versionData = vaultKeyData.data; - const value = versionData[dataKey]; + const value = versionData[secretKey]; command.issue('add-mask', value); core.exportVariable(outputName, `${value}`); - core.debug(`✔ ${keyPath} => ${outputName}`); + core.debug(`✔ ${secretPath} => ${outputName}`); } }; /** - * Parses a keys input string into key paths and their resulting environment variable name. - * @param {string} keys + * Parses a secrets input string into key paths and their resulting environment variable name. + * @param {string} secretsInput */ -function parseKeysInput(keys) { - const keyPairs = keys +function parseSecretsInput(secretsInput) { + const secrets = secretsInput .split(';') .filter(key => !!key) .map(key => key.trim()) .filter(key => key.length !== 0); - /** @type {{ keyPath: string; outputName: string; dataKey: string; }[]} */ + /** @type {{ secretPath: string; outputName: string; dataKey: string; }[]} */ const output = []; - for (const keyPair of keyPairs) { - let path = keyPair; + for (const secret of secrets) { + let path = secret; let outputName = null; - const renameSigilIndex = keyPair.lastIndexOf('|'); + const renameSigilIndex = secret.lastIndexOf('|'); if (renameSigilIndex > -1) { - path = keyPair.substring(0, renameSigilIndex).trim(); - outputName = keyPair.substring(renameSigilIndex + 1).trim(); + path = secret.substring(0, renameSigilIndex).trim(); + outputName = secret.substring(renameSigilIndex + 1).trim(); if (outputName.length < 1) { - throw Error(`You must provide a value when mapping a secret to a name. Input: "${keyPair}"`); + throw Error(`You must provide a value when mapping a secret to a name. Input: "${secret}"`); } } @@ -60,20 +60,20 @@ function parseKeysInput(keys) { .filter(part => part.length !== 0); if (pathParts.length !== 2) { - throw Error(`You must provide a valid path and key. Input: "${keyPair}"`) + throw Error(`You must provide a valid path and key. Input: "${secret}"`) } - const [keyPath, dataKey] = pathParts; + const [secretPath, secretKey] = pathParts; // If we're not using a mapped name, normalize the key path into a variable name. if (!outputName) { - outputName = normalizeKeyName(dataKey); + outputName = normalizeOutputKey(secretKey); } output.push({ - keyPath, + secretPath, outputName, - dataKey + secretKey }); } return output; @@ -81,14 +81,14 @@ function parseKeysInput(keys) { /** * Replaces any forward-slash characters to - * @param {string} keyPath + * @param {string} dataKey */ -function normalizeKeyName(keyPath) { - return keyPath.replace('/', '__').replace(/[^\w-]/, '').toUpperCase(); +function normalizeOutputKey(dataKey) { + return dataKey.replace('/', '__').replace(/[^\w-]/, '').toUpperCase(); } module.exports = { exportSecrets, - parseKeysInput, - normalizeKeyName + parseSecretsInput, + normalizeOutputKey }; \ No newline at end of file diff --git a/action.test.js b/action.test.js index bbe77df..840722e 100644 --- a/action.test.js +++ b/action.test.js @@ -6,23 +6,23 @@ const core = require('@actions/core'); const got = require('got'); const { exportSecrets, - parseKeysInput, + parseSecretsInput, } = require('./action'); const { when } = require('jest-when'); -describe('parseKeysInput', () => { - it('parses simple key', () => { - const output = parseKeysInput('test key'); +describe('parseSecretsInput', () => { + it('parses simple secret', () => { + const output = parseSecretsInput('test key'); expect(output).toContainEqual({ - keyPath: 'test', - dataKey: 'key', + secretPath: 'test', + secretKey: 'key', outputName: 'KEY', }); }); - it('parses mapped key', () => { - const output = parseKeysInput('test key|testName'); + it('parses mapped secret', () => { + const output = parseSecretsInput('test key|testName'); expect(output).toHaveLength(1); expect(output[0]).toMatchObject({ outputName: 'testName', @@ -30,29 +30,29 @@ describe('parseKeysInput', () => { }); it('fails on invalid mapped name', () => { - expect(() => parseKeysInput('test key|')) + expect(() => parseSecretsInput('test key|')) .toThrowError(`You must provide a value when mapping a secret to a name. Input: "test key|"`) }); it('fails on invalid path for mapped', () => { - expect(() => parseKeysInput('|testName')) + expect(() => parseSecretsInput('|testName')) .toThrowError(`You must provide a valid path and key. Input: "|testName"`) }); - it('parses multiple keys', () => { - const output = parseKeysInput('first a;second b;'); + it('parses multiple secrets', () => { + const output = parseSecretsInput('first a;second b;'); expect(output).toHaveLength(2); expect(output[0]).toMatchObject({ - keyPath: 'first', + secretPath: 'first', }); expect(output[1]).toMatchObject({ - keyPath: 'second', + secretPath: 'second', }); }); - it('parses multiple complex keys', () => { - const output = parseKeysInput('first a;second b|secondName'); + it('parses multiple complex secret input', () => { + const output = parseSecretsInput('first a;second b|secondName'); expect(output).toHaveLength(2); expect(output[0]).toMatchObject({ @@ -64,14 +64,14 @@ describe('parseKeysInput', () => { }); it('parses multiline input', () => { - const output = parseKeysInput(` + const output = parseSecretsInput(` first a; second b; third c | SOME_C;`); expect(output).toHaveLength(3); expect(output[0]).toMatchObject({ - keyPath: 'first', + secretPath: 'first', }); expect(output[1]).toMatchObject({ outputName: 'B', @@ -88,17 +88,17 @@ describe('exportSecrets', () => { jest.resetAllMocks(); when(core.getInput) - .calledWith('vaultUrl') + .calledWith('url') .mockReturnValue('http://vault:8200'); when(core.getInput) - .calledWith('vaultToken') + .calledWith('token') .mockReturnValue('EXAMPLE'); }); function mockInput(key) { when(core.getInput) - .calledWith('keys') + .calledWith('secrets') .mockReturnValue(key); } @@ -112,7 +112,7 @@ describe('exportSecrets', () => { }); } - it('simple key retrieval', async () => { + it('simple secret retrieval', async () => { mockInput('test key'); mockVaultData({ key: 1 @@ -123,7 +123,7 @@ describe('exportSecrets', () => { expect(core.exportVariable).toBeCalledWith('KEY', '1'); }); - it('mapped key retrieval', async () => { + it('mapped secret retrieval', async () => { mockInput('test key|TEST_NAME'); mockVaultData({ key: 1 diff --git a/action.yml b/action.yml index 3497022..2c9bf31 100644 --- a/action.yml +++ b/action.yml @@ -1,14 +1,14 @@ -name: 'Vault Secrets' -description: 'A Github Action that allows you to consume vault secrets as secure environment variables' +name: 'Vault' +description: 'A Github Action that allows you to consume the v2 K/V backend of HashiCorp Vault as secure environment variables' inputs: - vaultUrl: + url: description: 'The URL for the vault endpoint' required: true - vaultToken: + token: description: 'The Vault Token to be used to authenticate with Vault' required: true - keys: - description: 'A semicolon-separated list of key paths to retrieve. These will automatically be converted to environmental variable keys. See README for more details' + secrets: + description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details' required: true runs: using: 'node12' diff --git a/integration/integration.test.js b/integration/integration.test.js index 732b8f6..eb71c5b 100644 --- a/integration/integration.test.js +++ b/integration/integration.test.js @@ -48,18 +48,18 @@ describe('integration', () => { jest.resetAllMocks(); when(core.getInput) - .calledWith('vaultUrl') + .calledWith('url') .mockReturnValue(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`); when(core.getInput) - .calledWith('vaultToken') + .calledWith('token') .mockReturnValue('testtoken'); }); - function mockInput(key) { + function mockInput(secrets) { when(core.getInput) - .calledWith('keys') - .mockReturnValue(key); + .calledWith('secrets') + .mockReturnValue(secrets); } it('get simple secret', async () => {