Compare commits

...

11 Commits

Author SHA1 Message Date
Richard Simpson 50ece41861 chore: I really should PR changes 2020-03-31 12:20:05 -05:00
Richard Simpson ed8303ca53 fix: fix got not being use correctly and path prefix 2020-03-31 12:15:30 -05:00
Richard Simpson 0e719ef42a chore: compile release 2020-03-31 12:01:15 -05:00
Richard Simpson 795c9eddca feat: add github auth and cleanup docs a little
Adds the GitHub Authentication method
2020-03-31 11:56:54 -05:00
dependabot-preview[bot] 9e8c0bad27 chore(deps): bump got from 10.6.0 to 10.7.0 (#30)
Bumps [got](https://github.com/sindresorhus/got) from 10.6.0 to 10.7.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v10.6.0...v10.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-26 14:47:12 -05:00
dependabot-preview[bot] 751a3d19ad chore(deps-dev): bump jest from 25.1.0 to 25.2.1 (#31)
Bumps [jest](https://github.com/facebook/jest) from 25.1.0 to 25.2.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v25.1.0...v25.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-26 14:47:05 -05:00
dependabot-preview[bot] cc7ceeef06 chore(deps-dev): bump @zeit/ncc from 0.21.1 to 0.22.0 (#29)
Bumps [@zeit/ncc](https://github.com/zeit/ncc) from 0.21.1 to 0.22.0.
- [Release notes](https://github.com/zeit/ncc/releases)
- [Commits](https://github.com/zeit/ncc/compare/0.21.1...0.22.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-24 10:52:12 -05:00
dependabot-preview[bot] fa23030c10 chore(deps): [security] bump acorn from 6.4.0 to 6.4.1 (#28)
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-13 20:07:36 -05:00
Richard Simpson 4561f9e26e docs: fix typo and clarify masking 2020-03-11 14:16:21 -05:00
Richard Simpson cb2908ac94 docs: promote non-key syntax sections 2020-03-11 14:12:53 -05:00
Richard Simpson 198a7ed7d2 docs: add headers docs and toc 2020-03-11 14:11:13 -05:00
9 changed files with 1280 additions and 1130 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"markdown-toc.depthFrom": 2
}
+49 -6
View File
@@ -4,6 +4,24 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
By default, this action pulls from [Version 2](https://www.vaultproject.io/docs/secrets/kv/kv-v2/) of the K/V Engine. See examples below for how to [use v1](#using-kv-version-1) as well as [other non-K/V engines](#other-secret-engines).
<!-- TOC -->
- [Example Usage](#example-usage)
- [Authentication method](#authentication-method)
- [Key Syntax](#key-syntax)
- [Simple Key](#simple-key)
- [Set Output Variable Name](#set-output-variable-name)
- [Multiple Secrets](#multiple-secrets)
- [Using K/V version 1](#using-kv-version-1)
- [Custom K/V Engine Path](#custom-kv-engine-path)
- [Other Secret Engines](#other-secret-engines)
- [Adding Extra Headers](#adding-extra-headers)
- [Vault Enterprise Features](#vault-enterprise-features)
- [Namespace](#namespace)
- [Masking - Hidding Secrets from Logs](#masking---hidding-secrets-from-logs)
<!-- /TOC -->
## Example Usage
```yaml
@@ -28,14 +46,14 @@ jobs:
While most workflows will likely use a vault token, you can also use an `approle` to authenticate with vaule. You can configure which by using the `method` parameter:
- **token**: (by default) you must provide a token parameter
- **token**: (by default) you must provide a `token` parameter
```yaml
...
with:
url: https://vault.mycompany.com:8200
token: ${{ secrets.VaultToken }}
```
- **approle**: you must provide a roleId & secretId parameter
- **approle**: you must provide a `roleId` & `secretId` parameter
```yaml
...
with:
@@ -44,6 +62,14 @@ with:
roleId: ${{ secrets.roleId }}
secretId: ${{ secrets.secretId }}
```
- **github**: you must provide the github token as `githubToken`
```yaml
...
with:
url: https://vault.mycompany.com:8200
method: github
githubToken: ${{ secrets.GITHUB_TOKEN }}
```
## Key Syntax
@@ -131,7 +157,7 @@ with:
kv-version: 1
```
### Custom K/V Engine Path
## Custom K/V Engine Path
When you enable the K/V Engine, by default it's placed at the path `secret`, so a secret named `ci` will be accessed from `secret/ci`. However, [if you enabled the secrets engine using a custom `path`](https://www.vaultproject.io/docs/commands/secrets/enable/#inlinecode--path-4), you
can pass it as follows:
@@ -144,7 +170,7 @@ with:
This way, the `ci` secret in the example above will be retrieved from `my-secrets/ci`.
### Other Secret Engines
## Other Secret Engines
While this action primarily supports the K/V engine, it is possible to request secrets from other engines in Vault.
@@ -202,6 +228,22 @@ would work fine.
NOTE: The `Secret Key` is pulled from the `data` property of the response.
## Adding Extra Headers
If you ever need to add extra headers to the vault request, say if you need to authenticate with a firewall, all you need to do is set `extraHeaders`:
```yaml
with:
secrets: |
ci/aws accessKey | AWS_ACCESS_KEY_ID ;
ci/aws secretKey | AWS_SECRET_ACCESS_KEY
extraHeaders: |
X-Secure-Id: ${{ secrets.SECURE_ID }}
X-Secure-Secret: ${{ secrets.SECURE_SECRET }}
```
This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to vault.
## Vault Enterprise Features
### Namespace
@@ -224,6 +266,7 @@ steps:
ci npm_token
```
## Masking
## Masking - Hidding Secrets from Logs
This action uses Github Action's built in masking, so all variables will automatically be masked if printed to the console or to logs.
This action uses GitHub Action's built-in masking, so all variables will automatically be masked (aka hidden) if printed to the console or to logs.
**This only obscures secrets from output logs.** If someone has the ability to edit your workflows, then they are able to read and therefore write secrets to somewhere else just like normal GitHub Secrets.
+72 -34
View File
@@ -6,13 +6,14 @@ const core = require('@actions/core');
const command = require('@actions/core/lib/command');
const got = require('got');
const AUTH_METHODS = ['approle', 'token'];
const AUTH_METHODS = ['approle', 'token', 'github'];
const VALID_KV_VERSION = [-1, 1, 2];
async function exportSecrets() {
const vaultUrl = core.getInput('url', { required: true });
const vaultNamespace = core.getInput('namespace', { required: false });
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
let enginePath = core.getInput('path', { required: false });
let kvVersion = core.getInput('kv-version', { required: false });
@@ -20,41 +21,27 @@ async function exportSecrets() {
const secretsInput = core.getInput('secrets', { required: true });
const secretRequests = parseSecretsInput(secretsInput);
const vaultMethod = core.getInput('method', { required: false }) || 'token';
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
if (!AUTH_METHODS.includes(vaultMethod)) {
throw Error(`Sorry, the authentication method ${vaultMethod} is not currently supported.`);
}
let vaultToken = null;
switch (vaultMethod) {
case 'approle':
const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true });
core.debug('Try to retrieve Vault Token from approle');
var options = {
headers: {},
json: { role_id: vaultRoleId, secret_id: vaultSecretId },
responseType: 'json'
};
if (vaultNamespace != null) {
options.headers["X-Vault-Namespace"] = vaultNamespace;
}
/** @type {any} */
const result = await got.post(`${vaultUrl}/v1/auth/approle/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
vaultToken = result.body.auth.client_token;
core.debug('✔ Vault Token has retrieved from approle');
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
break;
default:
vaultToken = core.getInput('token', { required: true });
break;
const defaultOptions = {
prefixUrl: vaultUrl,
headers: {}
}
for (const [headerName, headerValue] of extraHeaders) {
defaultOptions.headers[headerName] = headerValue;
}
if (vaultNamespace != null) {
defaultOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
const client = got.extend(defaultOptions);
const vaultToken = await retrieveToken(vaultMethod, /** @type {any} */ (client));
if (!enginePath) {
enginePath = 'secret';
}
@@ -85,7 +72,7 @@ async function exportSecrets() {
requestOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
let requestPath = `${vaultUrl}/v1`;
let requestPath = `v1`;
const kvRequest = !secretPath.startsWith('/')
if (!kvRequest) {
requestPath += secretPath;
@@ -100,7 +87,7 @@ async function exportSecrets() {
body = responseCache.get(requestPath);
core.debug(' using cached response');
} else {
const result = await got(requestPath, requestOptions);
const result = await client.get(requestPath, requestOptions);
body = result.body;
responseCache.set(requestPath, body);
}
@@ -110,9 +97,11 @@ async function exportSecrets() {
const secretData = getResponseData(body, dataDepth);
const value = selectData(secretData, secretSelector, isJSONPath);
command.issue('add-mask', value);
core.exportVariable(envVarName, `${value}`);
if (exportEnv) {
core.exportVariable(envVarName, `${value}`);
}
core.setOutput(outputVarName, `${value}`);
core.debug(`${secretPath} => outputs.${outputVarName} | env.${envVarName}`);
core.debug(`${secretPath} => outputs.${outputVarName}${exportEnv ? ` | env.${envVarName}` : ''}`);
}
};
@@ -185,6 +174,55 @@ function parseSecretsInput(secretsInput) {
return output;
}
/***
* Authentication with Vault and retrieve a vault token
* @param {string} method
* @param {import('got')} client
*/
async function retrieveToken(method, client) {
switch (method) {
case 'approle': {
const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true });
core.debug('Try to retrieve Vault Token from approle');
/** @type {any} */
var options = {
json: { role_id: vaultRoleId, secret_id: vaultSecretId },
responseType: 'json'
};
const result = await client.post(`v1/auth/approle/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
core.debug('✔ Vault Token has retrieved from approle');
return result.body.auth.client_token;
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
}
case 'github': {
const githubToken = core.getInput('githubToken', { required: true });
core.debug('Try to retrieve Vault Token from approle');
/** @type {any} */
var options = {
json: { token: githubToken },
responseType: 'json'
};
const result = await client.post(`v1/auth/github/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
core.debug('✔ Vault Token has retrieved from approle');
return result.body.auth.client_token;
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
}
default:
return core.getInput('token', { required: true });
}
}
/**
* Parses a JSON response and returns the secret data
* @param {string} responseBody
+6 -10
View File
@@ -198,19 +198,15 @@ describe('exportSecrets', () => {
function mockVaultData(data, version='2') {
switch(version) {
case '1':
got.mockResolvedValue({
body: JSON.stringify({
data
})
got.extend.mockReturnValue({
get: async () => ({ body: JSON.stringify({ data }) })
});
break;
case '2':
got.mockResolvedValue({
body: JSON.stringify({
data: {
data
}
})
got.extend.mockReturnValue({
get: async () => ({ body: JSON.stringify({ data: {
data
} }) })
});
break;
}
+25 -4
View File
@@ -4,17 +4,38 @@ inputs:
url:
description: 'The URL for the vault endpoint'
required: true
token:
description: 'The Vault Token to be used to authenticate with Vault'
required: true
secrets:
description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details'
required: true
namespace:
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
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:
description: 'The method to use to authenticate with Vault. Default: token'
required: false
token:
description: 'The Vault Token to be used to authenticate with Vault'
required: false
roleId:
description: 'The Role Id for App Role authentication'
required: false
secretId:
description: 'The Secret Id for App Role authentication'
required: false
githubToken:
description: 'The Github Token to be used to authenticate with Vault'
required: false
extraHeaders:
description: 'A string of newline seperated extra headers to include on every request.'
description: 'A string of newline separated extra headers to include on every request.'
required: false
exportEnv:
description: 'Whether or not export secrets as environment variables. Default: true'
required: false
runs:
using: 'node12'
+134 -71
View File
@@ -653,6 +653,9 @@ const defaults = {
context: {},
_pagination: {
transform: (response) => {
if (response.request.options.responseType === 'json') {
return response.body;
}
return JSON.parse(response.body);
},
paginate: response => {
@@ -1573,7 +1576,7 @@ module.exports.iterator = (emitter, event, options) => {
"use strict";
// rfc7231 6.1
const statusCodeCacheableByDefault = [
const statusCodeCacheableByDefault = new Set([
200,
203,
204,
@@ -1585,10 +1588,10 @@ const statusCodeCacheableByDefault = [
410,
414,
501,
];
]);
// This implementation does not understand partial responses (206)
const understoodStatuses = [
const understoodStatuses = new Set([
200,
203,
204,
@@ -1603,7 +1606,14 @@ const understoodStatuses = [
410,
414,
501,
];
]);
const errorStatusCodes = new Set([
500,
502,
503,
504,
]);
const hopByHopHeaders = {
date: true, // included, because we add Age update Date
@@ -1616,6 +1626,7 @@ const hopByHopHeaders = {
'transfer-encoding': true,
upgrade: true,
};
const excludedFromRevalidationUpdate = {
// Since the old body is reused, it doesn't make sense to change properties of the body
'content-length': true,
@@ -1624,6 +1635,20 @@ const excludedFromRevalidationUpdate = {
'content-range': true,
};
function toNumberOrZero(s) {
const n = parseInt(s, 10);
return isFinite(n) ? n : 0;
}
// RFC 5861
function isErrorResponse(response) {
// consider undefined response as faulty
if(!response) {
return true
}
return errorStatusCodes.has(response.status);
}
function parseCacheControl(header) {
const cc = {};
if (!header) return cc;
@@ -1660,7 +1685,6 @@ module.exports = class CachePolicy {
cacheHeuristic,
immutableMinTimeToLive,
ignoreCargoCult,
trustServerDate,
_fromObject,
} = {}
) {
@@ -1676,8 +1700,6 @@ module.exports = class CachePolicy {
this._responseTime = this.now();
this._isShared = shared !== false;
this._trustServerDate =
undefined !== trustServerDate ? trustServerDate : true;
this._cacheHeuristic =
undefined !== cacheHeuristic ? cacheHeuristic : 0.1; // 10% matches IE
this._immutableMinTtl =
@@ -1738,7 +1760,7 @@ module.exports = class CachePolicy {
'HEAD' === this._method ||
('POST' === this._method && this._hasExplicitExpiration())) &&
// the response status code is understood by the cache, and
understoodStatuses.indexOf(this._status) !== -1 &&
understoodStatuses.has(this._status) &&
// the "no-store" cache directive does not appear in request or response header fields, and
!this._rescc['no-store'] &&
// the "private" response directive does not appear in the response, if the cache is shared, and
@@ -1757,7 +1779,7 @@ module.exports = class CachePolicy {
(this._isShared && this._rescc['s-maxage']) ||
this._rescc.public ||
// has a status code that is defined as cacheable by default
statusCodeCacheableByDefault.indexOf(this._status) !== -1)
statusCodeCacheableByDefault.has(this._status))
);
}
@@ -1904,24 +1926,13 @@ module.exports = class CachePolicy {
}
/**
* Value of the Date response header or current time if Date was demed invalid
* Value of the Date response header or current time if Date was invalid
* @return timestamp
*/
date() {
if (this._trustServerDate) {
return this._serverDate();
}
return this._responseTime;
}
_serverDate() {
const serverDate = Date.parse(this._resHeaders.date);
if (isFinite(serverDate)) {
const maxClockDrift = 8 * 3600 * 1000;
const clockDrift = Math.abs(this._responseTime - serverDate);
if (clockDrift < maxClockDrift) {
return serverDate;
}
return serverDate;
}
return this._responseTime;
}
@@ -1933,19 +1944,14 @@ module.exports = class CachePolicy {
* @return Number
*/
age() {
let age = Math.max(0, (this._responseTime - this.date()) / 1000);
if (this._resHeaders.age) {
let ageValue = this._ageValue();
if (ageValue > age) age = ageValue;
}
let age = this._ageValue();
const residentTime = (this.now() - this._responseTime) / 1000;
return age + residentTime;
}
_ageValue() {
const ageValue = parseInt(this._resHeaders.age);
return isFinite(ageValue) ? ageValue : 0;
return toNumberOrZero(this._resHeaders.age);
}
/**
@@ -1981,18 +1987,18 @@ module.exports = class CachePolicy {
}
// if a response includes the s-maxage directive, a shared cache recipient MUST ignore the Expires field.
if (this._rescc['s-maxage']) {
return parseInt(this._rescc['s-maxage'], 10);
return toNumberOrZero(this._rescc['s-maxage']);
}
}
// If a response includes a Cache-Control field with the max-age directive, a recipient MUST ignore the Expires field.
if (this._rescc['max-age']) {
return parseInt(this._rescc['max-age'], 10);
return toNumberOrZero(this._rescc['max-age']);
}
const defaultMinTtl = this._rescc.immutable ? this._immutableMinTtl : 0;
const serverDate = this._serverDate();
const serverDate = this.date();
if (this._resHeaders.expires) {
const expires = Date.parse(this._resHeaders.expires);
// A cache recipient MUST interpret invalid date formats, especially the value "0", as representing a time in the past (i.e., "already expired").
@@ -2016,13 +2022,24 @@ module.exports = class CachePolicy {
}
timeToLive() {
return Math.max(0, this.maxAge() - this.age()) * 1000;
const age = this.maxAge() - this.age();
const staleIfErrorAge = age + toNumberOrZero(this._rescc['stale-if-error']);
const staleWhileRevalidateAge = age + toNumberOrZero(this._rescc['stale-while-revalidate']);
return Math.max(0, age, staleIfErrorAge, staleWhileRevalidateAge) * 1000;
}
stale() {
return this.maxAge() <= this.age();
}
_useStaleIfError() {
return this.maxAge() + toNumberOrZero(this._rescc['stale-if-error']) > this.age();
}
useStaleWhileRevalidate() {
return this.maxAge() + toNumberOrZero(this._rescc['stale-while-revalidate']) > this.age();
}
static fromObject(obj) {
return new this(undefined, undefined, { _fromObject: obj });
}
@@ -2140,6 +2157,13 @@ module.exports = class CachePolicy {
*/
revalidatedPolicy(request, response) {
this._assertRequestHasHeaders(request);
if(this._useStaleIfError() && isErrorResponse(response)) { // I consider the revalidation request unsuccessful
return {
modified: false,
matches: false,
policy: this,
};
}
if (!response || !response.headers) {
throw Error('Response headers missing');
}
@@ -2217,7 +2241,6 @@ module.exports = class CachePolicy {
shared: this._isShared,
cacheHeuristic: this._cacheHeuristic,
immutableMinTimeToLive: this._immutableMinTtl,
trustServerDate: this._trustServerDate,
}),
modified: false,
matches: true,
@@ -2887,19 +2910,21 @@ const create = (defaults) => {
const result = await got(normalizedOptions);
// eslint-disable-next-line no-await-in-loop
const parsed = await pagination.transform(result);
const current = [];
for (const item of parsed) {
if (pagination.filter(item, all)) {
if (!pagination.shouldContinue(item, all)) {
if (pagination.filter(item, all, current)) {
if (!pagination.shouldContinue(item, all, current)) {
return;
}
yield item;
all.push(item);
current.push(item);
if (all.length === pagination.countLimit) {
return;
}
}
}
const optionsToMerge = pagination.paginate(result);
const optionsToMerge = pagination.paginate(result, all, current);
if (optionsToMerge === false) {
return;
}
@@ -4860,13 +4885,14 @@ const core = __webpack_require__(470);
const command = __webpack_require__(431);
const got = __webpack_require__(77);
const AUTH_METHODS = ['approle', 'token'];
const AUTH_METHODS = ['approle', 'token', 'github'];
const VALID_KV_VERSION = [-1, 1, 2];
async function exportSecrets() {
const vaultUrl = core.getInput('url', { required: true });
const vaultNamespace = core.getInput('namespace', { required: false });
const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
let enginePath = core.getInput('path', { required: false });
let kvVersion = core.getInput('kv-version', { required: false });
@@ -4874,41 +4900,27 @@ async function exportSecrets() {
const secretsInput = core.getInput('secrets', { required: true });
const secretRequests = parseSecretsInput(secretsInput);
const vaultMethod = core.getInput('method', { required: false }) || 'token';
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
if (!AUTH_METHODS.includes(vaultMethod)) {
throw Error(`Sorry, the authentication method ${vaultMethod} is not currently supported.`);
}
let vaultToken = null;
switch (vaultMethod) {
case 'approle':
const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true });
core.debug('Try to retrieve Vault Token from approle');
var options = {
headers: {},
json: { role_id: vaultRoleId, secret_id: vaultSecretId },
responseType: 'json'
};
if (vaultNamespace != null) {
options.headers["X-Vault-Namespace"] = vaultNamespace;
}
/** @type {any} */
const result = await got.post(`${vaultUrl}/v1/auth/approle/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
vaultToken = result.body.auth.client_token;
core.debug('✔ Vault Token has retrieved from approle');
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
break;
default:
vaultToken = core.getInput('token', { required: true });
break;
const defaultOptions = {
prefixUrl: vaultUrl,
headers: {}
}
for (const [headerName, headerValue] of extraHeaders) {
defaultOptions.headers[headerName] = headerValue;
}
if (vaultNamespace != null) {
defaultOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
const client = got.extend(defaultOptions);
const vaultToken = await retrieveToken(vaultMethod, /** @type {any} */ (client));
if (!enginePath) {
enginePath = 'secret';
}
@@ -4939,7 +4951,7 @@ async function exportSecrets() {
requestOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
let requestPath = `${vaultUrl}/v1`;
let requestPath = `v1`;
const kvRequest = !secretPath.startsWith('/')
if (!kvRequest) {
requestPath += secretPath;
@@ -4954,7 +4966,7 @@ async function exportSecrets() {
body = responseCache.get(requestPath);
core.debug(' using cached response');
} else {
const result = await got(requestPath, requestOptions);
const result = await client.get(requestPath, requestOptions);
body = result.body;
responseCache.set(requestPath, body);
}
@@ -4964,9 +4976,11 @@ async function exportSecrets() {
const secretData = getResponseData(body, dataDepth);
const value = selectData(secretData, secretSelector, isJSONPath);
command.issue('add-mask', value);
core.exportVariable(envVarName, `${value}`);
if (exportEnv) {
core.exportVariable(envVarName, `${value}`);
}
core.setOutput(outputVarName, `${value}`);
core.debug(`${secretPath} => outputs.${outputVarName} | env.${envVarName}`);
core.debug(`${secretPath} => outputs.${outputVarName}${exportEnv ? ` | env.${envVarName}` : ''}`);
}
};
@@ -5039,6 +5053,55 @@ function parseSecretsInput(secretsInput) {
return output;
}
/***
* Authentication with Vault and retrieve a vault token
* @param {string} method
* @param {import('got')} client
*/
async function retrieveToken(method, client) {
switch (method) {
case 'approle': {
const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true });
core.debug('Try to retrieve Vault Token from approle');
/** @type {any} */
var options = {
json: { role_id: vaultRoleId, secret_id: vaultSecretId },
responseType: 'json'
};
const result = await client.post(`v1/auth/approle/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
core.debug('✔ Vault Token has retrieved from approle');
return result.body.auth.client_token;
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
}
case 'github': {
const githubToken = core.getInput('githubToken', { required: true });
core.debug('Try to retrieve Vault Token from approle');
/** @type {any} */
var options = {
json: { token: githubToken },
responseType: 'json'
};
const result = await client.post(`v1/auth/github/login`, options);
if (result && result.body && result.body.auth && result.body.auth.client_token) {
core.debug('✔ Vault Token has retrieved from approle');
return result.body.auth.client_token;
} else {
throw Error(`No token was retrieved with the role_id and secret_id provided.`);
}
}
default:
return core.getInput('token', { required: true });
}
}
/**
* Parses a JSON response and returns the secret data
* @param {string} responseBody
@@ -212,7 +212,7 @@ describe('authenticate with approle', () => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('method')
.calledWith('method', expect.anything())
.mockReturnValueOnce('approle');
when(core.getInput)
.calledWith('roleId')
@@ -228,7 +228,7 @@ describe('authenticate with approle', () => {
.mockReturnValueOnce('ns2');
});
it('authenticate with approle', async()=> {
it('authenticate with approle', async() => {
mockInput('test secret');
await exportSecrets();
+987 -1002
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -42,8 +42,9 @@
"got": "^10.2.2"
},
"devDependencies": {
"@types/got": "^9.6.9",
"@types/jest": "^25.1.3",
"@zeit/ncc": "^0.21.1",
"@zeit/ncc": "^0.22.0",
"jest": "^25.1.0",
"jest-when": "^2.7.0",
"semantic-release": "^15.13.24"