5
0
Fork 0
mirror of https://github.com/hashicorp/vault-action.git synced 2025-11-07 15:16:56 +00:00

feat: simplify input parameters and docs

This commit is contained in:
Richard Simpson 2019-09-20 17:56:08 -05:00
parent 9c32e02d93
commit 19c0b21a1d
No known key found for this signature in database
GPG key ID: 0CECAF50D013D1E2
6 changed files with 80 additions and 80 deletions

View file

@ -71,12 +71,12 @@ jobs:
env: env:
VAULT_HOST: localhost VAULT_HOST: localhost
VAULT_PORT: ${{ job.services.vault.ports[8200] }} VAULT_PORT: ${{ job.services.vault.ports[8200] }}
- name: use vault actions - name: use vault action
uses: ./ uses: ./
with: with:
vaultUrl: http://localhost:${{ job.services.vault.ports[8200] }} url: http://localhost:${{ job.services.vault.ports[8200] }}
vaultToken: testtoken token: testtoken
keys: | secrets: |
test secret ; test secret ;
test secret | NAMED_SECRET ; test secret | NAMED_SECRET ;
nested/test otherSecret ; nested/test otherSecret ;

View file

@ -1,6 +1,6 @@
# vault-action # 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 ## Example Usage
@ -11,25 +11,25 @@ jobs:
steps: steps:
# ... # ...
- name: Import Secrets - name: Import Secrets
uses: richicoder1/vault-action uses: RichiCoder1/vault-action
with: with:
vaultUrl: https://vault.mycompany.com url: https://vault.mycompany.com:8200
vaultToken: ${{ secrets.VaultToken }} token: ${{ secrets.VaultToken }}
keys: | secrets: |
ci/aws accessKey | AWS_ACCESS_KEY_ID ; ci/aws accessKey | AWS_ACCESS_KEY_ID ;
ci/aws secretKey | AWS_SECRET_ACCESS_KEY ; ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
ci/npm token | NPM_TOKEN ci npm_token
# ... # ...
``` ```
## Key Syntax ## 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 ```raw
{{ Key Path }} > {{ JSONPath Query }} | {{ Output Environment Variable Name }} {{ Secret Path }} {{ Secret Key }} | {{ Output Environment Variable Name }}
``` ```
### Simple Key ### Simple Key
@ -38,7 +38,7 @@ To retrieve a key `npmToken` from path `ci` that has value `somelongtoken` from
```yaml ```yaml
with: with:
keys: ci npmToken secrets: ci npmToken
``` ```
`vault-action` will automatically normalize the given data key, and output: `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 ```yaml
with: with:
keys: ci npmToken | NPM_TOKEN secrets: ci npmToken | NPM_TOKEN
``` ```
With that, `vault-action` will now use your requested name and output: 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 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: 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:

View file

