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

Merged
merged 3 commits into from
Mar 3, 2025
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Improvements:
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,15 @@ 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 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

When accessing secrets from the KV secrets engine version 2, Vault Action
Expand Down
23 changes: 14 additions & 9 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18535,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 }, pki: { getCertificates } } = __nccwpck_require__(4351);

Expand Down Expand Up @@ -18752,7 +18752,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 @@ -19005,12 +19005,15 @@ module.exports = {
/***/ 4438:
/***/ ((module) => {

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

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


/***/ }),

/***/ 4351:
Expand Down Expand Up @@ -19114,7 +19117,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 @@ -19141,6 +19144,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 @@ -19174,7 +19178,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 @@ -19193,7 +19198,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 @@ -19302,12 +19307,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 @@ -395,7 +395,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 @@ -405,6 +405,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 }, pki: { getCertificates } } = require('./index');

Expand Down Expand Up @@ -221,7 +221,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