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

feat: added double asterisk wildcard selector to prevent uppercasing of keys before exporting envs (#545)

* feat: added double asterisk wildcard selector to prevent uppercasing of keys before exporting envs

* chore: update changelog

---------

Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>
This commit is contained in:
Rory 2025-03-03 23:31:00 +02:00 committed by GitHub
parent 4b1f32b395
commit 7709c60978
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 69 additions and 20 deletions

View file

@ -1,3 +1,8 @@
## 3.3.0 (March 3, 2025)
Features:
* Wildcard secret imports can use `**` to retain case of exported env keys [GH-545](https://github.com/hashicorp/vault-action/pull/545)
## 3.2.0 (March 3, 2025) ## 3.2.0 (March 3, 2025)
Improvements: Improvements:

View file

@ -403,6 +403,15 @@ with:
secret/data/ci/aws * | MYAPP_ ; secret/data/ci/aws * | MYAPP_ ;
``` ```
When using the `exportEnv` option all exported keys will be normalized to uppercase. For example, the key `SecretKey` would be exported as `MYAPP_SECRETKEY`.
You can disable uppercase normalization by specifying double asterisks `**` in the selector path:
```yaml
with:
secrets: |
secret/data/ci/aws ** | MYAPP_ ;
```
### KV secrets engine version 2 ### KV secrets engine version 2
When accessing secrets from the KV secrets engine version 2, Vault Action When accessing secrets from the KV secrets engine version 2, Vault Action

23
dist/index.js vendored
View file

@ -18535,7 +18535,7 @@ const command = __nccwpck_require__(7351);
const got = (__nccwpck_require__(3061)["default"]); const got = (__nccwpck_require__(3061)["default"]);
const jsonata = __nccwpck_require__(4245); const jsonata = __nccwpck_require__(4245);
const { normalizeOutputKey } = __nccwpck_require__(1608); const { normalizeOutputKey } = __nccwpck_require__(1608);
const { WILDCARD } = __nccwpck_require__(4438); const { WILDCARD, WILDCARD_UPPERCASE } = __nccwpck_require__(4438);
const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = __nccwpck_require__(4351); const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = __nccwpck_require__(4351);
@ -18752,7 +18752,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 (selector !== WILDCARD && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) { if (selector !== WILDCARD && selector !== WILDCARD_UPPERCASE && (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}"`);
} }
@ -19005,12 +19005,15 @@ module.exports = {
/***/ 4438: /***/ 4438:
/***/ ((module) => { /***/ ((module) => {
const WILDCARD = '*'; const WILDCARD_UPPERCASE = '*';
const WILDCARD = '**';
module.exports = { module.exports = {
WILDCARD WILDCARD,
WILDCARD_UPPERCASE,
}; };
/***/ }), /***/ }),
/***/ 4351: /***/ 4351:
@ -19114,7 +19117,7 @@ module.exports = {
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
const jsonata = __nccwpck_require__(4245); const jsonata = __nccwpck_require__(4245);
const { WILDCARD } = __nccwpck_require__(4438); const { WILDCARD, WILDCARD_UPPERCASE} = __nccwpck_require__(4438);
const { normalizeOutputKey } = __nccwpck_require__(1608); const { normalizeOutputKey } = __nccwpck_require__(1608);
const core = __nccwpck_require__(2186); const core = __nccwpck_require__(2186);
@ -19141,6 +19144,7 @@ const core = __nccwpck_require__(2186);
async function getSecrets(secretRequests, client, ignoreNotFound) { async function getSecrets(secretRequests, client, ignoreNotFound) {
const responseCache = new Map(); const responseCache = new Map();
let results = []; let results = [];
let upperCaseEnv = false;
for (const secretRequest of secretRequests) { for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest; let { path, selector } = secretRequest;
@ -19174,7 +19178,8 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
body = JSON.parse(body); body = JSON.parse(body);
if (selector == WILDCARD) { if (selector === WILDCARD || selector === WILDCARD_UPPERCASE) {
upperCaseEnv = selector === WILDCARD_UPPERCASE;
let keys = body.data; let keys = body.data;
if (body.data["data"] != undefined) { if (body.data["data"] != undefined) {
keys = keys.data; keys = keys.data;
@ -19193,7 +19198,7 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
} }
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName); newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true); newRequest.envVarName = normalizeOutputKey(newRequest.envVarName, upperCaseEnv);
// JSONata field references containing reserved tokens should // JSONata field references containing reserved tokens should
// be enclosed in backticks // be enclosed in backticks
@ -19302,12 +19307,12 @@ module.exports = {
* @param {string} dataKey * @param {string} dataKey
* @param {boolean=} isEnvVar * @param {boolean=} isEnvVar
*/ */
function normalizeOutputKey(dataKey, isEnvVar = false) { function normalizeOutputKey(dataKey, upperCase = false) {
let outputKey = dataKey let outputKey = dataKey
.replace(".", "__") .replace(".", "__")
.replace(new RegExp("-", "g"), "") .replace(new RegExp("-", "g"), "")
.replace(/[^\p{L}\p{N}_-]/gu, ""); .replace(/[^\p{L}\p{N}_-]/gu, "");
if (isEnvVar) { if (upperCase) {
outputKey = outputKey.toUpperCase(); outputKey = outputKey.toUpperCase();
} }
return outputKey; return outputKey;

View file

@ -395,7 +395,7 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('FOO', 'bar'); expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
}); });
it('wildcard supports cubbyhole', async () => { it('wildcard supports cubbyhole with uppercase transform', async () => {
mockInput('/cubbyhole/test *'); mockInput('/cubbyhole/test *');
await exportSecrets(); await exportSecrets();
@ -406,6 +406,32 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap'); expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
}); });
it('wildcard supports cubbyhole with no change in case', async () => {
mockInput('/cubbyhole/test **');
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(2);
expect(core.exportVariable).toBeCalledWith('foo', 'bar');
expect(core.exportVariable).toBeCalledWith('zip', 'zap');
});
it('wildcard supports cubbyhole with mixed case change', async () => {
mockInput(`
/cubbyhole/test * ;
/cubbyhole/test **`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(4);
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
expect(core.exportVariable).toBeCalledWith('foo', 'bar');
expect(core.exportVariable).toBeCalledWith('zip', 'zap');
});
it('caches responses', async () => { it('caches responses', async () => {
mockInput(` mockInput(`
/cubbyhole/test foo ; /cubbyhole/test foo ;

View file

@ -4,7 +4,7 @@ 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');
const { normalizeOutputKey } = require('./utils'); const { normalizeOutputKey } = require('./utils');
const { WILDCARD } = require('./constants'); const { WILDCARD, WILDCARD_UPPERCASE } = require('./constants');
const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = require('./index'); const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = require('./index');
@ -221,7 +221,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 (selector !== WILDCARD && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) { if (selector !== WILDCARD && selector !== WILDCARD_UPPERCASE && (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}"`);
} }

View file

@ -1,5 +1,7 @@
const WILDCARD = '*'; const WILDCARD_UPPERCASE = '*';
const WILDCARD = '**';
module.exports = { module.exports = {
WILDCARD WILDCARD,
WILDCARD_UPPERCASE,
}; };

View file

@ -1,5 +1,5 @@
const jsonata = require("jsonata"); const jsonata = require("jsonata");
const { WILDCARD } = require("./constants"); const { WILDCARD, WILDCARD_UPPERCASE} = require("./constants");
const { normalizeOutputKey } = require("./utils"); const { normalizeOutputKey } = require("./utils");
const core = require('@actions/core'); const core = require('@actions/core');
@ -26,6 +26,7 @@ const core = require('@actions/core');
async function getSecrets(secretRequests, client, ignoreNotFound) { async function getSecrets(secretRequests, client, ignoreNotFound) {
const responseCache = new Map(); const responseCache = new Map();
let results = []; let results = [];
let upperCaseEnv = false;
for (const secretRequest of secretRequests) { for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest; let { path, selector } = secretRequest;
@ -59,7 +60,8 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
body = JSON.parse(body); body = JSON.parse(body);
if (selector == WILDCARD) { if (selector === WILDCARD || selector === WILDCARD_UPPERCASE) {
upperCaseEnv = selector === WILDCARD_UPPERCASE;
let keys = body.data; let keys = body.data;
if (body.data["data"] != undefined) { if (body.data["data"] != undefined) {
keys = keys.data; keys = keys.data;
@ -78,7 +80,7 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
} }
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName); newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true); newRequest.envVarName = normalizeOutputKey(newRequest.envVarName, upperCaseEnv);
// JSONata field references containing reserved tokens should // JSONata field references containing reserved tokens should
// be enclosed in backticks // be enclosed in backticks

View file

@ -3,12 +3,12 @@
* @param {string} dataKey * @param {string} dataKey
* @param {boolean=} isEnvVar * @param {boolean=} isEnvVar
*/ */
function normalizeOutputKey(dataKey, isEnvVar = false) { function normalizeOutputKey(dataKey, upperCase = false) {
let outputKey = dataKey let outputKey = dataKey
.replace(".", "__") .replace(".", "__")
.replace(new RegExp("-", "g"), "") .replace(new RegExp("-", "g"), "")
.replace(/[^\p{L}\p{N}_-]/gu, ""); .replace(/[^\p{L}\p{N}_-]/gu, "");
if (isEnvVar) { if (upperCase) {
outputKey = outputKey.toUpperCase(); outputKey = outputKey.toUpperCase();
} }
return outputKey; return outputKey;