mirror of
https://github.com/hashicorp/vault-action.git
synced 2025-11-09 16:16:55 +00:00
Initial check-in of wildcard to get all secrets in path (Issue#234)
This commit is contained in:
parent
0451f06f9f
commit
f09a7fe7c9
4 changed files with 160 additions and 33 deletions
|
|
@ -162,6 +162,26 @@ describe('integration', () => {
|
|||
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 () => {
|
||||
mockInput('/secret/data/foobar fookv2');
|
||||
|
||||
|
|
@ -186,6 +206,26 @@ describe('integration', () => {
|
|||
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 () => {
|
||||
mockInput('/secret-kv1/foobar fookv1');
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,22 @@ describe('integration', () => {
|
|||
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 () => {
|
||||
mockInput('secret/data/nested/test otherSecret');
|
||||
|
||||
|
|
@ -102,6 +118,22 @@ describe('integration', () => {
|
|||
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 () => {
|
||||
mockInput('my-secret/nested/test otherSecret');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,25 @@ const core = require('@actions/core');
|
|||
const command = require('@actions/core/lib/command');
|
||||
const got = require('got').default;
|
||||
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_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes'];
|
||||
|
|
@ -90,6 +109,7 @@ async function exportSecrets() {
|
|||
core.debug(`✔ ${request.path} => outputs.${request.outputVarName}${exportEnv ? ` | env.${request.envVarName}` : ''}`);
|
||||
}
|
||||
};
|
||||
module.exports.exportSecrets = exportSecrets;
|
||||
|
||||
/** @typedef {Object} SecretRequest
|
||||
* @property {string} path
|
||||
|
|
@ -140,7 +160,7 @@ function parseSecretsInput(secretsInput) {
|
|||
const selectorAst = jsonata(selectorQuoted).ast();
|
||||
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}"`);
|
||||
}
|
||||
|
||||
|
|
@ -159,20 +179,7 @@ function parseSecretsInput(secretsInput) {
|
|||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.parseSecretsInput = parseSecretsInput;
|
||||
|
||||
/**
|
||||
* @param {string} inputKey
|
||||
|
|
@ -198,10 +205,14 @@ function parseHeadersInput(inputKey, inputOptions) {
|
|||
return map;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
module.exports.parseHeadersInput = parseHeadersInput;
|
||||
// restructured module.exports to avoid circular dependency when secrets imports this.
|
||||
/*
|
||||
module.exports = {
|
||||
exportSecrets,
|
||||
parseSecretsInput,
|
||||
normalizeOutputKey,
|
||||
parseHeadersInput
|
||||
parseHeadersInput,
|
||||
wildcard
|
||||
};
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const jsonata = require("jsonata");
|
||||
|
||||
|
||||
const { normalizeOutputKey, wildcard} = require('./action');
|
||||
/**
|
||||
* @typedef {Object} SecretRequest
|
||||
* @property {string} path
|
||||
|
|
@ -24,6 +23,7 @@ const jsonata = require("jsonata");
|
|||
async function getSecrets(secretRequests, client) {
|
||||
const responseCache = new Map();
|
||||
const results = [];
|
||||
|
||||
for (const secretRequest of secretRequests) {
|
||||
let { path, selector } = secretRequest;
|
||||
|
||||
|
|
@ -38,25 +38,69 @@ async function getSecrets(secretRequests, client) {
|
|||
body = result.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);
|
||||
results.push({
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse
|
||||
});
|
||||
if (selector == wildcard) {
|
||||
body = JSON.parse(body);
|
||||
const keys = body.data;
|
||||
for (let key in keys) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses a Jsonata selector retrieve a bit of data from the result
|
||||
* @param {object} data
|
||||
|
|
|
|||
Loading…
Reference in a new issue