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

Merge branch 'main' into master

This commit is contained in:
Scott Lemme 2023-03-02 14:05:17 -05:00 committed by GitHub
commit 5ecd962b6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 17804 additions and 22311 deletions

View file

@ -17,7 +17,7 @@ The yaml of the `vault-action` step, with any sensitive information masked or re
A clear and concise description of what you expected to happen.
**Log Output**
For the most verbose logs, [add a secret called `ACTIONS_STEP_DEBUG` with the value `true`](https://github.com/actions/toolkit/blob/master/docs/action-debugging.md). Then, re-run the workflow if possible and post the *raw logs* for the step here with any sensitive information masked or removed.
For the most verbose logs, [add a secret called `ACTIONS_STEP_DEBUG` with the value `true`](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md). Then, re-run the workflow if possible and post the *raw logs* for the step here with any sensitive information masked or removed.
**Additional context**
Add any other context about the problem here.

View file

@ -1,19 +1,22 @@
on:
push:
branches:
- master
- main
pull_request_target:
types: [opened, reopened, synchronize]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
with:
node-version: ''
ref: ${{ github.ref }}
- uses: actions/setup-node@v3
with:
node-version: '16.14.0'
- name: Setup NPM Cache
uses: actions/cache@v1
@ -36,14 +39,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Run docker-compose
run: docker-compose up -d vault
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ''
node-version: '16.14.0'
- name: Setup NPM Cache
uses: actions/cache@v1
@ -70,14 +75,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Run docker-compose
run: docker-compose up -d vault-enterprise
env:
VAULT_LICENSE_CI: ${{ secrets.VAULT_LICENSE_CI }}
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ''
node-version: '16.14.0'
- name: Setup NPM Cache
uses: actions/cache@v1
@ -93,7 +102,7 @@ jobs:
- name: NPM Build
run: npm run build
- name: NPM Run test:intergration:enterprise
- name: NPM Run test:integration:enterprise
run: npm run test:integration:enterprise
env:
VAULT_HOST: localhost
@ -104,14 +113,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Run docker-compose
run: docker-compose up -d vault
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ''
node-version: '16.14.0'
- name: Setup NPM Cache
uses: actions/cache@v1
@ -172,14 +183,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Run docker-compose
run: docker-compose up -d vault-tls
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ''
node-version: '16.14.0'
- name: Setup NPM Cache
uses: actions/cache@v1
@ -261,14 +274,14 @@ jobs:
# Removing publish step for now.
# publish:
# if: github.event_name == 'push' && contains(github.ref, 'master')
# if: github.event_name == 'push' && contains(github.ref, 'main')
# runs-on: ubuntu-latest
# needs: [build, integration, e2e]
# steps:
# - uses: actions/checkout@v1
# - uses: actions/setup-node@v1
# - uses: actions/setup-node@v3
# with:
# node-version: ''
# node-version: '16.14.0'
# - name: setup npm cache
# uses: actions/cache@v1
# with:
@ -279,9 +292,8 @@ jobs:
# - name: npm install
# run: npm ci
# - name: release
# if: success() && endsWith(github.ref, 'master')
# if: success() && endsWith(github.ref, 'main')
# run: npx semantic-release
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -13,21 +13,6 @@ jobs:
runs-on: ubuntu-latest
name: Jira sync
steps:
- name: Check if community user
if: github.event.action == 'opened'
id: vault-team-role
run: |
TEAM=vault
ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')"
if [[ -n ${ROLE} ]]; then
echo "Actor ${{ github.actor }} is a ${TEAM} team member, skipping ticket creation"
else
echo "Actor ${{ github.actor }} is not a ${TEAM} team member"
fi
echo "::set-output name=role::${ROLE}"
env:
GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }}
- name: Login
uses: atlassian/gajira-login@v2.0.0
env:
@ -46,7 +31,7 @@ jobs:
fi
- name: Create ticket
if: github.event.action == 'opened' && !steps.vault-team-role.outputs.role
if: github.event.action == 'opened'
uses: tomhjp/gh-action-jira-create@v0.2.0
with:
project: VAULT
@ -55,7 +40,7 @@ jobs:
description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created from GitHub Action for ${{ github.event.issue.html_url || github.event.pull_request.html_url }} from ${{ github.actor }}_"
# customfield_10089 is Issue Link custom field
# customfield_10091 is team custom field
extraFields: '{"fixVersions": [{"name": "TBD"}], "customfield_10091": ["ecosystem", "runtime"], "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"}'
extraFields: '{"fixVersions": [{"name": "TBD"}], "customfield_10091": ["ecosystem", "applications"], "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"}'
- name: Search
if: github.event.action != 'opened'
@ -63,7 +48,7 @@ jobs:
uses: tomhjp/gh-action-jira-search@v0.2.1
with:
# cf[10089] is Issue Link custom field
jql: 'project = "VAULT" and issuetype = "GH Issue" and cf[10089]="${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
jql: 'project = "VAULT" and cf[10089]="${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
- name: Sync comment
if: github.event.action == 'created' && steps.search.outputs.issue
@ -77,11 +62,11 @@ jobs:
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: Done
transition: Closed
- name: Reopen ticket
if: github.event.action == 'reopened' && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: "To Do"
transition: "Pending Triage"

24
.github/workflows/local-test.yaml vendored Normal file
View file

@ -0,0 +1,24 @@
# This is a sample workflow to help test contributions
# Change the branch name, url and token to fit with your own environment
# Use 'on: push' instead of 'on: local-test' if you wish to run the test on github
# If running locally with act, run the workflow with 'act local-test'
# Don't forget to revert the file changes and invalidate any tokens that were committed before opening a pull-request
on: local-test
name: local-test
jobs:
build:
name: local-test
runs-on: ubuntu-latest
steps:
- name: Import Secrets
uses: hashicorp/vault-action@YOUR_BRANCH_NAME
with:
url: http://localhost:8200
method: token
token: testtoken
secrets: |
secret/data/test secret | SAMPLE_SECRET;

View file

@ -1,5 +1,63 @@
## Unreleased
## 2.5.0 (Jan 26th, 2023)
Features:
* Adds ability to automatically decode secrets from base64, hex, and utf8 encodings. [GH-408](https://github.com/hashicorp/vault-action/pull/408)
Improvements:
* Improves error messages for Vault authentication failures [GH-409](https://github.com/hashicorp/vault-action/pull/409)
* bump jest from 28.1.1 to 29.3.1 [GH-397](https://github.com/hashicorp/vault-action/pull/397)
* bump @types/jest from 28.1.3 to 29.2.6 [GH-397](https://github.com/hashicorp/vault-action/pull/397), [GH-413](https://github.com/hashicorp/vault-action/pull/413)
* bump jsrsasign from 10.5.27 to 10.6.1 [GH-401](https://github.com/hashicorp/vault-action/pull/401)
* bump json5 from 2.2.1 to 2.2.3 [GH-404](https://github.com/hashicorp/vault-action/pull/404)
* bump minimatch from 3.0.4 to 3.1.2 [GH-410](https://github.com/hashicorp/vault-action/pull/410)
## 2.4.3 (Nov 8th, 2022)
Improvements:
* bump jest-when from 3.5.1 to 3.5.2 [GH-388](https://github.com/hashicorp/vault-action/pull/388)
* bump semantic-release from 19.0.3 to 19.0.5 [GH-360](https://github.com/hashicorp/vault-action/pull/360)
* bump jsrsasign from 10.5.25 to 10.5.27 [GH-358](https://github.com/hashicorp/vault-action/pull/358)
* bump @actions/core from 1.9.0 to 1.10.0 [GH-371](https://github.com/hashicorp/vault-action/pull/371)
* update runtime to node16 for action [GH-375](https://github.com/hashicorp/vault-action/pull/375)
## 2.4.2 (Aug 15, 2022)
Bugs:
* Errors due to replication delay for tokens will now be retried [GH-333](https://github.com/hashicorp/vault-action/pull/333)
Improvements:
* bump got from 11.5.1 to 11.8.5 [GH-344](https://github.com/hashicorp/vault-action/pull/344)
## 2.4.1 (April 28th, 2022)
Improvements:
* Make secrets parameter optional [GH-299](https://github.com/hashicorp/vault-action/pull/299)
* auth/jwt: make "role" input optional [GH-291](https://github.com/hashicorp/vault-action/pull/291)
* Write a better error message when secret not found [GH-306](https://github.com/hashicorp/vault-action/pull/306)
* bump jest-when from 2.7.2 to 3.5.1 [GH-294](https://github.com/hashicorp/vault-action/pull/294)
* bump node-fetch from 2.6.1 to 2.6.7 [GH-308](https://github.com/hashicorp/vault-action/pull/308)
* bump @types/jest from 26.0.23 to 27.4.1 [GH-297](https://github.com/hashicorp/vault-action/pull/297)
* bump trim-off-newlines from 1.0.1 to 1.0.3 [GH-309](https://github.com/hashicorp/vault-action/pull/309)
* bump moment from 2.28.0 to 2.29.2 [GH-304](https://github.com/hashicorp/vault-action/pull/304)
* bump @types/got from 9.6.11 to 9.6.12 [GH-266](https://github.com/hashicorp/vault-action/pull/266)
## 2.4.0 (October 21st, 2021)
Features:
* GitHub provided JWT auth is now supported [GH-257](https://github.com/hashicorp/vault-action/pull/257)
## 2.3.1 (August 23rd, 2021)
Improvements:
* bump normalize-url from 4.5.0 to 4.5.1 [GH-227](https://github.com/hashicorp/vault-action/pull/227)
* bump path-parse from 1.0.6 to 1.0.7 [GH-239](https://github.com/hashicorp/vault-action/pull/239)
## 2.3.0 (June 23rd, 2021)
Features:

266
README.md
View file

@ -12,18 +12,26 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
- [Vault GitHub Action](#vault-github-action)
- [Example Usage](#example-usage)
- [Authentication method](#authentication-method)
- [Authentication Methods](#authentication-methods)
- [JWT with GitHub OIDC Tokens](#jwt-with-github-oidc-tokens)
- [AppRole](#approle)
- [Token](#token)
- [GitHub](#github)
- [JWT with OIDC Provider](#jwt-with-oidc-provider)
- [Kubernetes](#kubernetes)
- [Other Auth Methods](#other-auth-methods)
- [Key Syntax](#key-syntax)
- [Simple Key](#simple-key)
- [Set Output Variable Name](#set-output-variable-name)
- [Multiple Secrets](#multiple-secrets)
- [Other Secret Engines](#other-secret-engines)
- [Adding Extra Headers](#adding-extra-headers)
- [Vault Enterprise Features](#vault-enterprise-features)
- [HashiCorp Cloud Platform or Vault Enterprise](#hashicorp-cloud-platform-or-vault-enterprise)
- [Namespace](#namespace)
- [Reference](#reference)
- [Masking - Hiding Secrets from Logs](#masking---hiding-secrets-from-logs)
- [Normalization](#normalization)
- [Contributing](#contributing)
<!-- /TOC -->
@ -36,11 +44,11 @@ jobs:
steps:
# ...
- name: Import Secrets
uses: hashicorp/vault-action@v2.3.0
uses: hashicorp/vault-action@v2
with:
url: https://vault.mycompany.com:8200
token: ${{ secrets.VaultToken }}
caCertificate: ${{ secrets.VAULTCA }}
token: ${{ secrets.VAULT_TOKEN }}
caCertificate: ${{ secrets.VAULT_CA_CERT }}
secrets: |
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
@ -48,66 +56,176 @@ jobs:
# ...
```
## Authentication method
## Authentication Methods
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:
Consider using a [Vault authentication method](https://www.vaultproject.io/docs/auth) such as the JWT auth method with
[GitHub OIDC tokens](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) or the AppRole auth method. You can configure which by using the `method` parameter.
### JWT with GitHub OIDC Tokens
You can configure trust between a GitHub Actions workflow
and Vault using the
[GitHub's OIDC provider](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect).
Each GitHub Actions workflow receives an auto-generated OIDC token with claims
to establish the identity of the workflow.
__Vault Configuration__
<details>
<summary>Click to toggle instructions for configuring Vault.</summary>
Set up Vault with the [JWT auth method](https://www.vaultproject.io/api/auth/jwt#configure).
Pass the following parameters to your auth method configuration:
- `oidc_discovery_url`: `https://token.actions.githubusercontent.com`
- `bound_issuer`: `https://token.actions.githubusercontent.com`
Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) for the auth method.
- `role_type`: `jwt`
- `bound_audiences`: `"https://github.com/<org>"`. Update this parameter if
you change the `aud` claim in the GitHub OIDC token via the
`jwtGithubAudience` parameter in the action config.
- `user_claim`: Set this to a claim name (e.g., `repository`) in the
[GitHub OIDC token](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token).
- `bound_claims` OR `bound_subject`: match on [GitHub subject claims](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims).
- For wildcard (non-exact) matches, use `bound_claims`.
- `bound_claims_type`: `glob`
- `bound_claims`: JSON object. Maps one or more claim names to corresponding wildcard values.
```json
{"sub": "repo:<orgName>/*"}
```
- For exact matches, use `bound_subject`.
- `bound_claims_type`: `string`
- `bound_subject`: Must exactly match the `sub` claim in the OIDC token.
```plaintext
repo:<orgName/repoName>:ref:refs/heads/branchName
```
</details>
__GitHub Actions Workflow__
In the GitHub Actions workflow, the workflow needs permissions to read contents
and write the ID token.
- **token**: (by default) you must provide a `token` parameter
```yaml
...
with:
url: https://vault.mycompany.com:8200
token: ${{ secrets.VaultToken }}
caCertificate: ${{ secrets.VAULTCA }}
jobs:
retrieve-secret:
permissions:
contents: read
id-token: write
```
- **approle**: you must provide a `roleId` & `secretId` parameter
In the action, provide the name of the Vault role you created to the `role` parameter.
You can optionally set the `jwtGithubAudience` parameter to change the `aud`
claim from its default.
```yaml
...
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
role: <Vault JWT Auth Role Name>
method: jwt
jwtGithubAudience: sigstore # set the GitHub token's aud claim
```
### AppRole
The [AppRole auth method](https://www.vaultproject.io/docs/auth/approle) allows
your GitHub Actions workflow to authenticate to Vault with a pre-defined role.
Set the role ID and secret ID as GitHub secrets and pass them to the
`roleId` and `secretId` parameters.
```yaml
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: approle
roleId: ${{ secrets.roleId }}
secretId: ${{ secrets.secretId }}
caCertificate: ${{ secrets.VAULTCA }}
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
```
- **github**: you must provide the github token as `githubToken`
**Notice: [Vault GitHub authentication](https://www.vaultproject.io/docs/auth/github)
### Token
For the default method of authenticating to Vault,
use a [Vault token](https://www.vaultproject.io/docs/concepts/tokens).
Set the Vault token as a GitHub secret and pass
it to the `token` parameter.
```yaml
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
token: ${{ secrets.VAULT_TOKEN }}
```
### GitHub
The [GitHub auth method](https://www.vaultproject.io/docs/auth/github)
requires `read:org` permissions for authentication. The auto-generated `GITHUB_TOKEN`
created for projects does not have these permissions and GitHub does not allow this
token's permissions to be modified. A new GitHub Token secret must be created with
`read:org` permissions to use this authentication method.**
`read:org` permissions to use this authentication method.
Pass the GitHub token as a GitHub secret into the `githubToken` parameter.
```yaml
...
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: github
githubToken: ${{ secrets.MY_GITHUB_TOKEN }}
caCertificate: ${{ secrets.VAULTCA }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
```
- **jwt**: you must provide a `role` & `jwtPrivateKey` parameters, additionally you can pass `jwtKeyPassword` & `jwtTtl` parameters
### JWT with OIDC Provider
You can configure trust between your own OIDC Provider and Vault
with the JWT auth method. Provide a `role` & `jwtPrivateKey` parameters,
additionally you can pass `jwtKeyPassword` & `jwtTtl` parameters
```yaml
...
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: jwt
role: github-action
role: <Vault JWT Auth Role Name>
jwtPrivateKey: ${{ secrets.JWT_PRIVATE_KEY }}
jwtKeyPassword: ${{ secrets.JWT_KEY_PASS }}
jwtTtl: 3600 # 1 hour, default value
```
- **kubernetes**: you must provide the `role` paramaters. You can optionally override the `kubernetesTokenPath` paramater for custom mounted serviceAccounts. Consider [kubernetes auth](https://www.vaultproject.io/docs/auth/kubernetes) when using self-hosted runners on Kubernetes:
### Kubernetes
Consider the [Kubernetes auth method](https://www.vaultproject.io/docs/auth/kubernetes)
when using self-hosted runners on Kubernetes. You must provide the `role` parameter
for the Vault role associated with the Kubernetes auth method.
You can optionally override the `kubernetesTokenPath` parameter for
custom-mounted serviceAccounts.
```yaml
...
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: kubernetes
role: ${{ secrets.KUBE_ROLE }}
role: <Vault Kubernetes Auth Role Name>
kubernetesTokenPath: /var/run/secrets/kubernetes.io/serviceaccount/token # default token path
```
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.
### Other Auth Methods
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
@ -144,7 +262,6 @@ steps:
# Import config...
- name: Sensitive Operation
run: "my-cli --token '${{ steps.secrets.outputs.npmToken }}'"
```
_**Note:** If you'd like to only use outputs and disable automatic environment variables, you can set the `exportEnv` option to `false`._
@ -244,11 +361,16 @@ with:
This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to Vault.
## Vault Enterprise Features
## HashiCorp Cloud Platform or Vault Enterprise
If you are using [HCP Vault](https://cloud.hashicorp.com/products/vault)
or Vault Enterprise, you may need additional parameters in
your GitHub Actions workflow.
### Namespace
If you need to retrieve secrets from a specific Vault namespace, all that's required is an additional parameter specifying the namespace.
If you need to retrieve secrets from a specific Vault namespace, set the `namespace`
parameter specifying the namespace. In HCP Vault, the namespace defaults to `admin`.
```yaml
steps:
@ -257,10 +379,10 @@ steps:
uses: hashicorp/vault-action
with:
url: https://vault-enterprise.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: token
caCertificate: ${{ secrets.VAULTCA }}
token: ${{ secrets.VaultToken }}
namespace: ns1
token: ${{ secrets.VAULT_TOKEN }}
namespace: admin
secrets: |
secret/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
@ -274,7 +396,7 @@ Here are 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 | | |
| `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 | | |
| `method` | The method to use to authenticate with Vault. | `token` | |
| `role` | Vault role for specified auth method | | |
@ -285,6 +407,7 @@ Here are all the inputs available through `with`:
| `githubToken` | The Github Token to be used to authenticate with Vault | | |
| `jwtPrivateKey` | Base64 encoded Private key to sign JWT | | |
| `jwtKeyPassword` | Password for key stored in jwtPrivateKey (if needed) | | |
| `jwtGithubAudience` | Identifies the recipient ("aud" claim) that the JWT is intended for |`sigstore`| |
| `jwtTtl` | Time in seconds, after which token expires | | 3600 |
| `kubernetesTokenPath` | The path to the service-account secret with the jwt token for kubernetes based authentication |`/var/run/secrets/kubernetes.io/serviceaccount/token` | |
| `authPayload` | The JSON payload to be sent to Vault when using a custom authentication method. | | |
@ -304,3 +427,70 @@ This action uses GitHub Action's built-in masking, so all variables will automat
## Normalization
To make it simpler to consume certain secrets as env vars, if no Env/Output Var Name is specified `vault-action` will replace and `.` chars with `__`, remove any other non-letter or number characters. If you're concerned about the result, it's recommended to provide an explicit Output Var Key.
## Contributing
If you wish to contribute to this project, the following dependencies are recommended for local development:
- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) to install dependencies, build project and run tests
- [docker](https://docs.docker.com/get-docker/) to run the pre-configured vault containers for acceptance tests
- [docker-compose](https://docs.docker.com/compose/) to spin up the pre-configured vault containers for acceptance tests
- [act](https://github.com/nektos/act) to run the vault-action locally
### Build
Use npm to install dependencies and build the project:
```sh
$ npm install && npm run build
```
### Vault test instance
The Github Action needs access to a working Vault instance to function.
Multiple docker configurations are available via the docker-compose.yml file to run containers compatible with the various acceptance test suites.
```sh
$ docker-compose up -d vault # Choose one of: vault, vault-enterprise, vault-tls depending on which tests you would like to run
```
Instead of using one of the dockerized instance, you can also use your own local or remote Vault instance by exporting these environment variables:
```sh
$ export VAULT_HOST=<YOUR VAULT CLUSTER LOCATION> # localhost if undefined
$ export VAULT_PORT=<YOUR VAULT PORT> # 8200 if undefined
$ export VAULT_TOKEN=<YOUR VAULT TOKEN> # testtoken if undefined
```
### Running unit tests
Unit tests can be executed at any time with no dependencies or prior setup.
```sh
$ npm test
```
### Running acceptance tests
With a succesful build to take your local changes into account and a working Vault instance configured, you can now run acceptance tests to validate if any regressions were introduced.
```sh
$ npm run test:integration:basic # Choose one of: basic, enterprise, e2e, e2e-tls
```
### Running the action locally
You can use the [act](https://github.com/nektos/act) command to test your changes locally if desired. Unfortunately it is not currently possible to use uncommitted local changes for a shared workfow. You will still need to push
the changes you would like to validate beforehand. Even if a commit is necessary, this is still a more detailed and faster feedback loop than waiting for the action to be executed by Github in a different repository.
Push your changes into a feature branch.
```sh
$ git checkout -b my-feature-branch
$ git commit -m "testing new changes"
$ git push
```
Edit the ./.github/workflows/local-test.yaml file to use your new feature branch. You may have to additionally edit the vault url, token and secret path if you are not using one of the provided containerized instance.
Run your feature branch locally.
```sh
$ act local-test
```

View file

@ -6,7 +6,7 @@ inputs:
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
required: false
namespace:
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
required: false
@ -69,12 +69,18 @@ inputs:
jwtKeyPassword:
description: 'Password for key stored in jwtPrivateKey (if needed)'
required: false
jwtGithubAudience:
description: 'Identifies the recipient ("aud" claim) that the JWT is intended for'
required: false
jwtTtl:
description: 'Time in seconds, after which token expires'
required: false
default: 3600
secretEncodingType:
description: 'The encoding type of the secret to decode. If not specified, the secret will not be decoded. Supported values: base64, hex, utf8'
required: false
runs:
using: 'node12'
using: 'node16'
main: 'dist/index.js'
branding:
icon: 'unlock'

16547
dist/index.js vendored

File diff suppressed because one or more lines are too long

View file

@ -12,6 +12,7 @@ services:
image: hashicorp/vault-enterprise:latest
environment:
VAULT_DEV_ROOT_TOKEN_ID: testtoken
VAULT_LICENSE: ${VAULT_LICENSE_CI}
ports:
- 8200:8200
privileged: true

View file

@ -8,20 +8,21 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('integration', () => {
beforeAll(async () => {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
});
await got(`${vaultUrl}/v1/secret/data/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -33,7 +34,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret/data/nested/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -45,7 +46,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret/data/foobar`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -59,7 +60,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/sys/mounts/secret-kv1`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
type: 'kv'
@ -77,7 +78,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
secret: 'CUSTOMSECRET',
@ -87,7 +88,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/foobar`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
fookv1: 'bar',
@ -97,7 +98,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/nested/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
"other-Secret-dash": 'OTHERCUSTOMSECRET',
@ -109,20 +110,28 @@ describe('integration', () => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url')
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('token')
.mockReturnValueOnce('testtoken');
.calledWith('token', expect.anything())
.mockReturnValueOnce(vaultToken);
});
function mockInput(secrets) {
when(core.getInput)
.calledWith('secrets')
.calledWith('secrets', expect.anything())
.mockReturnValueOnce(secrets);
}
it('prints a nice error message when secret not found', async () => {
mockInput(`secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ;
secret/data/notFound kehe | NO_SIR ;`);
expect(exportSecrets()).rejects.toEqual(Error(`Unable to retrieve result for "secret/data/notFound" because it was not found: {"errors":[]}`));
})
it('get simple secret', async () => {
mockInput('secret/data/test secret');
@ -247,7 +256,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/cubbyhole/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
foo: "bar",

View file

@ -1,7 +1,9 @@
jest.mock('@actions/core');
jest.mock('@actions/core/lib/command');
const core = require('@actions/core');
const rsasign = require('jsrsasign');
const {
privateRsaKey,
privateRsaKeyBase64,
publicRsaKey
} = require('./rsa_keys');
@ -12,13 +14,53 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
/**
* Returns Github OIDC response mock
* @param {string} aud Audience claim
* @returns {string}
*/
function mockGithubOIDCResponse(aud= "https://github.com/hashicorp/vault-action") {
const alg = 'RS256';
const header = { alg: alg, typ: 'JWT' };
const now = rsasign.KJUR.jws.IntDate.getNow();
const payload = {
jti: "unique-id",
sub: "repo:hashicorp/vault-action:ref:refs/heads/main",
aud,
ref: "refs/heads/main",
sha: "commit-sha",
repository: "hashicorp/vault-action",
repository_owner: "hashicorp",
run_id: "1",
run_number: "1",
run_attempt: "1",
actor: "github-username",
workflow: "Workflow Name",
head_ref: "",
base_ref: "",
event_name: "push",
ref_type: "branch",
job_workflow_ref: "hashicorp/vault-action/.github/workflows/workflow.yml@refs/heads/main",
iss: 'vault-action',
iat: now,
nbf: now,
exp: now + 3600,
};
const decryptedKey = rsasign.KEYUTIL.getKey(privateRsaKey);
return rsasign.KJUR.jws.JWS.sign(alg, JSON.stringify(header), JSON.stringify(payload), decryptedKey);
}
// The sign call inside this function takes a while to run, so cache the default JWT in a constant.
const defaultGithubJwt = mockGithubOIDCResponse();
describe('jwt auth', () => {
beforeAll(async () => {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
});
@ -26,7 +68,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/sys/auth/jwt`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
type: 'jwt'
@ -44,7 +86,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/sys/policy/reader`, {
method: 'PUT',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
policy: `
@ -58,17 +100,18 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/auth/jwt/config`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
jwt_validation_pubkeys: publicRsaKey
jwt_validation_pubkeys: publicRsaKey,
default_role: "default"
}
});
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
role_type: 'jwt',
@ -84,7 +127,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/secret/data/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -94,33 +137,120 @@ describe('jwt auth', () => {
});
});
beforeEach(() => {
jest.resetAllMocks();
describe('authenticate with private key', () => {
beforeEach(() => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url')
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('method')
.mockReturnValueOnce('jwt');
when(core.getInput)
.calledWith('method', expect.anything())
.mockReturnValueOnce('jwt');
when(core.getInput)
.calledWith('jwtPrivateKey')
.mockReturnValueOnce(privateRsaKeyBase64);
when(core.getInput)
.calledWith('jwtPrivateKey', expect.anything())
.mockReturnValueOnce(privateRsaKeyBase64);
when(core.getInput)
.calledWith('role')
.mockReturnValueOnce('default');
when(core.getInput)
.calledWith('role', expect.anything())
.mockReturnValueOnce('default');
when(core.getInput)
.calledWith('secrets')
.mockReturnValueOnce('secret/data/test secret');
when(core.getInput)
.calledWith('secrets', expect.anything())
.mockReturnValueOnce('secret/data/test secret');
});
it('successfully authenticates', async () => {
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
});
});
it('successfully authenticates', async () => {
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
describe('authenticate with Github OIDC', () => {
beforeAll(async () => {
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
role_type: 'jwt',
bound_audiences: null,
bound_claims: {
iss: 'vault-action',
aud: 'sigstore',
},
user_claim: 'iss',
policies: ['reader']
}
});
})
beforeEach(() => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('method', expect.anything())
.mockReturnValueOnce('jwt');
when(core.getInput)
.calledWith('jwtPrivateKey', expect.anything())
.mockReturnValueOnce('');
when(core.getInput)
.calledWith('secrets', expect.anything())
.mockReturnValueOnce('secret/data/test secret');
});
it('successfully authenticates', async () => {
when(core.getInput)
.calledWith('role', expect.anything())
.mockReturnValueOnce('default');
when(core.getIDToken)
.calledWith(undefined)
.mockReturnValueOnce(defaultGithubJwt);
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
});
it('successfully authenticates with `jwtGithubAudience` set to `sigstore`', async () => {
when(core.getInput)
.calledWith('role', expect.anything())
.mockReturnValueOnce('default-sigstore');
when(core.getInput)
.calledWith('jwtGithubAudience', expect.anything())
.mockReturnValueOnce('sigstore');
when(core.getIDToken)
.calledWith(expect.anything())
.mockReturnValueOnce(mockGithubOIDCResponse('sigstore'));
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
})
it('successfully authenticates as default role without specifying it', async () => {
when(core.getInput)
.calledWith('role', expect.anything())
.mockReturnValueOnce(null);
when(core.getIDToken)
.calledWith(undefined)
.mockReturnValueOnce(defaultGithubJwt);
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
})
});
});

View file

@ -1,54 +1,30 @@
const privateRsaKey = `
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAwavSHLLo7bUSuKX2EKu3YStrNTGdmhku7sAFaeDyi9His1oo
t+wzajWp1rqHaGVk4b5o5z6D7Xhm0zYPhpTTvEd3NyONc9sjVd7sf9rQaBY3QusP
YAdF6j0ydTJGnTeG9N2zhHjdMLR+3F39F9Ry6vddS9w3ibjVERucQtpqGhy5TIWh
ttk3gN3A7dk972+WCQeKUCC6wU6PvAEUPflThQc1hSHldpjVUHQlZkQXl/XHWBzZ
ESwMgiQcAmVL3lkvxmgIYscqWzf8cTHogNrPml9Il89N+2XYcEfXgWLOyzGhQggi
gN9DRHDDE1UWT1foHnmeXAIZPCveKZc/Jp5SASIxaZ+r73mWjVt19GSvEsqtejDR
mMC3jrYCdISrCVsHkbRQq7/yLCi5sJG9p2gD91jCgaBy2tw22nWN/KoHm9p/mhzW
mlWwVJrFTNiP0qccyoa5h1a6WBPt0oCKQk3IAMV5HhVw4DhPx8gvh7qwOZk9vXXA
FyA8bWhOVIFIJ8i8Gq25y7/bukyzqsEPkQ2mKgbQnh3VOUCSugJFodPC/Jf7VQK1
yRpUZH2v7r9cBjHVnUnePGac3Zns7/iRYBvK5cIFUg+X48VXIMKIvs4EA8ee8Vac
Vh8tyR6EuP42BU5fGQlvLC+ZKT165maQv/Vlf52E9W2iFNt3sxB0KFtOkbkCAwEA
AQKCAgBwAwAyuQce9GsvgE0gtzAIcyQ+T8PnLEmIrGZ1JjUhyPJk6PBD78iM1Ry1
pIxMRNhj98yUcgO7hLdz0QCJxenwKyU4LsfRCh0VvSjriZKfoLm1al4qHArDv0E/
pyRQKZ1UYiVBqOXFFZ+JtJJ9BdKxMwAyr9svPEd+7Yki4VAcaiCBsYgmSNthHOBI
sCyyHseX0VSdo1BgHR/kjHs4nMtBVToPFduxDBPTxFkdHKTIrs3smEKzO9bALkJE
4HFQ2CRZjDHNb4N/3pGSplriq6sGjbVel/dyPyU/S03I92zC+KFbn3jVMEunedBZ
jgypNx55Ab4lWNFfi7+iLmfH3ilumsajGSRGVTo0evuBhK5obUudHeXxmvEBa81n
yo38swQFcSF9VOYwLDud6WgryTGLejRspWxbbV1pLp4uBvcDJktzh/fGyboZc2oG
kmrozTenuLEsf85T8o6W9kOe4vNBFngMPjDf9rOw6zMFxO0Iy3d7ag9z0ccGV3zh
66QijkWPXQfaLOaueGCQakS9BI4AJCIIjqv51jo8D86fHOdUwK2RlhVBHW6k+XJo
VGVbeOLNbcOHA0/BU10ZX16F8ZKKBWs0NfNg5O7Vvr6qyAp4zQyUSfXgJHS0De2E
5sLAr8+lemdd/pP/Oi8GHFmo+rwDheH1EYyziA2zm8Zmc7q+QQKCAQEA/LRs4LOJ
XlwDfQE9w5L6geJ/jAKClrGOtP5tzBDjWSZ3xPZeBChXc5U+n2IEyqFU9j2HQlMr
XQl74aEclG/y0371zWm83OEQCEBp2z/VsP7JZuEGNxla1EO6hSZXAZDQrTeOodhZ
6OL6wdIGCQvnFZuiJwCfGItmBBaUjxpRsmyrf9naOM1BwM4PpZUvv18iqoK7YhGz
t9Bs6W/LP2WK3Ze5WShZXXsFxGoWvYJvs8pQYua2TF+SGsnmc8FfmHSFZdu8qhXa
J5XIxjynFweWZqxxCnfS77ejhx1h5s8iwKT1la8VHUJ7Tgcdn98VTcJPRsyYg6TU
xvztwvEfUA7eewKCAQEAxDJUmi3ksP/nMy1Ox7BEnnWKFiVS46g58m9qhF5y8Oib
ypCPt3EvdrXvzjlYJojbXMr0yjvGqiBblDMjewdvxb4CTTAIy3iwOCfHBnzdyNYA
XZjtuD991klQP/BB62CMbCiwC1XBJzN57qxHEgPBwtkQYgSI/M/hQWZehi8JgjW7
PNjDPqYdIck78G8zUQM96u6Y0F+BoQuo/YgKXyeeMhtMiTITN9NpCwa5KdlDunZ0
qlZe1GKnOHmCW0NgeMxNZTRTJXvU7wUigs4isR19GuLuXnHRIwBoydu0UgczCvgh
cGv19uCU/DcoxFF0fGD1Uual1R8KH+0FpN3mnJW0WwKCAQEAxy1Sj70Srcvqd/Gt
g+PqDMvAalNkKHBkoaXUVr6M4yydxCHHMpG1dAWTKT6xtiB4/ei7Hny9NgSOnuVE
yH6AL1DnXnNUB+hgoZBbnxLuVCZOCgecxXr3i0yiy+XPOA2zXIPoqQoEu7mDmZb3
aNP33KEhqooj282rp9dAWpaNBAwBFLFZ/eFSTSxdSs6OptDOWwTVutNnCp996HRU
B3D6hfPbhDl4TmTzw782k0Im1tfEil98GjBN0U2HlX854Mkeh40tZAX7P64gZJdT
v6QcWGrcYjrViFn+yzVOgASNSLf8VXF9O+W1mGelYugLO5HGuG/0WfZmOz0KDdfN
LWW61wKCAQB8qaZMGSEYvmF/iShnhb70GKdXDvwuH3RCcTzzQrgyDvr+qQBIhSit
e0kWdiVmxsrrmSIVZgoDi2/lKOFAiSciNGtt9DmCX/tIky3JF4os1J2C22shCWbB
w++z0Mtx7fULvIavjRuf9vthBiJadfyl/BqGzW7lhIkSbyNci4K1M8L3FJxqsE4O
a7kkOuQWc8LiBh0fObA6ThhgkBJXB+ti1ym4exLvA+vYz7rTtnNshVv35811kgHC
xqJnrtYbq2T6C1dRl+9iuJaHGse8VoppjQv9AsDqRpZOvMVE4cIzFBrbPh4ZcfX4
lGvY4hDr/weiV1/DnWdnhclySnT/xbfFAoIBADjm+PaaMVps8tfgNcLUjOoAQzXo
/ReGOa/Zs7pu3nQp49b+9QBHRXXRrfeJ6NAVAJvgc8PIUsVKQ67yjf1sLHZUSGjc
3wyaAChmMLgl+00kQ7VL4Ls1bEb4Qxho6zoCjvaorN6gcNO8D32Oecux9F9nWinK
zJi1GLoJpVenFT+M4zTWOl3otcVzTDxknFYx9Vul84n2z28GYmw8I0RKcmBKCvHo
q/JyRYl9XunMp/7QDA9IYKTJXqIkiD+ksKtDfWRsy+iLvuVv0z9gA+MlaRTGANpt
ucZsYfhw34I5dpcqrYP4DErpKdYA3nsjx9rNQg0I4Zo7yVCRoiqRHa97GVQ=
MIIEowIBAAKCAQEArcch89X6VuWj/CQtVfaCXUl0Pcv8IJRgICN8X+3zFNrbiTdh
kTtrOhdkbEU5VaW6aQiCXX5+4C1T2sXXXT682XJhIjKepyX3aY50Fh59pLCciwAK
c5wPy3PVMOhup15u9reiQKxps1SNrqVLyZNjha83qbN9IJvQcMnQghAjjPUeGPMa
MMzG1GOnuPOWIiM2kxqRpbugwwTyuepPnakmfkWqVtMIRprPLY6d3liDIUSSRZ7o
6vbmgeF+9U4DyaimKVNngrmi+mW0OnyH1eJYLrJEY9tZaRF8xraMZiOcBcyAt6S/
TS29HttJ6+zlhcWx34fItEZ8jA5gzhTmspOY8QIDAQABAoIBAQCncXT5qnipOmSk
E4fLiNdcY+aplN+/1Lg6v3acSH8s3SUkNkTA1+wd8WRGHv171VCk3BohVD2UbJib
+H3nzwfQzjFh7jyI+kBHaYfZuE+AXNy54rQvaXSeqWIG2i+k/Y0WFSM2BetjbFmI
qqU3+dive4G69sPeo8RYqV1LtZlLu11j1K1sptcmMi75/cFAB6/uURapNLI978sr
cIaOV2BbLs4Yk7ji4YAtpvL+mky9KF56QAsLspBKgsU/Oxy5FkgFORPlaVn9qBFv
cdXBsZOWFnZ2+F/OA55WJeQoyO8E9l5+N8TGKpzXbmkUWyKiqbN+AviZYrK6KPxi
zXS4SH0xAoGBANUDIhoUTM+u6dJze/i/sSe90k6UOBsSxA8Q7rOfksda4F0JEBPl
l4kbfmMVMEeIJrHNaFRE8r/p/J8sQCg4w/wDB5LEdHGaxf3b2Fbai+QHuElrbOXP
vQ/UOaaMQKFJlwlfOHpbDzXM3bMdGwfT6DCCoQyrAvVRE/x8veXJ9Fl/AoGBANDZ
B+sjiVbIjbWA9debx/QeEjoLB68Pi8DleCOgbtF7c4jJPPDRLz0fce16ePVFpiw4
Mu1E8QQdMTxWY+Y4ERNPwXj//PhD5xDfWYdRJ6IgKFK1bqKIwm7BmJn3WLD2JH2J
mLR0Wfa7M8OmWBbAS8fe5NvubeqERmbMs+f+eeWPAoGAR4Cgvt5XllNhm8o2MB6w
qeV3JfdtCfF3rJMDfXowPAkOTUyQgA1Om7CF8V6YcTqLup13yunGDpPNv+SLuLSt
XPfrX+HgMI5Crd9RNH5x/N52hvavfEkKbrjPjU+BFmLsdzHmdHQCnA2j0c8QVsVU
KIyA4Q66lHxd2CBLYkozYqMCgYAxiZkoPBiifhWm3LzzdF78V3mpTN54tq5Ghed2
Q+KlS6v+4QTUdjnHPMMwOiGgw/GDgZ0KzJSCjk4UasVtYRUjyIIyqj2dwbV4OhIp
V6WX/hqya5ifcuLzlHYW5yWha/EB2fZfr017ibHgkX9Jfjk7YnJUfHyT6OYuEhYG
TEUrnwKBgHztVFIi0vwELwhVCr37pFzKTTmm7G+SYH2hnvL2o3eCNAxSoE8/+vuP
qHxd6MME9OqeuY9s3eimsTuhSxnMN348v3Tr/FnA/VIeEMyDZyPqso1pFylpUnHn
67hv/xqXT3+/MHq6AgVWXjwTgn8XNRDfXmHrBIztq6Kzo/kLmthY
-----END RSA PRIVATE KEY-----
`;
@ -56,22 +32,18 @@ const privateRsaKeyBase64 = Buffer.from(privateRsaKey).toString('base64');
const publicRsaKey = `
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwavSHLLo7bUSuKX2EKu3
YStrNTGdmhku7sAFaeDyi9His1oot+wzajWp1rqHaGVk4b5o5z6D7Xhm0zYPhpTT
vEd3NyONc9sjVd7sf9rQaBY3QusPYAdF6j0ydTJGnTeG9N2zhHjdMLR+3F39F9Ry
6vddS9w3ibjVERucQtpqGhy5TIWhttk3gN3A7dk972+WCQeKUCC6wU6PvAEUPflT
hQc1hSHldpjVUHQlZkQXl/XHWBzZESwMgiQcAmVL3lkvxmgIYscqWzf8cTHogNrP
ml9Il89N+2XYcEfXgWLOyzGhQggigN9DRHDDE1UWT1foHnmeXAIZPCveKZc/Jp5S
ASIxaZ+r73mWjVt19GSvEsqtejDRmMC3jrYCdISrCVsHkbRQq7/yLCi5sJG9p2gD
91jCgaBy2tw22nWN/KoHm9p/mhzWmlWwVJrFTNiP0qccyoa5h1a6WBPt0oCKQk3I
AMV5HhVw4DhPx8gvh7qwOZk9vXXAFyA8bWhOVIFIJ8i8Gq25y7/bukyzqsEPkQ2m
KgbQnh3VOUCSugJFodPC/Jf7VQK1yRpUZH2v7r9cBjHVnUnePGac3Zns7/iRYBvK
5cIFUg+X48VXIMKIvs4EA8ee8VacVh8tyR6EuP42BU5fGQlvLC+ZKT165maQv/Vl
f52E9W2iFNt3sxB0KFtOkbkCAwEAAQ==
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcch89X6VuWj/CQtVfaC
XUl0Pcv8IJRgICN8X+3zFNrbiTdhkTtrOhdkbEU5VaW6aQiCXX5+4C1T2sXXXT68
2XJhIjKepyX3aY50Fh59pLCciwAKc5wPy3PVMOhup15u9reiQKxps1SNrqVLyZNj
ha83qbN9IJvQcMnQghAjjPUeGPMaMMzG1GOnuPOWIiM2kxqRpbugwwTyuepPnakm
fkWqVtMIRprPLY6d3liDIUSSRZ7o6vbmgeF+9U4DyaimKVNngrmi+mW0OnyH1eJY
LrJEY9tZaRF8xraMZiOcBcyAt6S/TS29HttJ6+zlhcWx34fItEZ8jA5gzhTmspOY
8QIDAQAB
-----END PUBLIC KEY-----
`;
module.exports = {
privateRsaKey,
privateRsaKeyBase64,
publicRsaKey
};

View file

@ -1,20 +1,21 @@
const got = require('got');
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
(async () => {
try {
// Verify Connection
await got(`http://${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
});
await got(`http://${vaultUrl}/v1/secret/data/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -26,7 +27,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/secret/data/nested/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
data: {
@ -38,7 +39,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/sys/mounts/my-secret`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
type: 'kv'
@ -48,7 +49,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/my-secret/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
altSecret: 'CUSTOMSECRET',
@ -58,7 +59,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/my-secret/nested/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
otherAltSecret: 'OTHERCUSTOMSECRET',
@ -68,7 +69,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/cubbyhole/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
json: {
foo: 'bar',

View file

@ -8,6 +8,7 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8201'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('integration', () => {
beforeAll(async () => {
@ -15,7 +16,7 @@ describe('integration', () => {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
});
@ -43,15 +44,15 @@ describe('integration', () => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url')
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('token')
.mockReturnValueOnce('testtoken');
.calledWith('token', expect.anything())
.mockReturnValueOnce(vaultToken);
when(core.getInput)
.calledWith('namespace')
.calledWith('namespace', expect.anything())
.mockReturnValueOnce('ns1');
});
@ -151,7 +152,7 @@ describe('authenticate with approle', () => {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
},
});
@ -169,7 +170,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/sys/auth/approle`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2',
},
json: {
@ -189,7 +190,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/sys/policies/acl/test`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2',
},
json: {
@ -202,7 +203,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/auth/approle/role/my-role`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2',
},
json: {
@ -213,7 +214,7 @@ describe('authenticate with approle', () => {
// Get role-id
const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, {
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2',
},
responseType: 'json',
@ -224,7 +225,7 @@ describe('authenticate with approle', () => {
const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2',
},
responseType: 'json',
@ -243,16 +244,16 @@ describe('authenticate with approle', () => {
.calledWith('method', expect.anything())
.mockReturnValueOnce('approle');
when(core.getInput)
.calledWith('roleId')
.calledWith('roleId', expect.anything())
.mockReturnValueOnce(roleId);
when(core.getInput)
.calledWith('secretId')
.calledWith('secretId', expect.anything())
.mockReturnValueOnce(secretId);
when(core.getInput)
.calledWith('url')
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
when(core.getInput)
.calledWith('namespace')
.calledWith('namespace', expect.anything())
.mockReturnValueOnce('ns2');
});
@ -270,7 +271,7 @@ async function enableNamespace(name) {
await got(`${vaultUrl}/v1/sys/namespaces/${name}`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
}
});
} catch (error) {
@ -288,7 +289,7 @@ async function enableEngine(path, namespace, version) {
await got(`${vaultUrl}/v1/sys/mounts/${path}`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': namespace,
},
json: { type: 'kv', config: {}, options: { version }, generate_signing_key: true },
@ -309,7 +310,7 @@ async function writeSecret(engine, path, namespace, version, data) {
await got(`${vaultUrl}/v1/${secretPath}`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
'X-Vault-Token': vaultToken,
'X-Vault-Namespace': namespace,
},
json: secretPayload
@ -318,18 +319,6 @@ async function writeSecret(engine, path, namespace, version, data) {
function mockInput(secrets) {
when(core.getInput)
.calledWith('secrets')
.calledWith('secrets', expect.anything())
.mockReturnValueOnce(secrets);
}
function mockEngineName(name) {
when(core.getInput)
.calledWith('path')
.mockReturnValueOnce(name);
}
function mockVersion(version) {
when(core.getInput)
.calledWith('kv-version')
.mockReturnValueOnce(version);
}

22505
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,16 +15,6 @@
"src/**/*",
"dist/**/*"
],
"release": {
"branch": "master",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/npm"
],
"ci": false
},
"repository": {
"type": "git",
"url": "git+https://github.com/hashicorp/vault-action.git"
@ -44,20 +34,18 @@
},
"homepage": "https://github.com/hashicorp/vault-action#readme",
"dependencies": {
"got": "^11.5.1",
"jsonata": "^1.8.2",
"jsrsasign": "^10.1.10"
"got": "^11.8.5",
"jsonata": "^2.0.2",
"jsrsasign": "^10.6.1"
},
"peerDependencies": {
"@actions/core": ">=1 <2"
},
"devDependencies": {
"@actions/core": "^1.2.3",
"@types/got": "^9.6.11",
"@types/jest": "^26.0.13",
"@zeit/ncc": "^0.22.3",
"jest": "^26.4.2",
"jest-when": "^2.7.2",
"semantic-release": "^17.1.1"
"@actions/core": "^1.10.0",
"@vercel/ncc": "^0.36.1",
"jest": "^29.4.3",
"jest-when": "^3.5.2",
"mock-http-server": "^1.4.5"
}
}

View file

@ -25,6 +25,7 @@ module.exports.normalizeOutputKey = normalizeOutputKey;
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes'];
const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
async function exportSecrets() {
const vaultUrl = core.getInput('url', { required: true });
@ -33,9 +34,11 @@ async function exportSecrets() {
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
const exportToken = (core.getInput('exportToken', { required: false }) || 'false').toLowerCase() != 'false';
const secretsInput = core.getInput('secrets', { required: true });
const secretsInput = core.getInput('secrets', { required: false });
const secretRequests = parseSecretsInput(secretsInput);
const secretEncodingType = core.getInput('secretEncodingType', { required: false });
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
const authPayload = core.getInput('authPayload', { required: false });
if (!AUTH_METHODS.includes(vaultMethod) && !authPayload) {
@ -45,7 +48,15 @@ async function exportSecrets() {
const defaultOptions = {
prefixUrl: vaultUrl,
headers: {},
https: {}
https: {},
retry: {
statusCodes: [
...got.defaults.options.retry.statusCodes,
// Vault returns 412 when the token in use hasn't yet been replicated
// to the performance replica queried. See issue #332.
412,
]
}
}
const tlsSkipVerify = (core.getInput('tlsSkipVerify', { required: false }) || 'false').toLowerCase() != 'false';
@ -92,11 +103,23 @@ async function exportSecrets() {
const results = await getSecrets(requests, client);
for (const result of results) {
const { value, request, cachedResponse } = result;
// Output the result
var value = result.value;
const request = result.request;
const cachedResponse = result.cachedResponse;
if (cachedResponse) {
core.debug(' using cached response');
}
// if a secret is encoded, decode it
if (ENCODING_TYPES.includes(secretEncodingType)) {
value = Buffer.from(value, secretEncodingType).toString();
}
for (const line of value.replace(/\r/g, '').split('\n')) {
if (line.length > 0) {
command.issue('add-mask', line);
@ -111,7 +134,7 @@ async function exportSecrets() {
};
module.exports.exportSecrets = exportSecrets;
/** @typedef {Object} SecretRequest
/** @typedef {Object} SecretRequest
* @property {string} path
* @property {string} envVarName
* @property {string} outputVarName
@ -123,6 +146,10 @@ module.exports.exportSecrets = exportSecrets;
* @param {string} secretsInput
*/
function parseSecretsInput(secretsInput) {
if (!secretsInput) {
return []
}
const secrets = secretsInput
.split(';')
.filter(key => !!key)

View file

@ -8,7 +8,6 @@ const got = require('got');
const {
exportSecrets,
parseSecretsInput,
parseResponse,
parseHeadersInput
} = require('./action');
@ -94,7 +93,7 @@ describe('parseSecretsInput', () => {
describe('parseHeaders', () => {
it('parses simple header', () => {
when(core.getInput)
.calledWith('extraHeaders')
.calledWith('extraHeaders', undefined)
.mockReturnValueOnce('TEST: 1');
const result = parseHeadersInput('extraHeaders');
expect(Array.from(result)).toContainEqual(['test', '1']);
@ -102,7 +101,7 @@ describe('parseHeaders', () => {
it('parses simple header with whitespace', () => {
when(core.getInput)
.calledWith('extraHeaders')
.calledWith('extraHeaders', undefined)
.mockReturnValueOnce(`
TEST: 1
`);
@ -112,7 +111,7 @@ describe('parseHeaders', () => {
it('parses multiple headers', () => {
when(core.getInput)
.calledWith('extraHeaders')
.calledWith('extraHeaders', undefined)
.mockReturnValueOnce(`
TEST: 1
FOO: bAr
@ -124,7 +123,7 @@ describe('parseHeaders', () => {
it('parses null response', () => {
when(core.getInput)
.calledWith('extraHeaders')
.calledWith('extraHeaders', undefined)
.mockReturnValueOnce(null);
const result = parseHeadersInput('extraHeaders');
expect(Array.from(result)).toHaveLength(0);
@ -136,29 +135,29 @@ describe('exportSecrets', () => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('url')
.calledWith('url', expect.anything())
.mockReturnValueOnce('http://vault:8200');
when(core.getInput)
.calledWith('token')
.calledWith('token', expect.anything())
.mockReturnValueOnce('EXAMPLE');
});
function mockInput(key) {
when(core.getInput)
.calledWith('secrets')
.calledWith('secrets', expect.anything())
.mockReturnValueOnce(key);
}
function mockVersion(version) {
when(core.getInput)
.calledWith('kv-version')
.calledWith('kv-version', expect.anything())
.mockReturnValueOnce(version);
}
function mockExtraHeaders(headerString) {
when(core.getInput)
.calledWith('extraHeaders')
.calledWith('extraHeaders', expect.anything())
.mockReturnValueOnce(headerString);
}
@ -181,10 +180,16 @@ describe('exportSecrets', () => {
function mockExportToken(doExport) {
when(core.getInput)
.calledWith('exportToken')
.calledWith('exportToken', expect.anything())
.mockReturnValueOnce(doExport);
}
function mockEncodeType(doEncode) {
when(core.getInput)
.calledWith('secretEncodingType', expect.anything())
.mockReturnValueOnce(doEncode);
}
it('simple secret retrieval', async () => {
mockInput('test key');
mockVaultData({
@ -197,6 +202,19 @@ describe('exportSecrets', () => {
expect(core.setOutput).toBeCalledWith('key', '1');
});
it('encoded secret retrieval', async () => {
mockInput('test key');
mockVaultData({
key: 'MQ=='
});
mockEncodeType('base64');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', '1');
expect(core.setOutput).toBeCalledWith('key', '1');
});
it('intl secret retrieval', async () => {
mockInput('测试 测试');
mockVaultData({
@ -331,4 +349,13 @@ with blank lines
expect(command.issue).toBeCalledWith('add-mask', 'with blank lines');
expect(core.setOutput).toBeCalledWith('key', multiLineString);
})
it('export only Vault token, no secrets', async () => {
mockExportToken("true")
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('VAULT_TOKEN', 'EXAMPLE');
})
});

View file

@ -2,6 +2,7 @@
const core = require('@actions/core');
const rsasign = require('jsrsasign');
const fs = require('fs');
const { default: got } = require('got');
const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token'
/***
@ -23,12 +24,21 @@ async function retrieveToken(method, client) {
return await getClientToken(client, method, path, { token: githubToken });
}
case 'jwt': {
const role = core.getInput('role', { required: true });
const privateKeyRaw = core.getInput('jwtPrivateKey', { required: true });
/** @type {string} */
let jwt;
const role = core.getInput('role', { required: false });
const privateKeyRaw = core.getInput('jwtPrivateKey', { required: false });
const privateKey = Buffer.from(privateKeyRaw, 'base64').toString();
const keyPassword = core.getInput('jwtKeyPassword', { required: false });
const tokenTtl = core.getInput('jwtTtl', { required: false }) || '3600'; // 1 hour
const jwt = generateJwt(privateKey, keyPassword, Number(tokenTtl));
const githubAudience = core.getInput('jwtGithubAudience', { required: false });
if (!privateKey) {
jwt = await core.getIDToken(githubAudience)
} else {
jwt = generateJwt(privateKey, keyPassword, Number(tokenTtl));
}
return await getClientToken(client, method, path, { jwt: jwt, role: role });
}
case 'kubernetes': {
@ -100,7 +110,16 @@ async function getClientToken(client, method, path, payload) {
core.debug(`Retrieving Vault Token from v1/auth/${path}/login endpoint`);
/** @type {import('got').Response<VaultLoginResponse>} */
const response = await client.post(`v1/auth/${path}/login`, options);
let response;
try {
response = await client.post(`v1/auth/${path}/login`, options);
} catch (err) {
if (err instanceof got.HTTPError) {
throw Error(`failed to retrieve vault token. code: ${err.code}, message: ${err.message}, vaultResponse: ${JSON.stringify(err.response.body)}`)
} else {
throw err
}
}
if (response && response.body && response.body.auth && response.body.auth.client_token) {
core.debug('✔ Vault Token successfully retrieved');

View file

@ -1,7 +1,12 @@
jest.mock('got');
jest.mock('@actions/core');
jest.mock('@actions/core/lib/command');
jest.mock("fs")
jest.mock('fs', () => ({
stat: jest.fn().mockResolvedValue(null),
promises: {
access: jest.fn().mockResolvedValue(null),
}
}));
const core = require('@actions/core');
const got = require('got');
@ -16,7 +21,7 @@ const {
function mockInput(name, key) {
when(core.getInput)
.calledWith(name)
.calledWith(name, expect.anything())
.mockReturnValueOnce(key);
}

69
src/retries.test.js Normal file
View file

@ -0,0 +1,69 @@
jest.mock('@actions/core');
const core = require('@actions/core');
const ServerMock = require("mock-http-server");
const { exportSecrets } = require("./action");
const { when } = require('jest-when');
describe('exportSecrets retries', () => {
var server = new ServerMock({ host: "127.0.0.1", port: 0 });
var calls = 0;
beforeEach((done) => {
calls = 0;
jest.resetAllMocks();
when(core.getInput)
.calledWith('token', expect.anything())
.mockReturnValueOnce('EXAMPLE');
when(core.getInput)
.calledWith('secrets', expect.anything())
.mockReturnValueOnce("kv/mysecret key");
server.start(() => {
expect(server.getHttpPort()).not.toBeNull();
when(core.getInput)
.calledWith('url', expect.anything())
.mockReturnValueOnce('http://127.0.0.1:' + server.getHttpPort());
done();
});
});
afterEach((done) => {
server.stop(done);
});
function mockStatusCodes(statusCodes) {
server.on({
path: '/v1/kv/mysecret',
reply: {
status: function() {
let status = statusCodes[calls];
calls += 1;
return status;
},
headers: { "content-type": "application/json" },
body: function() {
return JSON.stringify({ data: {"key": "value"} })
}
}
});
}
it('retries on 412 status code', (done) => {
mockStatusCodes([412, 200])
exportSecrets().then(() => {
expect(calls).toEqual(2);
done();
});
});
it('retries on 500 status code', (done) => {
mockStatusCodes([500, 200])
exportSecrets().then(() => {
expect(calls).toEqual(2);
done();
});
});
});

View file

@ -34,9 +34,17 @@ async function getSecrets(secretRequests, client) {
body = responseCache.get(requestPath);
cachedResponse = true;
} else {
const result = await client.get(requestPath);
body = result.body;
responseCache.set(requestPath, body);
try {
const result = await client.get(requestPath);
body = result.body;
responseCache.set(requestPath, body);
} catch (error) {
const {response} = error;
if (response.statusCode === 404) {
throw Error(`Unable to retrieve result for "${path}" because it was not found: ${response.body.trim()}`)
}
throw error
}
}
if (selector == wildcard) {
@ -96,7 +104,8 @@ async function getSecrets(secretRequests, client) {
selector = "data." + selector
}
const value = selectData(body, selector);
const value = await selectData(body, selector);
results.push({
request: secretRequest,
value,
@ -113,12 +122,12 @@ async function getSecrets(secretRequests, client) {
* @param {object} data
* @param {string} selector
*/
function selectData(data, selector) {
async function selectData(data, selector) {
const ata = jsonata(selector);
let result = JSON.stringify(ata.evaluate(data));
let result = JSON.stringify(await ata.evaluate(data));
// Compat for custom engines
if (!result && ((ata.ast().type === "path" && ata.ast()['steps'].length === 1) || ata.ast().type === "string") && selector !== 'data' && 'data' in data) {
result = JSON.stringify(jsonata(`data.${selector}`).evaluate(data));
result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
} else if (!result) {
throw Error(`Unable to retrieve result for ${selector}. No match data was found. Double check your Key or Selector.`);
}