@ -3,15 +3,15 @@ const command = require('@actions/core/lib/command');
const got = require('got'); const got = require('got');
async function exportSecrets() { async function exportSecrets() {
const vaultUrl = core.getInput('vaultUrl', { required: true }); const vaultUrl = core.getInput('url', { required: true });
const vaultToken = core.getInput('vaultToken', { required: true }); const vaultToken = core.getInput('token', { required: true });
const keysInput = core.getInput('keys', { required: true }); const secretsInput = core.getInput('secrets', { required: true });
const keys = parseKeysInput(keysInput); const secrets = parseSecretsInput(secretsInput);
for (const key of keys) { for (const secret of secrets) {
const { keyPath, outputName, dataKey } = key; const { secretPath, outputName, secretKey } = secret;
const result = await got(`${vaultUrl}/v1/secret/data/${keyPath}`, { const result = await got(`${vaultUrl}/v1/secret/data/${secretPath}`, {
headers: { headers: {
'X-Vault-Token': vaultToken 'X-Vault-Token': vaultToken
} }
@ -20,37 +20,37 @@ async function exportSecrets() {
const parsedResponse = JSON.parse(result.body); const parsedResponse = JSON.parse(result.body);
const vaultKeyData = parsedResponse.data; const vaultKeyData = parsedResponse.data;
const versionData = vaultKeyData.data; const versionData = vaultKeyData.data;
const value = versionData[dataKey]; const value = versionData[secretKey];
command.issue('add-mask', value); command.issue('add-mask', value);
core.exportVariable(outputName, `${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. * Parses a secrets input string into key paths and their resulting environment variable name.
* @param {string} keys * @param {string} secretsInput
*/ */
function parseKeysInput(keys) { function parseSecretsInput(secretsInput) {
const keyPairs = keys const secrets = secretsInput
.split(';') .split(';')
.filter(key => !!key) .filter(key => !!key)
.map(key => key.trim()) .map(key => key.trim())
.filter(key => key.length !== 0); .filter(key => key.length !== 0);
/** @type {{ keyPath: string; outputName: string; dataKey: string; }[]} */ /** @type {{ secretPath: string; outputName: string; dataKey: string; }[]} */
const output = []; const output = [];
for (const keyPair of keyPairs) { for (const secret of secrets) {
let path = keyPair; let path = secret;
let outputName = null; let outputName = null;
const renameSigilIndex = keyPair.lastIndexOf('|'); const renameSigilIndex = secret.lastIndexOf('|');
if (renameSigilIndex > -1) { if (renameSigilIndex > -1) {
path = keyPair.substring(0, renameSigilIndex).trim(); path = secret.substring(0, renameSigilIndex).trim();
outputName = keyPair.substring(renameSigilIndex + 1).trim(); outputName = secret.substring(renameSigilIndex + 1).trim();
if (outputName.length < 1) { 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); .filter(part => part.length !== 0);
if (pathParts.length !== 2) { 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 we're not using a mapped name, normalize the key path into a variable name.
if (!outputName) { if (!outputName) {
outputName = normalizeKeyName(dataKey); outputName = normalizeOutputKey(secretKey);
} }
output.push({ output.push({
keyPath, secretPath,
outputName, outputName,
dataKey secretKey
}); });
} }
return output; return output;
@ -81,14 +81,14 @@ function parseKeysInput(keys) {
/** /**
* Replaces any forward-slash characters to * Replaces any forward-slash characters to
* @param {string} keyPath * @param {string} dataKey
*/ */
function normalizeKeyName(keyPath) { function normalizeOutputKey(dataKey) {
return keyPath.replace('/', '__').replace(/[^\w-]/, '').toUpperCase(); return dataKey.replace('/', '__').replace(/[^\w-]/, '').toUpperCase();
} }
module.exports = { module.exports = {
exportSecrets, exportSecrets,
parseKeysInput, parseSecretsInput,
normalizeKeyName normalizeOutputKey
}; };

View file

@ -6,23 +6,23 @@ const core = require('@actions/core');
const got = require('got'); const got = require('got');
const { const {
exportSecrets, exportSecrets,
parseKeysInput, parseSecretsInput,
} = require('./action'); } = require('./action');
const { when } = require('jest-when'); const { when } = require('jest-when');
describe('parseKeysInput', () => { describe('parseSecretsInput', () => {
it('parses simple key', () => { it('parses simple secret', () => {
const output = parseKeysInput('test key'); const output = parseSecretsInput('test key');
expect(output).toContainEqual({ expect(output).toContainEqual({
keyPath: 'test', secretPath: 'test',
dataKey: 'key', secretKey: 'key',
outputName: 'KEY', outputName: 'KEY',
}); });
}); });
it('parses mapped key', () => { it('parses mapped secret', () => {
const output = parseKeysInput('test key|testName'); const output = parseSecretsInput('test key|testName');
expect(output).toHaveLength(1); expect(output).toHaveLength(1);
expect(output[0]).toMatchObject({ expect(output[0]).toMatchObject({
outputName: 'testName', outputName: 'testName',
@ -30,29 +30,29 @@ describe('parseKeysInput', () => {
}); });
it('fails on invalid mapped name', () => { 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|"`) .toThrowError(`You must provide a value when mapping a secret to a name. Input: "test key|"`)
}); });
it('fails on invalid path for mapped', () => { it('fails on invalid path for mapped', () => {
expect(() => parseKeysInput('|testName')) expect(() => parseSecretsInput('|testName'))
.toThrowError(`You must provide a valid path and key. Input: "|testName"`) .toThrowError(`You must provide a valid path and key. Input: "|testName"`)
}); });
it('parses multiple keys', () => { it('parses multiple secrets', () => {
const output = parseKeysInput('first a;second b;'); const output = parseSecretsInput('first a;second b;');
expect(output).toHaveLength(2); expect(output).toHaveLength(2);
expect(output[0]).toMatchObject({ expect(output[0]).toMatchObject({
keyPath: 'first', secretPath: 'first',
}); });
expect(output[1]).toMatchObject({ expect(output[1]).toMatchObject({
keyPath: 'second', secretPath: 'second',
}); });
}); });
it('parses multiple complex keys', () => { it('parses multiple complex secret input', () => {
const output = parseKeysInput('first a;second b|secondName'); const output = parseSecretsInput('first a;second b|secondName');
expect(output).toHaveLength(2); expect(output).toHaveLength(2);
expect(output[0]).toMatchObject({ expect(output[0]).toMatchObject({
@ -64,14 +64,14 @@ describe('parseKeysInput', () => {
}); });
it('parses multiline input', () => { it('parses multiline input', () => {
const output = parseKeysInput(` const output = parseSecretsInput(`
first a; first a;
second b; second b;
third c | SOME_C;`); third c | SOME_C;`);
expect(output).toHaveLength(3); expect(output).toHaveLength(3);
expect(output[0]).toMatchObject({ expect(output[0]).toMatchObject({
keyPath: 'first', secretPath: 'first',
}); });
expect(output[1]).toMatchObject({ expect(output[1]).toMatchObject({
outputName: 'B', outputName: 'B',
@ -88,17 +88,17 @@ describe('exportSecrets', () => {
jest.resetAllMocks(); jest.resetAllMocks();
when(core.getInput) when(core.getInput)
.calledWith('vaultUrl') .calledWith('url')
.mockReturnValue('http://vault:8200'); .mockReturnValue('http://vault:8200');
when(core.getInput) when(core.getInput)
.calledWith('vaultToken') .calledWith('token')
.mockReturnValue('EXAMPLE'); .mockReturnValue('EXAMPLE');
}); });
function mockInput(key) { function mockInput(key) {
when(core.getInput) when(core.getInput)
.calledWith('keys') .calledWith('secrets')
.mockReturnValue(key); .mockReturnValue(key);
} }
@ -112,7 +112,7 @@ describe('exportSecrets', () => {
}); });
} }
it('simple key retrieval', async () => { it('simple secret retrieval', async () => {
mockInput('test key'); mockInput('test key');
mockVaultData({ mockVaultData({
key: 1 key: 1
@ -123,7 +123,7 @@ describe('exportSecrets', () => {
expect(core.exportVariable).toBeCalledWith('KEY', '1'); expect(core.exportVariable).toBeCalledWith('KEY', '1');
}); });
it('mapped key retrieval', async () => { it('mapped secret retrieval', async () => {
mockInput('test key|TEST_NAME'); mockInput('test key|TEST_NAME');
mockVaultData({ mockVaultData({
key: 1 key: 1

View file

@ -1,14 +1,14 @@
name: 'Vault Secrets' name: 'Vault'
description: 'A Github Action that allows you to consume vault secrets as secure environment variables' description: 'A Github Action that allows you to consume the v2 K/V backend of HashiCorp Vault as secure environment variables'
inputs: inputs:
vaultUrl: url:
description: 'The URL for the vault endpoint' description: 'The URL for the vault endpoint'
required: true required: true
vaultToken: token:
description: 'The Vault Token to be used to authenticate with Vault' description: 'The Vault Token to be used to authenticate with Vault'
required: true required: true
keys: secrets:
description: 'A semicolon-separated list of key paths to retrieve. These will automatically be converted to environmental variable keys. See README for more details' 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 required: true
runs: runs:
using: 'node12' using: 'node12'

View file

@ -48,18 +48,18 @@ describe('integration', () => {
jest.resetAllMocks(); jest.resetAllMocks();
when(core.getInput) when(core.getInput)
.calledWith('vaultUrl') .calledWith('url')
.mockReturnValue(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`); .mockReturnValue(`http://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`);
when(core.getInput) when(core.getInput)
.calledWith('vaultToken') .calledWith('token')
.mockReturnValue('testtoken'); .mockReturnValue('testtoken');
}); });
function mockInput(key) { function mockInput(secrets) {
when(core.getInput) when(core.getInput)
.calledWith('keys') .calledWith('secrets')
.mockReturnValue(key); .mockReturnValue(secrets);
} }
it('get simple secret', async () => { it('get simple secret', async () => {