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

fix secrets stored in JSON format

This commit is contained in:
JM Faircloth 2023-07-03 15:57:28 -05:00
parent e926631bb2
commit a24b038252
5 changed files with 185 additions and 4 deletions

View file

@ -3,6 +3,8 @@ const got = require('got');
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.VAULT_TOKEN}` : "testtoken";
const jsonStringMultiline = '{"x": 1, "y": "q\\nux"}';
(async () => {
try {
// Verify Connection
@ -36,6 +38,44 @@ const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.V
}
});
await got(`http://${vaultUrl}/v1/secret/data/test-json-string`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
// this is stored in Vault as a string
jsonString: '{"x":1,"y":"qux"}',
},
},
});
await got(`http://${vaultUrl}/v1/secret/data/test-json-data`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
// this is stored in Vault as a map
jsonData: {"x":1,"y":"qux"},
},
},
});
await got(`http://${vaultUrl}/v1/secret/data/test-json-string-multiline`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
jsonStringMultiline,
},
},
});
await got(`http://${vaultUrl}/v1/sys/mounts/my-secret`, {
method: 'POST',
headers: {

50
scripts/parse.js Normal file
View file

@ -0,0 +1,50 @@
// const core = require('@actions/core');
try {
let inputs = [
process.env.JSONSTRING,
process.env.JSONSTRINGMULTILINE,
process.env.JSONDATA,
process.env.SINGLELINE,
process.env.MULTILINE,
];
let names = [
"test-json-string",
"test-json-string-multiline",
"test-json-data",
"singleline",
"multiline",
];
let i = 0;
inputs.forEach(input => {
console.log(`processing: ${names[i]}`)
i++;
input = (input || '').trim();
if (!input) {
throw new Error(`Missing service account key JSON (got empty value)`);
}
// If the string doesn't start with a JSON object character, it is probably
// base64-encoded.
if (!input.startsWith('{')) {
let str = input.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
input = Buffer.from(str, 'base64').toString('utf8');
}
try {
const creds = JSON.parse(input);
console.log('success!')
return creds;
} catch (err) {
console.log('error parsing')
console.log(err)
}
})
} catch (error) {
console.log(error)
}

View file

@ -220,6 +220,38 @@ describe('exportSecrets', () => {
expect(core.setOutput).toBeCalledWith('key', '1');
});
it('json secret retrieval', async () => {
const jsonString = '{"x":1,"y":2}';
mockInput('test key');
mockVaultData({
key: jsonString,
});
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
expect(core.setOutput).toBeCalledWith('key', jsonString);
});
it('multi-line json secret retrieval', async () => {
const jsonString = `
{
"x":1,
"y":"bar"
}
`;
mockInput('test key');
mockVaultData({
key: jsonString,
});
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
expect(core.setOutput).toBeCalledWith('key', jsonString);
});
it('intl secret retrieval', async () => {
mockInput('测试 测试');
mockVaultData({

View file

@ -66,4 +66,4 @@ describe('exportSecrets retries', () => {
done();
});
});
});
});

View file

@ -72,7 +72,21 @@ async function getSecrets(secretRequests, client) {
*/
async function selectData(data, selector) {
const ata = jsonata(selector);
let result = JSON.stringify(await ata.evaluate(data));
let d = await ata.evaluate(data);
// If we have a Javascript Object, then this data was stored in Vault as
// pure JSON (not a JSON string)
const storedAsJSONData = isObject(d);
if (isJSONString(d)) {
// If we already have a JSON string we will not "stringify" it yet so
// that we don't end up calling JSON.parse. This would break the
// secrets that are stored as pure JSON. See: https://github.com/hashicorp/vault-action/issues/194
result = d;
} else {
result = JSON.stringify(d);
}
// Compat for custom engines
if (!result && ((ata.ast().type === "path" && ata.ast()['steps'].length === 1) || ata.ast().type === "string") && selector !== 'data' && 'data' in data) {
result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
@ -81,12 +95,57 @@ async function selectData(data, selector) {
}
if (result.startsWith(`"`)) {
result = JSON.parse(result);
// we need to strip the beginning and ending quotes otherwise it will
// always successfully parse as a JSON string
result = result.substring(1, result.length - 1);
if (!isJSONString(result)) {
// add the quotes back so we can parse it into a Javascript object
// to allow support for multi-line secrets. See https://github.com/hashicorp/vault-action/issues/160
result = `"${result}"`
result = JSON.parse(result);
}
} else if (isJSONString(result)) {
if (storedAsJSONData) {
// Support secrets stored in Vault as pure JSON.
// See https://github.com/hashicorp/vault-action/issues/194 and https://github.com/hashicorp/vault-action/pull/173
result = JSON.stringify(result);
result = result.substring(1, result.length - 1);
} else {
// Support secrets stored in Vault as JSON Strings
result = JSON.stringify(result);
result = JSON.parse(result);
}
}
return result;
}
/**
* isOjbect returns true if target is a Javascript object
* @param {Type} target
*/
function isObject(target) {
return typeof target === 'object' && target !== null;
}
/**
* isJSONString returns true if target parses as a valid JSON string
* @param {Type} target
*/
function isJSONString(target) {
if (typeof target !== "string"){
return false;
}
try {
let o = JSON.parse(target);
} catch (e) {
return false;
}
return true;
}
module.exports = {
getSecrets,
selectData
}
}