mirror of
https://github.com/hashicorp/vault-action.git
synced 2025-11-14 18:13:45 +00:00
Simplify secret UX
This commit is contained in:
parent
5de67274a5
commit
622329f2ef
6 changed files with 83 additions and 116 deletions
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
|
|
@ -141,21 +141,19 @@ jobs:
|
||||||
url: http://localhost:8200
|
url: http://localhost:8200
|
||||||
token: testtoken
|
token: testtoken
|
||||||
secrets: |
|
secrets: |
|
||||||
test secret ;
|
secret/data/test secret ;
|
||||||
test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
nested/test otherSecret ;
|
secret/data/nested/test otherSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V1)
|
- name: Test Vault Action (default KV V1)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: http://localhost:8200
|
url: http://localhost:8200
|
||||||
token: testtoken
|
token: testtoken
|
||||||
path: my-secret
|
|
||||||
kv-version: 1
|
|
||||||
secrets: |
|
secrets: |
|
||||||
test altSecret ;
|
secret/data/test altSecret ;
|
||||||
test altSecret | NAMED_ALTSECRET ;
|
secret/data/test altSecret | NAMED_ALTSECRET ;
|
||||||
nested/test otherAltSecret ;
|
secret/data/nested/test otherAltSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (cubbyhole)
|
- name: Test Vault Action (cubbyhole)
|
||||||
uses: ./
|
uses: ./
|
||||||
|
|
@ -217,9 +215,9 @@ jobs:
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
test secret ;
|
secret/data/test secret ;
|
||||||
test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
nested/test otherSecret ;
|
secret/data/nested/test otherSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (tlsSkipVerify)
|
- name: Test Vault Action (tlsSkipVerify)
|
||||||
uses: ./
|
uses: ./
|
||||||
|
|
@ -230,22 +228,20 @@ jobs:
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
tlsSkipVerify skip ;
|
secret/data/tlsSkipVerify skip ;
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V1)
|
- name: Test Vault Action (default KV V1)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: https://localhost:8200
|
url: https://localhost:8200
|
||||||
token: ${{ env.VAULT_TOKEN }}
|
token: ${{ env.VAULT_TOKEN }}
|
||||||
path: my-secret
|
|
||||||
kv-version: 1
|
|
||||||
caCertificate: ${{ secrets.VAULTCA }}
|
caCertificate: ${{ secrets.VAULTCA }}
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
test altSecret ;
|
my-secret/test altSecret ;
|
||||||
test altSecret | NAMED_ALTSECRET ;
|
my-secret/test altSecret | NAMED_ALTSECRET ;
|
||||||
nested/test otherAltSecret ;
|
my-secret/nested/test otherAltSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (cubbyhole)
|
- name: Test Vault Action (cubbyhole)
|
||||||
uses: ./
|
uses: ./
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,6 @@ inputs:
|
||||||
namespace:
|
namespace:
|
||||||
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
|
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
|
||||||
required: false
|
required: false
|
||||||
path:
|
|
||||||
description: 'The path of a non-default K/V engine'
|
|
||||||
required: false
|
|
||||||
kv-version:
|
|
||||||
description: 'The version of the K/V engine to use.'
|
|
||||||
default: '2'
|
|
||||||
required: false
|
|
||||||
method:
|
method:
|
||||||
description: 'The method to use to authenticate with Vault.'
|
description: 'The method to use to authenticate with Vault.'
|
||||||
default: 'token'
|
default: 'token'
|
||||||
|
|
|
||||||
42
dist/index.js
vendored
42
dist/index.js
vendored
|
|
@ -10552,9 +10552,9 @@ async function getSecrets(secretRequests, client) {
|
||||||
const responseCache = new Map();
|
const responseCache = new Map();
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const secretRequest of secretRequests) {
|
for (const secretRequest of secretRequests) {
|
||||||
const { path, selector } = secretRequest;
|
let { path, selector } = secretRequest;
|
||||||
|
|
||||||
const requestPath = `v1${path}`;
|
const requestPath = `v1/${path}`;
|
||||||
let body;
|
let body;
|
||||||
let cachedResponse = false;
|
let cachedResponse = false;
|
||||||
if (responseCache.has(requestPath)) {
|
if (responseCache.has(requestPath)) {
|
||||||
|
|
@ -10566,7 +10566,13 @@ async function getSecrets(secretRequests, client) {
|
||||||
responseCache.set(requestPath, body);
|
responseCache.set(requestPath, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = selectData(JSON.parse(body), selector);
|
selector = "data." + selector
|
||||||
|
body = JSON.parse(body)
|
||||||
|
if (body.data["data"] != undefined) {
|
||||||
|
selector = "data." + selector
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = selectData(body, selector);
|
||||||
results.push({
|
results.push({
|
||||||
request: secretRequest,
|
request: secretRequest,
|
||||||
value,
|
value,
|
||||||
|
|
@ -14099,7 +14105,6 @@ const jsonata = __webpack_require__(350);
|
||||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = __webpack_require__(676);
|
const { auth: { retrieveToken }, secrets: { getSecrets } } = __webpack_require__(676);
|
||||||
|
|
||||||
const AUTH_METHODS = ['approle', 'token', 'github'];
|
const AUTH_METHODS = ['approle', 'token', 'github'];
|
||||||
const VALID_KV_VERSION = [-1, 1, 2];
|
|
||||||
|
|
||||||
async function exportSecrets() {
|
async function exportSecrets() {
|
||||||
const vaultUrl = core.getInput('url', { required: true });
|
const vaultUrl = core.getInput('url', { required: true });
|
||||||
|
|
@ -14107,10 +14112,6 @@ async function exportSecrets() {
|
||||||
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
|
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
|
||||||
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
|
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
|
||||||
|
|
||||||
let enginePath = core.getInput('path', { required: false });
|
|
||||||
/** @type {number | string} */
|
|
||||||
let kvVersion = core.getInput('kv-version', { required: false });
|
|
||||||
|
|
||||||
const secretsInput = core.getInput('secrets', { required: true });
|
const secretsInput = core.getInput('secrets', { required: true });
|
||||||
const secretRequests = parseSecretsInput(secretsInput);
|
const secretRequests = parseSecretsInput(secretsInput);
|
||||||
|
|
||||||
|
|
@ -14158,32 +14159,9 @@ async function exportSecrets() {
|
||||||
defaultOptions.headers['X-Vault-Token'] = vaultToken;
|
defaultOptions.headers['X-Vault-Token'] = vaultToken;
|
||||||
const client = got.extend(defaultOptions);
|
const client = got.extend(defaultOptions);
|
||||||
|
|
||||||
if (!enginePath) {
|
|
||||||
enginePath = 'secret';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kvVersion) {
|
|
||||||
kvVersion = 2;
|
|
||||||
}
|
|
||||||
kvVersion = +kvVersion;
|
|
||||||
|
|
||||||
if (Number.isNaN(kvVersion) || !VALID_KV_VERSION.includes(kvVersion)) {
|
|
||||||
throw Error(`You must provide a valid K/V version (${VALID_KV_VERSION.slice(1).join(', ')}). Input: "${kvVersion}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requests = secretRequests.map(request => {
|
const requests = secretRequests.map(request => {
|
||||||
const { path, selector } = request;
|
const { path, selector } = request;
|
||||||
|
return request;
|
||||||
if (path.startsWith('/')) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
const kvPath = (kvVersion === 2)
|
|
||||||
? `/${enginePath}/data/${path}`
|
|
||||||
: `/${enginePath}/${path}`;
|
|
||||||
const kvSelector = (kvVersion === 2)
|
|
||||||
? `data.data.${selector}`
|
|
||||||
: `data.${selector}`;
|
|
||||||
return { ...request, path: kvPath, selector: kvSelector };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await getSecrets(requests, client);
|
const results = await getSecrets(requests, client);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const { when } = require('jest-when');
|
||||||
|
|
||||||
const { exportSecrets } = require('../../src/action');
|
const { exportSecrets } = require('../../src/action');
|
||||||
|
|
||||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
|
const vaultUrl = `http://${process.env.VAULT_HOST || '0.0.0.0'}:${process.env.VAULT_PORT || '8200'}`;
|
||||||
|
|
||||||
describe('integration', () => {
|
describe('integration', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
@ -42,9 +42,21 @@ describe('integration', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await got(`${vaultUrl}/v1/secret/data/foobar`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': 'testtoken',
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
fookv2: 'bar',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Enable custom secret engine
|
// Enable custom secret engine
|
||||||
try {
|
try {
|
||||||
await got(`${vaultUrl}/v1/sys/mounts/my-secret`, {
|
await got(`${vaultUrl}/v1/sys/mounts/secret-kv1`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Vault-Token': 'testtoken',
|
'X-Vault-Token': 'testtoken',
|
||||||
|
|
@ -62,7 +74,7 @@ describe('integration', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/my-secret/test`, {
|
await got(`${vaultUrl}/v1/secret-kv1/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Vault-Token': 'testtoken',
|
'X-Vault-Token': 'testtoken',
|
||||||
|
|
@ -72,7 +84,17 @@ describe('integration', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/my-secret/nested/test`, {
|
await got(`${vaultUrl}/v1/secret-kv1/foobar`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': 'testtoken',
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
fookv1: 'bar',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await got(`${vaultUrl}/v1/secret-kv1/nested/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Vault-Token': 'testtoken',
|
'X-Vault-Token': 'testtoken',
|
||||||
|
|
@ -101,20 +123,8 @@ describe('integration', () => {
|
||||||
.mockReturnValueOnce(secrets);
|
.mockReturnValueOnce(secrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockEngineName(name) {
|
|
||||||
when(core.getInput)
|
|
||||||
.calledWith('path')
|
|
||||||
.mockReturnValueOnce(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockVersion(version) {
|
|
||||||
when(core.getInput)
|
|
||||||
.calledWith('kv-version')
|
|
||||||
.mockReturnValueOnce(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('get simple secret', async () => {
|
it('get simple secret', async () => {
|
||||||
mockInput('test secret');
|
mockInput('secret/data/test secret');
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
|
|
@ -122,7 +132,7 @@ describe('integration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('re-map secret', async () => {
|
it('re-map secret', async () => {
|
||||||
mockInput('test secret | TEST_KEY');
|
mockInput('secret/data/test secret | TEST_KEY');
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
|
|
@ -130,7 +140,7 @@ describe('integration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get nested secret', async () => {
|
it('get nested secret', async () => {
|
||||||
mockInput('nested/test otherSecret');
|
mockInput('secret/data/nested/test otherSecret');
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
|
|
@ -139,9 +149,9 @@ describe('integration', () => {
|
||||||
|
|
||||||
it('get multiple secrets', async () => {
|
it('get multiple secrets', async () => {
|
||||||
mockInput(`
|
mockInput(`
|
||||||
test secret ;
|
secret/data/test secret ;
|
||||||
test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
nested/test otherSecret ;`);
|
secret/data/nested/test otherSecret ;`);
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
|
|
@ -152,10 +162,16 @@ describe('integration', () => {
|
||||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERSUPERSECRET');
|
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERSUPERSECRET');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('leading slash kvv2', async () => {
|
||||||
|
mockInput('/secret/data/foobar fookv2');
|
||||||
|
|
||||||
|
await exportSecrets();
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledWith('FOOKV2', 'bar');
|
||||||
|
});
|
||||||
|
|
||||||
it('get secret from K/V v1', async () => {
|
it('get secret from K/V v1', async () => {
|
||||||
mockInput('test secret');
|
mockInput('secret-kv1/test secret');
|
||||||
mockEngineName('my-secret');
|
|
||||||
mockVersion('1');
|
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
|
|
@ -163,15 +179,21 @@ describe('integration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get nested secret from K/V v1', async () => {
|
it('get nested secret from K/V v1', async () => {
|
||||||
mockInput('nested/test otherSecret');
|
mockInput('secret-kv1/nested/test otherSecret');
|
||||||
mockEngineName('my-secret');
|
|
||||||
mockVersion('1');
|
|
||||||
|
|
||||||
await exportSecrets();
|
await exportSecrets();
|
||||||
|
|
||||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERCUSTOMSECRET');
|
expect(core.exportVariable).toBeCalledWith('OTHERSECRET', 'OTHERCUSTOMSECRET');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('leading slash kvv1', async () => {
|
||||||
|
mockInput('/secret-kv1/foobar fookv1');
|
||||||
|
|
||||||
|
await exportSecrets();
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledWith('FOOKV1', 'bar');
|
||||||
|
});
|
||||||
|
|
||||||
describe('generic engines', () => {
|
describe('generic engines', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await got(`${vaultUrl}/v1/cubbyhole/test`, {
|
await got(`${vaultUrl}/v1/cubbyhole/test`, {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ const jsonata = require('jsonata');
|
||||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
|
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
|
||||||
|
|
||||||
const AUTH_METHODS = ['approle', 'token', 'github'];
|
const AUTH_METHODS = ['approle', 'token', 'github'];
|
||||||
const VALID_KV_VERSION = [-1, 1, 2];
|
|
||||||
|
|
||||||
async function exportSecrets() {
|
async function exportSecrets() {
|
||||||
const vaultUrl = core.getInput('url', { required: true });
|
const vaultUrl = core.getInput('url', { required: true });
|
||||||
|
|
@ -14,10 +13,6 @@ async function exportSecrets() {
|
||||||
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
|
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
|
||||||
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
|
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
|
||||||
|
|
||||||
let enginePath = core.getInput('path', { required: false });
|
|
||||||
/** @type {number | string} */
|
|
||||||
let kvVersion = core.getInput('kv-version', { required: false });
|
|
||||||
|
|
||||||
const secretsInput = core.getInput('secrets', { required: true });
|
const secretsInput = core.getInput('secrets', { required: true });
|
||||||
const secretRequests = parseSecretsInput(secretsInput);
|
const secretRequests = parseSecretsInput(secretsInput);
|
||||||
|
|
||||||
|
|
@ -65,32 +60,9 @@ async function exportSecrets() {
|
||||||
defaultOptions.headers['X-Vault-Token'] = vaultToken;
|
defaultOptions.headers['X-Vault-Token'] = vaultToken;
|
||||||
const client = got.extend(defaultOptions);
|
const client = got.extend(defaultOptions);
|
||||||
|
|
||||||
if (!enginePath) {
|
|
||||||
enginePath = 'secret';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kvVersion) {
|
|
||||||
kvVersion = 2;
|
|
||||||
}
|
|
||||||
kvVersion = +kvVersion;
|
|
||||||
|
|
||||||
if (Number.isNaN(kvVersion) || !VALID_KV_VERSION.includes(kvVersion)) {
|
|
||||||
throw Error(`You must provide a valid K/V version (${VALID_KV_VERSION.slice(1).join(', ')}). Input: "${kvVersion}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requests = secretRequests.map(request => {
|
const requests = secretRequests.map(request => {
|
||||||
const { path, selector } = request;
|
const { path, selector } = request;
|
||||||
|
return request;
|
||||||
if (path.startsWith('/')) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
const kvPath = (kvVersion === 2)
|
|
||||||
? `/${enginePath}/data/${path}`
|
|
||||||
: `/${enginePath}/${path}`;
|
|
||||||
const kvSelector = (kvVersion === 2)
|
|
||||||
? `data.data.${selector}`
|
|
||||||
: `data.${selector}`;
|
|
||||||
return { ...request, path: kvPath, selector: kvSelector };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await getSecrets(requests, client);
|
const results = await getSecrets(requests, client);
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ async function getSecrets(secretRequests, client) {
|
||||||
const responseCache = new Map();
|
const responseCache = new Map();
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const secretRequest of secretRequests) {
|
for (const secretRequest of secretRequests) {
|
||||||
const { path, selector } = secretRequest;
|
let { path, selector } = secretRequest;
|
||||||
|
|
||||||
const requestPath = `v1${path}`;
|
const requestPath = `v1/${path}`;
|
||||||
let body;
|
let body;
|
||||||
let cachedResponse = false;
|
let cachedResponse = false;
|
||||||
if (responseCache.has(requestPath)) {
|
if (responseCache.has(requestPath)) {
|
||||||
|
|
@ -39,7 +39,13 @@ async function getSecrets(secretRequests, client) {
|
||||||
responseCache.set(requestPath, body);
|
responseCache.set(requestPath, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = selectData(JSON.parse(body), selector);
|
selector = "data." + selector
|
||||||
|
body = JSON.parse(body)
|
||||||
|
if (body.data["data"] != undefined) {
|
||||||
|
selector = "data." + selector
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = selectData(body, selector);
|
||||||
results.push({
|
results.push({
|
||||||
request: secretRequest,
|
request: secretRequest,
|
||||||
value,
|
value,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue