5
0
Fork 0
mirror of https://github.com/hashicorp/vault-action.git synced 2025-11-10 00:26:55 +00:00

Initial check-in of wildcard to get all secrets in path (Issue#234)

This commit is contained in:
Lemme 2021-07-27 15:36:28 -04:00
parent 0451f06f9f
commit f09a7fe7c9
4 changed files with 160 additions and 33 deletions

View file

@ -162,6 +162,26 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET'); expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
}); });
it('get wildcard secrets', async () => {
mockInput(`secret/data/test * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
});
it('get wildcard secrets with name prefix', async () => {
mockInput(`secret/data/test * | GROUP_ ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET');
});
it('leading slash kvv2', async () => { it('leading slash kvv2', async () => {
mockInput('/secret/data/foobar fookv2'); mockInput('/secret/data/foobar fookv2');
@ -186,6 +206,26 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET'); expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
}); });
it('get K/V v1 wildcard secrets', async () => {
mockInput(`secret-kv1/test * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET');
});
it('get K/V v1 wildcard secrets with name prefix', async () => {
mockInput(`secret-kv1/test * | GROUP_ ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET');
});
it('leading slash kvv1', async () => { it('leading slash kvv1', async () => {
mockInput('/secret-kv1/foobar fookv1'); mockInput('/secret-kv1/foobar fookv1');

View file

@ -71,6 +71,22 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE'); expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE');
}); });
it('get wildcard secrets', async () => {
mockInput('secret/data/test *');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_IN_NAMESPACE');
});
it('get wildcard secrets with name prefix', async () => {
mockInput('secret/data/test * | GROUP_');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET_IN_NAMESPACE');
});
it('get nested secret', async () => { it('get nested secret', async () => {
mockInput('secret/data/nested/test otherSecret'); mockInput('secret/data/nested/test otherSecret');
@ -102,6 +118,22 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE'); expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
}); });
it('get wildcard secrets from K/V v1', async () => {
mockInput('my-secret/test *');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
});
it('get wildcard secrets from K/V v1 with name prefix', async () => {
mockInput('my-secret/test * | GROUP_');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
});
it('get nested secret from K/V v1', async () => { it('get nested secret from K/V v1', async () => {
mockInput('my-secret/nested/test otherSecret'); mockInput('my-secret/nested/test otherSecret');

View file

@ -3,6 +3,25 @@ const core = require('@actions/core');
const command = require('@actions/core/lib/command'); const command = require('@actions/core/lib/command');
const got = require('got').default; const got = require('got').default;
const jsonata = require('jsonata'); const jsonata = require('jsonata');
module.exports = {};
const wildcard = '*';
module.exports.wildcard = wildcard;
/**
* Replaces any dot chars to __ and removes non-ascii charts
* @param {string} dataKey
* @param {boolean=} isEnvVar
*/
function normalizeOutputKey(dataKey, isEnvVar = false) {
let outputKey = dataKey
.replace('.', '__').replace(new RegExp('-', 'g'), '').replace(/[^\p{L}\p{N}_-]/gu, '');
if (isEnvVar) {
outputKey = outputKey.toUpperCase();
}
return outputKey;
}
module.exports.normalizeOutputKey = normalizeOutputKey;
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index'); const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes']; const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes'];
@ -90,6 +109,7 @@ async function exportSecrets() {
core.debug(`${request.path} => outputs.${request.outputVarName}${exportEnv ? ` | env.${request.envVarName}` : ''}`); core.debug(`${request.path} => outputs.${request.outputVarName}${exportEnv ? ` | env.${request.envVarName}` : ''}`);
} }
}; };
module.exports.exportSecrets = exportSecrets;
/** @typedef {Object} SecretRequest /** @typedef {Object} SecretRequest
* @property {string} path * @property {string} path
@ -140,7 +160,7 @@ function parseSecretsInput(secretsInput) {
const selectorAst = jsonata(selectorQuoted).ast(); const selectorAst = jsonata(selectorQuoted).ast();
const selector = selectorQuoted.replace(new RegExp('"', 'g'), ''); const selector = selectorQuoted.replace(new RegExp('"', 'g'), '');
if ((selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) { if (selector !== wildcard && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`); throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
} }
@ -159,20 +179,7 @@ function parseSecretsInput(secretsInput) {
} }
return output; return output;
} }
module.exports.parseSecretsInput = parseSecretsInput;
/**
* Replaces any dot chars to __ and removes non-ascii charts
* @param {string} dataKey
* @param {boolean=} isEnvVar
*/
function normalizeOutputKey(dataKey, isEnvVar = false) {
let outputKey = dataKey
.replace('.', '__').replace(new RegExp('-', 'g'), '').replace(/[^\p{L}\p{N}_-]/gu, '');
if (isEnvVar) {
outputKey = outputKey.toUpperCase();
}
return outputKey;
}
/** /**
* @param {string} inputKey * @param {string} inputKey
@ -198,10 +205,14 @@ function parseHeadersInput(inputKey, inputOptions) {
return map; return map;
}, new Map()); }, new Map());
} }
module.exports.parseHeadersInput = parseHeadersInput;
// restructured module.exports to avoid circular dependency when secrets imports this.
/*
module.exports = { module.exports = {
exportSecrets, exportSecrets,
parseSecretsInput, parseSecretsInput,
normalizeOutputKey, normalizeOutputKey,
parseHeadersInput parseHeadersInput,
wildcard
}; };
*/

View file

@ -1,6 +1,5 @@
const jsonata = require("jsonata"); const jsonata = require("jsonata");
const { normalizeOutputKey, wildcard} = require('./action');
/** /**
* @typedef {Object} SecretRequest * @typedef {Object} SecretRequest
* @property {string} path * @property {string} path
@ -24,6 +23,7 @@ const jsonata = require("jsonata");
async function getSecrets(secretRequests, client) { async function getSecrets(secretRequests, client) {
const responseCache = new Map(); const responseCache = new Map();
const results = []; const results = [];
for (const secretRequest of secretRequests) { for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest; let { path, selector } = secretRequest;
@ -38,25 +38,69 @@ async function getSecrets(secretRequests, client) {
body = result.body; body = result.body;
responseCache.set(requestPath, body); responseCache.set(requestPath, body);
} }
if (!selector.match(/.*[\.].*/)) {
selector = '"' + selector + '"'
}
selector = "data." + selector
body = JSON.parse(body)
if (body.data["data"] != undefined) {
selector = "data." + selector
}
const value = selectData(body, selector); if (selector == wildcard) {
results.push({ body = JSON.parse(body);
request: secretRequest, const keys = body.data;
value, for (let key in keys) {
cachedResponse let newRequest = Object.assign({},secretRequest);
}); newRequest.selector = key;
if (secretRequest.selector === secretRequest.outputVarName) {
newRequest.outputVarName = key;
newRequest.envVarName = key;
}
else {
newRequest.outputVarName = secretRequest.outputVarName+key;
newRequest.envVarName = secretRequest.envVarName+key;
}
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
selector = key;
//This code (with exception of parsing body again and using newRequest instead of secretRequest) should match the else code for a single key
if (!selector.match(/.*[\.].*/)) {
selector = '"' + selector + '"'
}
selector = "data." + selector
//body = JSON.parse(body)
if (body.data["data"] != undefined) {
selector = "data." + selector
}
const value = selectData(body, selector);
results.push({
request: newRequest,
value,
cachedResponse
});
// used cachedResponse for first entry in wildcard list and set to true for the rest
cachedResponse = true;
}
}
else {
if (!selector.match(/.*[\.].*/)) {
selector = '"' + selector + '"'
}
selector = "data." + selector
body = JSON.parse(body)
if (body.data["data"] != undefined) {
selector = "data." + selector
}
const value = selectData(body, selector);
results.push({
request: secretRequest,
value,
cachedResponse
});
}
} }
return results; return results;
} }
/** /**
* Uses a Jsonata selector retrieve a bit of data from the result * Uses a Jsonata selector retrieve a bit of data from the result
* @param {object} data * @param {object} data