Compare commits

...

25 Commits

Author SHA1 Message Date
Richard Simpson 9878eba70a chore: rebuild action 2020-04-07 12:16:27 -05:00
Richard Simpson 83d944ba1a fix: actually allow custom methods 2020-04-07 12:14:02 -05:00
Richard Simpson 567ec72c33 fix: document custom authentication method 2020-04-07 12:09:06 -05:00
Richard Simpson 5c464962be feat: add generic auth (#39)
* feat: add generic auth

Adds the ability to authenticate against any normal Vault endpoint by added the `authPayload` input.

When an unrecognized method is provided, the action will attempt to hit `v1/auth/<method>/login` with the provided `authPayload and parse out the token in the response
2020-04-04 13:19:48 -05:00
dependabot-preview[bot] ea29244204 chore(deps): [security] bump minimist from 1.2.0 to 1.2.5 (#38)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.5. **This update includes security fixes.**
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.5)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 20:49:29 -05:00
Richard Simpson 5de02d5a14 docs: fix type 2020-04-03 12:34:34 -05:00
Richard Simpson 8845b5c724 docs: Actually mark required as required 2020-04-03 12:33:08 -05:00
Richard Simpson c80d0b130c docs: Clean up output a bit 2020-04-03 12:30:03 -05:00
Richard Simpson 01bb0f9bc9 docs: add reference 2020-04-03 12:24:42 -05:00
dependabot-preview[bot] 5a70924133 chore(deps-dev): bump semantic-release from 15.13.24 to 17.0.4 (#18)
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 15.13.24 to 17.0.4.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v15.13.24...v17.0.4)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 11:00:18 -05:00
dependabot-preview[bot] b5fdbf352b chore(deps-dev): bump jest-when from 2.7.0 to 2.7.1 (#34)
Bumps [jest-when](https://github.com/timkindberg/jest-when) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/timkindberg/jest-when/releases)
- [Commits](https://github.com/timkindberg/jest-when/commits)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 10:53:49 -05:00
dependabot-preview[bot] 268a32f886 chore(deps-dev): bump @types/jest from 25.1.4 to 25.1.5 (#36)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 25.1.4 to 25.1.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 10:53:43 -05:00
dependabot-preview[bot] f2b4d0b645 chore(deps-dev): bump jest from 25.2.1 to 25.2.7 (#37)
Bumps [jest](https://github.com/facebook/jest) from 25.2.1 to 25.2.7.
- [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.2.1...v25.2.7)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 10:53:38 -05:00
Richard Simpson 675d33e3da docs: add note about exportEnv option 2020-04-03 10:52:40 -05:00
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
13 changed files with 2828 additions and 2297 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"markdown-toc.depthFrom": 2
}
+76 -8
View File
@@ -4,6 +4,25 @@ 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)
- [Reference](#reference)
- [Masking - Hiding Secrets from Logs](#masking---hiding-secrets-from-logs)
<!-- /TOC -->
## Example Usage
```yaml
@@ -26,16 +45,16 @@ jobs:
## Authentication method
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:
While most workflows will likely use a vault token, you can also use an `approle` to authenticate with Vault. 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 +63,16 @@ 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 }}
```
If any other method is specified and you provide an `authPayload`, the action will attempt to `POST` to `auth/${method}/login` with the provided payload and parse out the client token.
## Key Syntax
@@ -70,7 +99,7 @@ with:
NPMTOKEN=somelongtoken
```
You can also access the secret via ouputs:
You can also access the secret via outputs:
```yaml
steps:
@@ -83,6 +112,8 @@ steps:
```
_**Note:** If you'd like to only use outputs and disable automatic environment variables, you can set the `exportEnv` option to `false`._
### Set Output Variable Name
However, if you want to set it to a specific name, say `NPM_TOKEN`, you could do this instead:
@@ -131,7 +162,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 +175,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 +233,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 +271,27 @@ steps:
ci npm_token
```
## Masking
## Reference
This action uses Github Action's built in masking, so all variables will automatically be masked if printed to the console or to logs.
Here is all the inputs available through `with`:
| Input | Description | Default | Required |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `url` | The URL for the vault endpoint | | ✔ |
| `secrets` | A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details | | ✔ |
| `namespace` | The Vault namespace from which to query secrets. Vault Enterprise only, unset by default | | |
| `path` | The path of a non-default K/V engine | | |
| `kv-version` | The version of the K/V engine to use. | `2` | |
| `method` | The method to use to authenticate with Vault. | `token` | |
| `token` | The Vault Token to be used to authenticate with Vault | | |
| `roleId` | The Role Id for App Role authentication | | |
| `secretId` | The Secret Id for App Role authentication | | |
| `githubToken` | The Github Token to be used to authenticate with Vault | | |
| `authPayload` | The JSON payload to be sent to Vault when using a custom authentication method. | | |
| `extraHeaders` | A string of newline separated extra headers to include on every request. | | |
| `exportEnv` | Whether or not export secrets as environment variables. | `true` | |
## Masking - Hiding Secrets from 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.
+32 -5
View File
@@ -1,20 +1,47 @@
name: 'Vault Secrets'
description: 'A Github Action that allows you to consume the v2 K/V backend of HashiCorp Vault as secure environment variables'
description: 'A Github Action that allows you to consume the v2 K/V backend of HashiCorp Vault as secure environment variables'
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
authPayload:
description: 'The JSON payload to be sent to Vault when using a custom authentication method.'
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'
+417 -326
View File
@@ -34,7 +34,7 @@ module.exports =
/******/ // the startup function
/******/ function startup() {
/******/ // Load entry module and return exports
/******/ return __webpack_require__(104);
/******/ return __webpack_require__(676);
/******/ };
/******/ // initialize runtime
/******/ runtime(__webpack_require__);
@@ -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 => {
@@ -802,22 +805,6 @@ class Response extends Readable {
module.exports = Response;
/***/ }),
/***/ 104:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const core = __webpack_require__(470);
const { exportSecrets } = __webpack_require__(751);
(async () => {
try {
await core.group('Get Vault Secrets', exportSecrets);
} catch (error) {
core.setFailed(error.message);
}
})();
/***/ }),
/***/ 110:
@@ -1565,6 +1552,94 @@ module.exports.iterator = (emitter, event, options) => {
};
/***/ }),
/***/ 151:
/***/ (function(module, __unusedexports, __webpack_require__) {
// @ts-check
const core = __webpack_require__(470);
/***
* Authenticate with Vault and retrieve a Vault token that can be used for requests.
* @param {string} method
* @param {import('got').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 });
return await getClientToken(client, method, { role_id: vaultRoleId, secret_id: vaultSecretId });
}
case 'github': {
const githubToken = core.getInput('githubToken', { required: true });
return await getClientToken(client, method, { token: githubToken });
}
default: {
if (!method || method === 'token') {
return core.getInput('token', { required: true });
} else {
/** @type {string} */
const payload = core.getInput('authPayload', { required: true });
if (!payload) {
throw Error('When using a custom authentication method, you must provide the payload');
}
return await getClientToken(client, method, JSON.parse(payload.trim()));
}
}
}
}
/***
* Call the appropriate login endpoint and parse out the token in the response.
* @param {import('got').Got} client
* @param {string} method
* @param {any} payload
*/
async function getClientToken(client, method, payload) {
/** @type {'json'} */
const responseType = 'json';
var options = {
json: payload,
responseType,
};
core.debug(`Retrieving Vault Token from v1/auth/${method}/login endpoint`);
/** @type {import('got').Response<VaultLoginResponse>} */
const response = await client.post(`v1/auth/${method}/login`, options);
if (response && response.body && response.body.auth && response.body.auth.client_token) {
core.debug('✔ Vault Token successfully retrieved');
core.startGroup('Token Info');
core.debug(`Operating under policies: ${JSON.stringify(response.body.auth.policies)}`);
core.debug(`Token Metadata: ${JSON.stringify(response.body.auth.metadata)}`);
core.endGroup();
return response.body.auth.client_token;
} else {
throw Error(`Unable to retrieve token from ${method}'s login endpoint.`);
}
}
/***
* @typedef {Object} VaultLoginResponse
* @property {{
* client_token: string;
* accessor: string;
* policies: string[];
* metadata: unknown;
* lease_duration: number;
* renewable: boolean;
* }} auth
*/
module.exports = {
retrieveToken,
};
/***/ }),
/***/ 154:
@@ -1573,7 +1648,7 @@ module.exports.iterator = (emitter, event, options) => {
"use strict";
// rfc7231 6.1
const statusCodeCacheableByDefault = [
const statusCodeCacheableByDefault = new Set([
200,
203,
204,
@@ -1585,10 +1660,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 +1678,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 +1698,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 +1707,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 +1757,6 @@ module.exports = class CachePolicy {
cacheHeuristic,
immutableMinTimeToLive,
ignoreCargoCult,
trustServerDate,
_fromObject,
} = {}
) {
@@ -1676,8 +1772,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 +1832,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 +1851,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 +1998,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 +2016,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 +2059,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 +2094,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 +2229,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 +2313,6 @@ module.exports = class CachePolicy {
shared: this._isShared,
cacheHeuristic: this._cacheHeuristic,
immutableMinTimeToLive: this._immutableMinTtl,
trustServerDate: this._trustServerDate,
}),
modified: false,
matches: true,
@@ -2887,19 +2982,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;
}
@@ -4622,6 +4719,22 @@ module.exports = require("util");
/***/ }),
/***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const core = __webpack_require__(470);
const { exportSecrets } = __webpack_require__(928);
(async () => {
try {
await core.group('Get Vault Secrets', exportSecrets);
} catch (error) {
core.setFailed(error.message);
}
})();
/***/ }),
/***/ 678:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
@@ -4847,278 +4960,6 @@ module.exports.DuplexWrapper = DuplexWrapper;
module.exports = require("fs");
/***/ }),
/***/ 751:
/***/ (function(module, __unusedexports, __webpack_require__) {
// @ts-check
// @ts-ignore
const core = __webpack_require__(470);
// @ts-ignore
const command = __webpack_require__(431);
const got = __webpack_require__(77);
const AUTH_METHODS = ['approle', 'token'];
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 });
let enginePath = core.getInput('path', { required: false });
let kvVersion = core.getInput('kv-version', { required: false });
const secretsInput = core.getInput('secrets', { required: true });
const secretRequests = parseSecretsInput(secretsInput);
const vaultMethod = core.getInput('method', { required: false }) || 'token';
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;
}
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 responseCache = new Map();
for (const secretRequest of secretRequests) {
const { secretPath, outputVarName, envVarName, secretSelector, isJSONPath } = secretRequest;
const requestOptions = {
headers: {
'X-Vault-Token': vaultToken
},
};
for (const [headerName, headerValue] of extraHeaders) {
requestOptions.headers[headerName] = headerValue;
}
if (vaultNamespace != null) {
requestOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
let requestPath = `${vaultUrl}/v1`;
const kvRequest = !secretPath.startsWith('/')
if (!kvRequest) {
requestPath += secretPath;
} else {
requestPath += (kvVersion === 2)
? `/${enginePath}/data/${secretPath}`
: `/${enginePath}/${secretPath}`;
}
let body;
if (responseCache.has(requestPath)) {
body = responseCache.get(requestPath);
core.debug(' using cached response');
} else {
const result = await got(requestPath, requestOptions);
body = result.body;
responseCache.set(requestPath, body);
}
let dataDepth = isJSONPath === true ? 0 : kvRequest === false ? 1 : kvVersion;
const secretData = getResponseData(body, dataDepth);
const value = selectData(secretData, secretSelector, isJSONPath);
command.issue('add-mask', value);
core.exportVariable(envVarName, `${value}`);
core.setOutput(outputVarName, `${value}`);
core.debug(`${secretPath} => outputs.${outputVarName} | env.${envVarName}`);
}
};
/** @typedef {Object} SecretRequest
* @property {string} secretPath
* @property {string} envVarName
* @property {string} outputVarName
* @property {string} secretSelector
* @property {boolean} isJSONPath
*/
/**
* Parses a secrets input string into key paths and their resulting environment variable name.
* @param {string} secretsInput
*/
function parseSecretsInput(secretsInput) {
const secrets = secretsInput
.split(';')
.filter(key => !!key)
.map(key => key.trim())
.filter(key => key.length !== 0);
/** @type {SecretRequest[]} */
const output = [];
for (const secret of secrets) {
let path = secret;
let outputVarName = null;
const renameSigilIndex = secret.lastIndexOf('|');
if (renameSigilIndex > -1) {
path = secret.substring(0, renameSigilIndex).trim();
outputVarName = secret.substring(renameSigilIndex + 1).trim();
if (outputVarName.length < 1) {
throw Error(`You must provide a value when mapping a secret to a name. Input: "${secret}"`);
}
}
const pathParts = path
.split(/\s+/)
.map(part => part.trim())
.filter(part => part.length !== 0);
if (pathParts.length !== 2) {
throw Error(`You must provide a valid path and key. Input: "${secret}"`);
}
const [secretPath, secretSelector] = pathParts;
const isJSONPath = secretSelector.includes('.');
if (isJSONPath && !outputVarName) {
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
}
let envVarName = outputVarName;
if (!outputVarName) {
outputVarName = secretSelector;
envVarName = normalizeOutputKey(outputVarName);
}
output.push({
secretPath,
envVarName,
outputVarName,
secretSelector,
isJSONPath
});
}
return output;
}
/**
* Parses a JSON response and returns the secret data
* @param {string} responseBody
* @param {number} dataLevel
*/
function getResponseData(responseBody, dataLevel) {
let secretData = JSON.parse(responseBody);
for (let i = 0; i < dataLevel; i++) {
secretData = secretData['data'];
}
return secretData;
}
/**
* Parses a JSON response and returns the secret data
* @param {Object} data
* @param {string} selector
*/
function selectData(data, selector, isJSONPath) {
if (!isJSONPath) {
return data[selector];
}
// TODO: JSONPath
}
/**
* Replaces any forward-slash characters to
* @param {string} dataKey
*/
function normalizeOutputKey(dataKey) {
return dataKey.replace('/', '__').replace(/[^\w-]/, '').toUpperCase();
}
// @ts-ignore
/**
* @param {string} input
*/
function parseBoolInput(input) {
if (input === null || input === undefined || input.trim() === '') {
return null;
}
return Boolean(input);
}
/**
* @param {string} inputKey
* @param {any} inputOptions
*/
function parseHeadersInput(inputKey, inputOptions) {
/** @type {string}*/
const rawHeadersString = core.getInput(inputKey, inputOptions) || '';
const headerStrings = rawHeadersString
.split('\n')
.map(line => line.trim())
.filter(line => line !== '');
return headerStrings
.reduce((map, line) => {
const seperator = line.indexOf(':');
const key = line.substring(0, seperator).trim().toLowerCase();
const value = line.substring(seperator + 1).trim();
if (map.has(key)) {
map.set(key, [map.get(key), value].join(', '));
} else {
map.set(key, value);
}
return map;
}, new Map());
}
module.exports = {
exportSecrets,
parseSecretsInput,
parseResponse: getResponseData,
normalizeOutputKey,
parseHeadersInput
};
/***/ }),
/***/ 753:
@@ -5886,6 +5727,256 @@ exports.proxyEvents = (proxy, emitter) => {
module.exports = require("dns");
/***/ }),
/***/ 928:
/***/ (function(module, __unusedexports, __webpack_require__) {
// @ts-check
const core = __webpack_require__(470);
const command = __webpack_require__(431);
const got = __webpack_require__(77).default;
const { retrieveToken } = __webpack_require__(151);
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 });
/** @type {number | string} */
let kvVersion = core.getInput('kv-version', { required: false });
const secretsInput = core.getInput('secrets', { required: true });
const secretRequests = parseSecretsInput(secretsInput);
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
const authPayload = core.getInput('authPayload', { required: false });
if (!AUTH_METHODS.includes(vaultMethod) && !authPayload) {
throw Error(`Sorry, the provided authentication method ${vaultMethod} is not currently supported and no custom authPayload was provided.`);
}
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, client);
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 responseCache = new Map();
for (const secretRequest of secretRequests) {
const { secretPath, outputVarName, envVarName, secretSelector, isJSONPath } = secretRequest;
const requestOptions = {
headers: {
'X-Vault-Token': vaultToken
},
};
for (const [headerName, headerValue] of extraHeaders) {
requestOptions.headers[headerName] = headerValue;
}
if (vaultNamespace != null) {
requestOptions.headers["X-Vault-Namespace"] = vaultNamespace;
}
let requestPath = `v1`;
const kvRequest = !secretPath.startsWith('/')
if (!kvRequest) {
requestPath += secretPath;
} else {
requestPath += (kvVersion === 2)
? `/${enginePath}/data/${secretPath}`
: `/${enginePath}/${secretPath}`;
}
let body;
if (responseCache.has(requestPath)) {
body = responseCache.get(requestPath);
core.debug(' using cached response');
} else {
const result = await client.get(requestPath, requestOptions);
body = result.body;
responseCache.set(requestPath, body);
}
let dataDepth = isJSONPath === true ? 0 : kvRequest === false ? 1 : kvVersion;
const secretData = getResponseData(body, dataDepth);
const value = selectData(secretData, secretSelector, isJSONPath);
command.issue('add-mask', value);
if (exportEnv) {
core.exportVariable(envVarName, `${value}`);
}
core.setOutput(outputVarName, `${value}`);
core.debug(`${secretPath} => outputs.${outputVarName}${exportEnv ? ` | env.${envVarName}` : ''}`);
}
};
/** @typedef {Object} SecretRequest
* @property {string} secretPath
* @property {string} envVarName
* @property {string} outputVarName
* @property {string} secretSelector
* @property {boolean} isJSONPath
*/
/**
* Parses a secrets input string into key paths and their resulting environment variable name.
* @param {string} secretsInput
*/
function parseSecretsInput(secretsInput) {
const secrets = secretsInput
.split(';')
.filter(key => !!key)
.map(key => key.trim())
.filter(key => key.length !== 0);
/** @type {SecretRequest[]} */
const output = [];
for (const secret of secrets) {
let path = secret;
let outputVarName = null;
const renameSigilIndex = secret.lastIndexOf('|');
if (renameSigilIndex > -1) {
path = secret.substring(0, renameSigilIndex).trim();
outputVarName = secret.substring(renameSigilIndex + 1).trim();
if (outputVarName.length < 1) {
throw Error(`You must provide a value when mapping a secret to a name. Input: "${secret}"`);
}
}
const pathParts = path
.split(/\s+/)
.map(part => part.trim())
.filter(part => part.length !== 0);
if (pathParts.length !== 2) {
throw Error(`You must provide a valid path and key. Input: "${secret}"`);
}
const [secretPath, secretSelector] = pathParts;
const isJSONPath = secretSelector.includes('.');
if (isJSONPath && !outputVarName) {
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
}
let envVarName = outputVarName;
if (!outputVarName) {
outputVarName = secretSelector;
envVarName = normalizeOutputKey(outputVarName);
}
output.push({
secretPath,
envVarName,
outputVarName,
secretSelector,
isJSONPath
});
}
return output;
}
/**
* Parses a JSON response and returns the secret data
* @param {string} responseBody
* @param {number} dataLevel
*/
function getResponseData(responseBody, dataLevel) {
let secretData = JSON.parse(responseBody);
for (let i = 0; i < dataLevel; i++) {
secretData = secretData['data'];
}
return secretData;
}
/**
* Parses a JSON response and returns the secret data
* @param {Object} data
* @param {string} selector
*/
function selectData(data, selector, isJSONPath) {
if (!isJSONPath) {
return data[selector];
}
// TODO: JSONPath
}
/**
* Replaces any forward-slash characters to
* @param {string} dataKey
*/
function normalizeOutputKey(dataKey) {
return dataKey.replace('/', '__').replace(/[^\w-]/, '').toUpperCase();
}
/**
* @param {string} inputKey
* @param {any} inputOptions
*/
function parseHeadersInput(inputKey, inputOptions) {
/** @type {string}*/
const rawHeadersString = core.getInput(inputKey, inputOptions) || '';
const headerStrings = rawHeadersString
.split('\n')
.map(line => line.trim())
.filter(line => line !== '');
return headerStrings
.reduce((map, line) => {
const seperator = line.indexOf(':');
const key = line.substring(0, seperator).trim().toLowerCase();
const value = line.substring(seperator + 1).trim();
if (map.has(key)) {
map.set(key, [map.get(key), value].join(', '));
} else {
map.set(key, value);
}
return map;
}, new Map());
}
module.exports = {
exportSecrets,
parseSecretsInput,
parseResponse: getResponseData,
normalizeOutputKey,
parseHeadersInput
};
/***/ }),
/***/ 946:
+1 -1
View File
@@ -5,7 +5,7 @@ const core = require('@actions/core');
const got = require('got');
const { when } = require('jest-when');
const { exportSecrets } = require('../../action');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
@@ -5,7 +5,7 @@ const core = require('@actions/core');
const got = require('got');
const { when } = require('jest-when');
const { exportSecrets } = require('../../action');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8201'}`;
@@ -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();
+2162 -1888
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -4,7 +4,7 @@
"description": "A Github Action that allows you to consume vault secrets as secure environment variables.",
"main": "dist/index.js",
"scripts": {
"build": "ncc build index.js -o dist",
"build": "ncc build src/index.js -o dist",
"test": "jest",
"test:integration:basic": "jest -c integrationTests/basic/jest.config.js",
"test:integration:enterprise": "jest -c integrationTests/enterprise/jest.config.js",
@@ -38,14 +38,15 @@
},
"homepage": "https://github.com/RichiCoder1/vault-action#readme",
"dependencies": {
"@actions/core": "^1.2.2",
"got": "^10.2.2"
"got": "^10.2.2",
"@actions/core": "^1.2.3"
},
"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"
"semantic-release": "^17.0.4"
}
}
+29 -51
View File
@@ -1,60 +1,47 @@
// @ts-check
// @ts-ignore
const core = require('@actions/core');
// @ts-ignore
const command = require('@actions/core/lib/command');
const got = require('got');
const got = require('got').default;
const { retrieveToken } = require('./auth');
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 });
/** @type {number | string} */
let kvVersion = core.getInput('kv-version', { required: false });
const secretsInput = core.getInput('secrets', { required: true });
const secretRequests = parseSecretsInput(secretsInput);
const vaultMethod = core.getInput('method', { required: false }) || 'token';
if (!AUTH_METHODS.includes(vaultMethod)) {
throw Error(`Sorry, the authentication method ${vaultMethod} is not currently supported.`);
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
const authPayload = core.getInput('authPayload', { required: false });
if (!AUTH_METHODS.includes(vaultMethod) && !authPayload) {
throw Error(`Sorry, the provided authentication method ${vaultMethod} is not currently supported and no custom authPayload was provided.`);
}
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, 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}` : ''}`);
}
};
@@ -220,17 +209,6 @@ function normalizeOutputKey(dataKey) {
return dataKey.replace('/', '__').replace(/[^\w-]/, '').toUpperCase();
}
// @ts-ignore
/**
* @param {string} input
*/
function parseBoolInput(input) {
if (input === null || input === undefined || input.trim() === '') {
return null;
}
return Boolean(input);
}
/**
* @param {string} inputKey
* @param {any} inputOptions
+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;
}
+81
View File
@@ -0,0 +1,81 @@
// @ts-check
const core = require('@actions/core');
/***
* Authenticate with Vault and retrieve a Vault token that can be used for requests.
* @param {string} method
* @param {import('got').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 });
return await getClientToken(client, method, { role_id: vaultRoleId, secret_id: vaultSecretId });
}
case 'github': {
const githubToken = core.getInput('githubToken', { required: true });
return await getClientToken(client, method, { token: githubToken });
}
default: {
if (!method || method === 'token') {
return core.getInput('token', { required: true });
} else {
/** @type {string} */
const payload = core.getInput('authPayload', { required: true });
if (!payload) {
throw Error('When using a custom authentication method, you must provide the payload');
}
return await getClientToken(client, method, JSON.parse(payload.trim()));
}
}
}
}
/***
* Call the appropriate login endpoint and parse out the token in the response.
* @param {import('got').Got} client
* @param {string} method
* @param {any} payload
*/
async function getClientToken(client, method, payload) {
/** @type {'json'} */
const responseType = 'json';
var options = {
json: payload,
responseType,
};
core.debug(`Retrieving Vault Token from v1/auth/${method}/login endpoint`);
/** @type {import('got').Response<VaultLoginResponse>} */
const response = await client.post(`v1/auth/${method}/login`, options);
if (response && response.body && response.body.auth && response.body.auth.client_token) {
core.debug('✔ Vault Token successfully retrieved');
core.startGroup('Token Info');
core.debug(`Operating under policies: ${JSON.stringify(response.body.auth.policies)}`);
core.debug(`Token Metadata: ${JSON.stringify(response.body.auth.metadata)}`);
core.endGroup();
return response.body.auth.client_token;
} else {
throw Error(`Unable to retrieve token from ${method}'s login endpoint.`);
}
}
/***
* @typedef {Object} VaultLoginResponse
* @property {{
* client_token: string;
* accessor: string;
* policies: string[];
* metadata: unknown;
* lease_duration: number;
* renewable: boolean;
* }} auth
*/
module.exports = {
retrieveToken,
};
View File
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"moduleResolution": "node",
"allowJs": true,
"noEmit": true
},
"exclude": [
"node_modules"
]
}