Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Improvements:
Features:

* `secretId` is no longer required for approle to support advanced use cases like machine login when `bind_secret_id` is false. [GH-522](https://github.com/hashicorp/vault-action/pull/522)
* Wildcard secret imports can use `**` to retain case of exported env keys [GH-545](https://github.com/hashicorp/vault-action/pull/545)

## 3.0.0 (February 15, 2024)

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,14 @@ with:
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 disable uppercase normalization by specifying double asterisks `**` in the selector path:

```yaml
with:
secrets: |
secret/data/ci/aws ** | MYAPP_ ;
```

## Other Secret Engines

Vault Action currently supports retrieving secrets from any engine where secrets
Expand Down
42 changes: 31 additions & 11 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9686,6 +9686,13 @@ const dateTime = (function () {
if (offset === 0 && markerSpec.presentation2 === 't') {
componentValue = 'Z';
}
} else if (markerSpec.component === 'P') {
// §9.8.4.7 Formatting Other Components
// Formatting P for am/pm
// getDateTimeFragment() always returns am/pm lower case so check for UPPER here
if (markerSpec.names === tcase.UPPER) {
componentValue = componentValue.toUpperCase();
}
}
return componentValue;
};
Expand Down Expand Up @@ -13501,6 +13508,13 @@ var jsonata = (function() {
}
for(var ii = 0; ii < matches.length; ii++) {
var match = matches[ii];
if (match && (match.isPrototypeOf(result) || match instanceof Object.constructor)) {
throw {
code: "D1010",
stack: (new Error()).stack,
position: expr.position
};
}
// evaluate the update value for each match
var update = await evaluate(expr.update, match, environment);
// update must be an object
Expand Down Expand Up @@ -13747,7 +13761,7 @@ var jsonata = (function() {
if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') {
err.token = proc.token;
}
err.position = proc.position;
err.position = proc.position || err.position;
}
throw err;
}
Expand Down Expand Up @@ -14180,6 +14194,7 @@ var jsonata = (function() {
"T1007": "Attempted to partially apply a non-function. Did you mean ${{{token}}}?",
"T1008": "Attempted to partially apply a non-function",
"D1009": "Multiple key definitions evaluate to same key: {{value}}",
"D1010": "Attempted to access the Javascript object prototype", // Javascript specific
"T1010": "The matcher function argument passed to function {{token}} does not return the correct object structure",
"T2001": "The left side of the {{token}} operator must evaluate to a number",
"T2002": "The right side of the {{token}} operator must evaluate to a number",
Expand Down Expand Up @@ -18520,7 +18535,7 @@ const command = __nccwpck_require__(7351);
const got = (__nccwpck_require__(3061)["default"]);
const jsonata = __nccwpck_require__(4245);
const { normalizeOutputKey } = __nccwpck_require__(1608);
const { WILDCARD } = __nccwpck_require__(4438);
const { WILDCARD, WILDCARD_UPPERCASE } = __nccwpck_require__(4438);

const { auth: { retrieveToken }, secrets: { getSecrets } } = __nccwpck_require__(4351);

Expand Down Expand Up @@ -18690,7 +18705,7 @@ function parseSecretsInput(secretsInput) {
const selectorAst = jsonata(selectorQuoted).ast();
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}"`);
}

Expand Down Expand Up @@ -18767,7 +18782,7 @@ async function retrieveToken(method, client) {
switch (method) {
case 'approle': {
const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: false });
return await getClientToken(client, method, path, { role_id: vaultRoleId, secret_id: vaultSecretId });
}
case 'github': {
Expand Down Expand Up @@ -18914,12 +18929,15 @@ module.exports = {
/***/ 4438:
/***/ ((module) => {

const WILDCARD = '*';
const WILDCARD_UPPERCASE = '*';
const WILDCARD = '**';

module.exports = {
WILDCARD
WILDCARD,
WILDCARD_UPPERCASE,
};


/***/ }),

/***/ 4351:
Expand All @@ -18939,7 +18957,7 @@ module.exports = {
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

const jsonata = __nccwpck_require__(4245);
const { WILDCARD } = __nccwpck_require__(4438);
const { WILDCARD, WILDCARD_UPPERCASE} = __nccwpck_require__(4438);
const { normalizeOutputKey } = __nccwpck_require__(1608);
const core = __nccwpck_require__(2186);

Expand All @@ -18966,6 +18984,7 @@ const core = __nccwpck_require__(2186);
async function getSecrets(secretRequests, client, ignoreNotFound) {
const responseCache = new Map();
let results = [];
let upperCaseEnv = false;

for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest;
Expand Down Expand Up @@ -18999,7 +19018,8 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {

body = JSON.parse(body);

if (selector == WILDCARD) {
if (selector === WILDCARD || selector === WILDCARD_UPPERCASE) {
upperCaseEnv = selector === WILDCARD_UPPERCASE;
let keys = body.data;
if (body.data["data"] != undefined) {
keys = keys.data;
Expand All @@ -19018,7 +19038,7 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
}

newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName, upperCaseEnv);

// JSONata field references containing reserved tokens should
// be enclosed in backticks
Expand Down Expand Up @@ -19127,12 +19147,12 @@ module.exports = {
* @param {string} dataKey
* @param {boolean=} isEnvVar
*/
function normalizeOutputKey(dataKey, isEnvVar = false) {
function normalizeOutputKey(dataKey, upperCase = false) {
let outputKey = dataKey
.replace(".", "__")
.replace(new RegExp("-", "g"), "")
.replace(/[^\p{L}\p{N}_-]/gu, "");
if (isEnvVar) {
if (upperCase) {
outputKey = outputKey.toUpperCase();
}
return outputKey;
Expand Down
28 changes: 27 additions & 1 deletion integrationTests/basic/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
});

it('wildcard supports cubbyhole', async () => {
it('wildcard supports cubbyhole with uppercase transform', async () => {
mockInput('/cubbyhole/test *');

await exportSecrets();
Expand All @@ -323,6 +323,32 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
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 () => {
mockInput(`
Expand Down
4 changes: 2 additions & 2 deletions src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const command = require('@actions/core/lib/command');
const got = require('got').default;
const jsonata = require('jsonata');
const { normalizeOutputKey } = require('./utils');
const { WILDCARD } = require('./constants');
const { WILDCARD, WILDCARD_UPPERCASE } = require('./constants');

const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');

Expand Down Expand Up @@ -174,7 +174,7 @@ function parseSecretsInput(secretsInput) {
const selectorAst = jsonata(selectorQuoted).ast();
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}"`);
}

Expand Down
8 changes: 5 additions & 3 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const WILDCARD = '*';
const WILDCARD_UPPERCASE = '*';
const WILDCARD = '**';

module.exports = {
WILDCARD
};
WILDCARD,
WILDCARD_UPPERCASE,
};
8 changes: 5 additions & 3 deletions src/secrets.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const jsonata = require("jsonata");
const { WILDCARD } = require("./constants");
const { WILDCARD, WILDCARD_UPPERCASE} = require("./constants");
const { normalizeOutputKey } = require("./utils");
const core = require('@actions/core');

Expand All @@ -26,6 +26,7 @@ const core = require('@actions/core');
async function getSecrets(secretRequests, client, ignoreNotFound) {
const responseCache = new Map();
let results = [];
let upperCaseEnv = false;

for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest;
Expand Down Expand Up @@ -59,7 +60,8 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {

body = JSON.parse(body);

if (selector == WILDCARD) {
if (selector === WILDCARD || selector === WILDCARD_UPPERCASE) {
upperCaseEnv = selector === WILDCARD_UPPERCASE;
let keys = body.data;
if (body.data["data"] != undefined) {
keys = keys.data;
Expand All @@ -78,7 +80,7 @@ async function getSecrets(secretRequests, client, ignoreNotFound) {
}

newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName, upperCaseEnv);

// JSONata field references containing reserved tokens should
// be enclosed in backticks
Expand Down
4 changes: 2 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* @param {string} dataKey
* @param {boolean=} isEnvVar
*/
function normalizeOutputKey(dataKey, isEnvVar = false) {
function normalizeOutputKey(dataKey, upperCase = false) {
let outputKey = dataKey
.replace(".", "__")
.replace(new RegExp("-", "g"), "")
.replace(/[^\p{L}\p{N}_-]/gu, "");
if (isEnvVar) {
if (upperCase) {
outputKey = outputKey.toUpperCase();
}
return outputKey;
Expand Down