mirror of
https://github.com/hashicorp/vault-action.git
synced 2026-07-26 00:13:16 +03:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 867290158a | |||
| 790b5eac34 | |||
| 074f580c7d | |||
| 14c5ac9697 | |||
| ee41aa2fcf | |||
| 77efb36ae3 | |||
| a727ce205a | |||
| d1720f055e | |||
| 92626383ce | |||
| 9c2d817b85 | |||
| b477844b5f | |||
| 9f522b8598 | |||
| efab57ede0 | |||
| d523bb05b2 | |||
| 11845b19f6 | |||
| 7a6258bb0b | |||
| a0b66b1cc3 | |||
| c616aba63e | |||
| e3d5714d59 | |||
| 00bce0da9c | |||
| 6853090cd9 | |||
| 45dc5344f1 | |||
| 2fb925f14c | |||
| caba6efd0e | |||
| affa6f04da | |||
| 4727f0b168 | |||
| 86c7f837eb | |||
| 375956aa33 | |||
| 1328cd9fa9 | |||
| d4437ee96c | |||
| a5f6c67fe1 | |||
| d9197ec2d2 | |||
| cb841f2c86 | |||
| 0010502df7 | |||
| 65d7a12a80 | |||
| b138504969 | |||
| e926631bb2 | |||
| 5213b69445 | |||
| 357cb9c034 | |||
| b9f4d16071 | |||
| 62aa8bb4c4 | |||
| ec2980c187 | |||
| 166100bd2a | |||
| dc4f72debb | |||
| a87a71c289 |
@@ -3,21 +3,29 @@ name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
assignees: RichiCoder1
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
## Vault server version
|
||||
v0.0.0
|
||||
|
||||
## vault-action version
|
||||
v0.0.0
|
||||
|
||||
## Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
## To Reproduce
|
||||
The yaml of the `vault-action` step, with any sensitive information masked or removed.
|
||||
|
||||
**Expected behavior**
|
||||
## Expected behavior
|
||||
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/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.
|
||||
## Log Output
|
||||
For the most verbose logs, add a secret called
|
||||
[`ACTIONS_STEP_DEBUG`](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md)
|
||||
with the value `true`. 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**
|
||||
## Additional context
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -3,18 +3,17 @@ name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEAT] "
|
||||
labels: enhancement
|
||||
assignees: RichiCoder1
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
## Is your feature request related to a problem? Please describe.
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
## Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
## Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
## Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
### Description
|
||||
<!--- Description of the change. For example: This PR updates ABC resource so that we can XYZ --->
|
||||
|
||||
|
||||
<!--- If your PR fully resolves and should automatically close the linked issue, use Closes. Otherwise, use Relates --->
|
||||
Relates OR Closes #0000
|
||||
|
||||
|
||||
### Checklist
|
||||
- [ ] Added [CHANGELOG](https://github.com/hashicorp/vault-action/blob/master/CHANGELOG.md) entry (only for user-facing changes)
|
||||
|
||||
|
||||
### Community Note
|
||||
|
||||
* Please vote on this pull request by adding a 👍
|
||||
[reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)
|
||||
to the original pull request comment to help the community and maintainers
|
||||
prioritize this request
|
||||
* Please do not leave "+1" comments, they generate extra noise for pull request
|
||||
followers and do not help prioritize the request
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Lint GitHub Actions Workflows
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
- name: "Lint workflow files"
|
||||
uses: docker://docker.mirror.hashicorp.services/rhysd/actionlint:latest
|
||||
with:
|
||||
# Ignore actionlint errors from strict typing for outputs that we use
|
||||
# in our e2e tests.
|
||||
# This error occurs because vault-action's outputs are dynamic but
|
||||
# actionlint expects action.yml to define them.
|
||||
args: >
|
||||
-ignore "property \"othersecret\" is not defined in object type"
|
||||
-ignore "property \"jsonstring\" is not defined in object type"
|
||||
-ignore "property \"jsonstringmultiline\" is not defined in object type"
|
||||
+35
-61
@@ -1,9 +1,5 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -11,12 +7,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
node-version: '20.12.2'
|
||||
|
||||
- name: Setup NPM Cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -40,15 +34,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
node-version: '20.12.2'
|
||||
|
||||
- name: Setup NPM Cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -76,8 +68,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault-enterprise
|
||||
@@ -86,7 +76,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
node-version: '20.12.2'
|
||||
|
||||
- name: Setup NPM Cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -114,15 +104,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
node-version: '20.12.2'
|
||||
|
||||
- name: Setup NPM Cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -145,7 +133,7 @@ jobs:
|
||||
VAULT_PORT: 8200
|
||||
|
||||
- name: Test Vault Action (default KV V2)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
id: kv-secrets
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
@@ -156,7 +144,7 @@ jobs:
|
||||
secret/data/nested/test otherSecret ;
|
||||
|
||||
- name: Test Vault Action (default KV V1)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
token: testtoken
|
||||
@@ -166,7 +154,7 @@ jobs:
|
||||
my-secret/nested/test otherAltSecret ;
|
||||
|
||||
- name: Test Vault Action (cubbyhole)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
token: testtoken
|
||||
@@ -174,40 +162,52 @@ jobs:
|
||||
/cubbyhole/test foo ;
|
||||
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
||||
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
# The ordering of these two Test Vault Action Overwrites Env Vars In Subsequent Action steps matters
|
||||
# They should come before the Verify Vault Action Outputs step
|
||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: http://localhost:8200/
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test secret | SUBSEQUENT_TEST_SECRET;
|
||||
|
||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 2/2)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: http://localhost:8200/
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/subsequent-test secret | SUBSEQUENT_TEST_SECRET;
|
||||
|
||||
- name: Test JSON Secrets
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test-json-data jsonData;
|
||||
secret/data/test-json-string jsonString;
|
||||
secret/data/test-json-string-multiline jsonStringMultiline;
|
||||
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
|
||||
e2e-tls:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Run docker-compose
|
||||
run: docker-compose up -d vault-tls
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '16.14.0'
|
||||
node-version: '20.12.2'
|
||||
|
||||
- name: Setup NPM Cache
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||
@@ -233,8 +233,8 @@ jobs:
|
||||
VAULT_CLIENT_KEY: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||
|
||||
- name: Test Vault Action (default KV V2)
|
||||
uses: ./
|
||||
id: kv-secrets
|
||||
uses: hashicorp/vault-action@main
|
||||
id: kv-secrets-tls
|
||||
with:
|
||||
url: https://localhost:8200
|
||||
token: ${{ env.VAULT_TOKEN }}
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
secret/data/nested/test otherSecret ;
|
||||
|
||||
- name: Test Vault Action (tlsSkipVerify)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: https://localhost:8200
|
||||
token: ${{ env.VAULT_TOKEN }}
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
secret/data/tlsSkipVerify skip ;
|
||||
|
||||
- name: Test Vault Action (default KV V1)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: https://localhost:8200
|
||||
token: ${{ env.VAULT_TOKEN }}
|
||||
@@ -271,7 +271,7 @@ jobs:
|
||||
my-secret/nested/test otherAltSecret ;
|
||||
|
||||
- name: Test Vault Action (cubbyhole)
|
||||
uses: ./
|
||||
uses: hashicorp/vault-action@main
|
||||
with:
|
||||
url: https://localhost:8200
|
||||
token: ${{ env.VAULT_TOKEN }}
|
||||
@@ -285,30 +285,4 @@ jobs:
|
||||
- name: Verify Vault Action Outputs
|
||||
run: npm run test:integration:e2e-tls
|
||||
env:
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
||||
|
||||
# Removing publish step for now.
|
||||
# publish:
|
||||
# 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@v3
|
||||
# with:
|
||||
# node-version: '16.14.0'
|
||||
# - name: setup npm cache
|
||||
# uses: actions/cache@v1
|
||||
# with:
|
||||
# path: ~/.npm
|
||||
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-node-
|
||||
# - name: npm install
|
||||
# run: npm ci
|
||||
# - name: release
|
||||
# if: success() && endsWith(github.ref, 'main')
|
||||
# run: npx semantic-release
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets-tls.outputs.otherSecret }}
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
|
||||
JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
|
||||
with:
|
||||
teams-array: '["applications-eco"]'
|
||||
teams-array: '["ecosystem", "applications-eco"]'
|
||||
|
||||
@@ -1,24 +1,73 @@
|
||||
# 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'
|
||||
# To run this locally with act use:
|
||||
# act workflow_dispatch -j local-test
|
||||
#
|
||||
# If you have permissions, you can run this workflow via the GitHub UI.
|
||||
# Otherwise, use 'on: push' instead of 'on: workflow_dispatch'.
|
||||
|
||||
# Don't forget to revert the file changes and invalidate any tokens that were committed before opening a pull-request
|
||||
on: local-test
|
||||
# Don't forget to revert the file changes and invalidate any tokens that were
|
||||
# committed before opening a pull request.
|
||||
on: workflow_dispatch
|
||||
|
||||
name: local-test
|
||||
|
||||
jobs:
|
||||
build:
|
||||
local-test:
|
||||
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;
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
|
||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||
with:
|
||||
node-version: '20.9.0'
|
||||
|
||||
- name: NPM Install
|
||||
run: npm ci
|
||||
|
||||
- name: NPM Build
|
||||
run: npm run build
|
||||
|
||||
- name: Setup Vault
|
||||
run: node ./integrationTests/e2e/setup.js
|
||||
env:
|
||||
VAULT_HOST: localhost
|
||||
VAULT_PORT: 8200
|
||||
|
||||
- name: Import Secrets
|
||||
id: import-secrets
|
||||
# use the local changes
|
||||
uses: ./
|
||||
# run against a specific version of vault-action
|
||||
# uses: hashicorp/vault-action@v2.1.2
|
||||
with:
|
||||
url: http://localhost:8200
|
||||
method: token
|
||||
token: testtoken
|
||||
secrets: |
|
||||
secret/data/test-json-string jsonString;
|
||||
secret/data/test-json-data jsonData;
|
||||
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: "foobar"
|
||||
script: |
|
||||
const { JSONSTRING, JSONDATA } = process.env
|
||||
|
||||
console.log(`string ${JSONSTRING}`)
|
||||
console.log(`data ${JSONDATA}`)
|
||||
const str = JSONDATA
|
||||
|
||||
let valid = true
|
||||
try {
|
||||
JSON.parse(str)
|
||||
} catch (e) {
|
||||
valid = false
|
||||
}
|
||||
if (valid) {
|
||||
console.log("valid json")
|
||||
} else {
|
||||
console.log("not valid json")
|
||||
}
|
||||
|
||||
@@ -59,3 +59,6 @@ typings/
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# GoLand IDE project files
|
||||
.idea
|
||||
|
||||
@@ -1,5 +1,82 @@
|
||||
## Unreleased
|
||||
|
||||
Improvements:
|
||||
|
||||
* chore(deps): bump jsonata from 2.0.3 to 2.0.4 [GH-531](https://github.com/hashicorp/vault-action/pull/531)
|
||||
* chore(deps): bump got from 11.8.6 to 14.2.1 [GH-533](https://github.com/hashicorp/vault-action/pull/533)
|
||||
* convert project from common js to an esm module [GH-533](https://github.com/hashicorp/vault-action/pull/533)
|
||||
|
||||
Features:
|
||||
|
||||
* `secretId` is no longer required for approle to support advanced use cases like machine login when `bind_secret_id` is false. [GH-522](https://github.com/hashicorp/vault-action/pull/522)
|
||||
|
||||
## 3.0.0 (February 15, 2024)
|
||||
|
||||
Improvements:
|
||||
|
||||
* Bump node runtime from node16 to node20 [GH-529](https://github.com/hashicorp/vault-action/pull/529)
|
||||
|
||||
## 2.8.1 (February 15, 2024)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Revert [GH-509](https://github.com/hashicorp/vault-action/pull/509) which made a backwards incompatible bump of the node runtime from node16 to node20 [GH-527](https://github.com/hashicorp/vault-action/pull/527)
|
||||
|
||||
## 2.8.0 (February 1, 2024)
|
||||
|
||||
Features:
|
||||
|
||||
* Add `ignoreNotFound` input (default: false) to prevent the action from failing when a secret does not exist [GH-518](https://github.com/hashicorp/vault-action/pull/518)
|
||||
|
||||
Improvements:
|
||||
|
||||
* bump jsrsasign from 10.8.6 to 11.0.0 [GH-513](https://github.com/hashicorp/vault-action/pull/513)
|
||||
* bump @actions/core from 1.10.0 to 1.10.1 [GH-489](https://github.com/hashicorp/vault-action/pull/489)
|
||||
* bump jest-when from 3.5.2 to 3.6.0 [GH-484](https://github.com/hashicorp/vault-action/pull/484)
|
||||
* bump jest from 29.5.0 to 29.7.0 [GH-490](https://github.com/hashicorp/vault-action/pull/490)
|
||||
* bump @vercel/ncc from 0.36.1 to 0.38.1 [GH-503](https://github.com/hashicorp/vault-action/pull/503)
|
||||
|
||||
## 2.7.5 (January 30, 2024)
|
||||
|
||||
Improvements:
|
||||
|
||||
* Bump node runtime from node16 to node20 [GH-509](https://github.com/hashicorp/vault-action/pull/509)
|
||||
* Bump got from 11.8.5 to 11.8.6 [GH-492](https://github.com/hashicorp/vault-action/pull/492)
|
||||
|
||||
## 2.7.4 (October 26, 2023)
|
||||
|
||||
Features:
|
||||
|
||||
* Add ability to specify a wildcard for the key name to get all keys in the path [GH-488](https://github.com/hashicorp/vault-action/pull/488)
|
||||
|
||||
## 2.7.3 (July 13, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Revert to the handling of secrets in JSON format since v2.1.2 [GH-478](https://github.com/hashicorp/vault-action/pull/478)
|
||||
|
||||
## 2.7.2 (July 6, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Fix a regression that broke support for secrets in JSON format [GH-473](https://github.com/hashicorp/vault-action/pull/473)
|
||||
|
||||
## 2.7.1 (July 3, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Revert [GH-466](https://github.com/hashicorp/vault-action/pull/466) which caused a regression in secrets stored as JSON strings [GH-471](https://github.com/hashicorp/vault-action/pull/471)
|
||||
|
||||
## 2.7.0 (June 21, 2023)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Fix a regression that broke support for secrets in JSON format [GH-466](https://github.com/hashicorp/vault-action/pull/466)
|
||||
|
||||
Improvements:
|
||||
|
||||
* Fix a warning about outputToken being an unexpected input [GH-461](https://github.com/hashicorp/vault-action/pull/461)
|
||||
|
||||
## 2.6.0 (June 7, 2023)
|
||||
|
||||
Features:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.PHONY: local-test
|
||||
local-test:
|
||||
docker compose down; docker-compose up -d vault && act workflow_dispatch -j local-test
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
A helper action for easily pulling secrets from HashiCorp Vault™.
|
||||
|
||||
Note: The Vault Github Action is a read-only action, and in general
|
||||
is not meant to modify Vault’s state.
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [Vault GitHub Action](#vault-github-action)
|
||||
@@ -22,6 +25,7 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
|
||||
- [Userpass](#userpass)
|
||||
- [Ldap](#ldap)
|
||||
- [Other Auth Methods](#other-auth-methods)
|
||||
- [Custom Path](#custom-path-name)
|
||||
- [Key Syntax](#key-syntax)
|
||||
- [Simple Key](#simple-key)
|
||||
- [Set Output Variable Name](#set-output-variable-name)
|
||||
@@ -76,7 +80,7 @@ For example, a common pattern is to save all the secrets in a JSON file:
|
||||
- name: Step following 'Import Secrets'
|
||||
run: |
|
||||
touch secrets.json
|
||||
echo "${{ toJson(steps.import-secrets.outputs) }}" >> secrets.json
|
||||
echo '${{ toJson(steps.import-secrets.outputs) }}' >> secrets.json
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -297,6 +301,20 @@ with:
|
||||
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.
|
||||
|
||||
### Custom Path Name
|
||||
|
||||
Auth methods at custom path names can be configured using the [`path`](#path) parameter
|
||||
|
||||
```yaml
|
||||
with:
|
||||
url: https://vault.mycompany.com:8200
|
||||
caCertificate: ${{ secrets.VAULT_CA_CERT }}
|
||||
path: my-custom-path
|
||||
method: userpass
|
||||
username: ${{ secrets.VAULT_USERNAME }}
|
||||
password: ${{ secrets.VAULT_PASSWORD }}
|
||||
```
|
||||
|
||||
## Key Syntax
|
||||
|
||||
The `secrets` parameter is a set of multiple secret requests separated by the `;` character.
|
||||
@@ -373,6 +391,13 @@ with:
|
||||
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
|
||||
```
|
||||
You can specify a wildcard * for the key name to get all keys in the path. If you provide an output name with the wildcard, the name will be prepended to the key name:
|
||||
|
||||
```yaml
|
||||
with:
|
||||
secrets: |
|
||||
secret/data/ci/aws * | MYAPP_ ;
|
||||
```
|
||||
|
||||
## Other Secret Engines
|
||||
|
||||
@@ -457,34 +482,179 @@ steps:
|
||||
|
||||
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 | | |
|
||||
| `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 | | |
|
||||
| `path` | Custom vault path, if the auth method was enabled at a different path | | |
|
||||
| `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 | | |
|
||||
| `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` | |
|
||||
| `username` | The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods | | |
|
||||
| `password` | The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods | | |
|
||||
| `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` | |
|
||||
| `exportToken` | Whether or not export Vault token as environment variables (i.e VAULT_TOKEN). | `false` | |
|
||||
| `outputToken` | Whether or not to set the `vault_token` output to contain the Vault token after authentication. | `false` | |
|
||||
| `caCertificate` | Base64 encoded CA certificate the server certificate was signed with. | | |
|
||||
| `clientCertificate` | Base64 encoded client certificate the action uses to authenticate with Vault when mTLS is enabled. | | |
|
||||
| `clientKey` | Base64 encoded client key the action uses to authenticate with Vault when mTLS is enabled. | | |
|
||||
| `tlsSkipVerify` | When set to true, disables verification of server certificates when testing the action. | `false` | |
|
||||
### `url`
|
||||
|
||||
**Type: `string`**\
|
||||
**Required**
|
||||
|
||||
The URL for the Vault endpoint.
|
||||
|
||||
### `secrets`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
A semicolon-separated list of secrets to retrieve. These will automatically be
|
||||
converted to environmental variable keys. See [Key Syntax](#key-syntax) for
|
||||
more details.
|
||||
|
||||
### `namespace`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The Vault namespace from which to query secrets. Vault Enterprise only, unset by default.
|
||||
|
||||
### `method`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `token`**
|
||||
|
||||
The method to use to authenticate with Vault.
|
||||
|
||||
### `role`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Vault role for the specified auth method.
|
||||
|
||||
### `path`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The Vault path for the auth method.
|
||||
|
||||
### `token`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The Vault token to be used to authenticate with Vault.
|
||||
|
||||
### `roleId`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The role ID for App Role authentication.
|
||||
|
||||
### `secretId`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The secret ID for App Role authentication.
|
||||
|
||||
### `githubToken`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The Github Token to be used to authenticate with Vault.
|
||||
|
||||
### `jwtPrivateKey`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Base64 encoded private key to sign the JWT.
|
||||
|
||||
### `jwtKeyPassword`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Password for key stored in `jwtPrivateKey` (if needed).
|
||||
|
||||
### `jwtGithubAudience`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `sigstore`**
|
||||
|
||||
Identifies the recipient ("aud" claim) that the JWT is intended for.
|
||||
|
||||
### `jwtTtl`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `3600`**
|
||||
|
||||
Time in seconds, after which token expires.
|
||||
|
||||
### `kubernetesTokenPath`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`**
|
||||
|
||||
The path to the service-account secret with the jwt token for kubernetes based authentication.
|
||||
|
||||
### `username`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods.
|
||||
|
||||
### `password`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods.
|
||||
|
||||
### `authPayload`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
The JSON payload to be sent to Vault when using a custom authentication method.
|
||||
|
||||
### `extraHeaders`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
A string of newline separated extra headers to include on every request.
|
||||
|
||||
### `exportEnv`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `true`**
|
||||
|
||||
Whether or not to export secrets as environment variables.
|
||||
|
||||
### `exportToken`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `false`**
|
||||
|
||||
Whether or not export Vault token as environment variables (i.e VAULT_TOKEN).
|
||||
|
||||
### `outputToken`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `false`**
|
||||
|
||||
Whether or not to set the `vault_token` output to contain the Vault token after authentication.
|
||||
|
||||
### `caCertificate`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Base64 encoded CA certificate the server certificate was signed with. Defaults to CAs provided by Mozilla.
|
||||
|
||||
### `clientCertificate`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Base64 encoded client certificate the action uses to authenticate with Vault when mTLS is enabled.
|
||||
|
||||
### `clientKey`
|
||||
|
||||
**Type: `string`**
|
||||
|
||||
Base64 encoded client key the action uses to authenticate with Vault when mTLS is enabled.
|
||||
|
||||
### `tlsSkipVerify`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `false`**
|
||||
|
||||
When set to true, disables verification of server certificates when testing the action.
|
||||
|
||||
### `ignoreNotFound`
|
||||
|
||||
**Type: `string`**\
|
||||
**Default: `false`**
|
||||
|
||||
When set to true, prevents the action from failing when a secret does not exist.
|
||||
|
||||
## Masking - Hiding Secrets from Logs
|
||||
|
||||
@@ -546,18 +716,23 @@ $ npm run test:integration:basic # Choose one of: basic, enterprise, e2e, e2e-tl
|
||||
|
||||
### 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.
|
||||
You can use the [act](https://github.com/nektos/act) command to test your
|
||||
changes locally.
|
||||
|
||||
Edit the ./.github/workflows/local-test.yaml file and add any steps necessary
|
||||
to test your changes. You may have to additionally edit the Vault url, token
|
||||
and secret path if you are not using one of the provided containerized
|
||||
instances. The `local-test` job will call the ./integrationTests/e2e/setup.js
|
||||
script to bootstrap your local Vault instance with secrets.
|
||||
|
||||
Run your feature branch locally:
|
||||
|
||||
Push your changes into a feature branch.
|
||||
```sh
|
||||
$ git checkout -b my-feature-branch
|
||||
$ git commit -m "testing new changes"
|
||||
$ git push
|
||||
act workflow_dispatch -j local-test
|
||||
```
|
||||
|
||||
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.
|
||||
Or use the provided make target which will also spin up a Vault container:
|
||||
|
||||
```sh
|
||||
$ act local-test
|
||||
```
|
||||
make local-test
|
||||
```
|
||||
|
||||
+15
-7
@@ -1,4 +1,4 @@
|
||||
name: 'Vault Secrets'
|
||||
name: 'HashiCorp Vault'
|
||||
description: 'A Github Action that allows you to consume HashiCorp Vault™ secrets as secure environment variables'
|
||||
inputs:
|
||||
url:
|
||||
@@ -18,16 +18,16 @@ inputs:
|
||||
description: 'Vault role for specified auth method'
|
||||
required: false
|
||||
path:
|
||||
description: 'Custom Vault path, if the auth method was mounted at a different path'
|
||||
description: 'The Vault path for the auth method.'
|
||||
required: false
|
||||
token:
|
||||
description: 'The Vault Token to be used to authenticate with Vault'
|
||||
description: 'The Vault token to be used to authenticate with Vault'
|
||||
required: false
|
||||
roleId:
|
||||
description: 'The Role Id for App Role authentication'
|
||||
description: 'The role ID for App Role authentication'
|
||||
required: false
|
||||
secretId:
|
||||
description: 'The Secret Id for App Role authentication'
|
||||
description: 'The secret ID for App Role authentication'
|
||||
required: false
|
||||
githubToken:
|
||||
description: 'The Github Token to be used to authenticate with Vault'
|
||||
@@ -56,8 +56,12 @@ inputs:
|
||||
description: 'Whether or not export Vault token as environment variables.'
|
||||
default: 'false'
|
||||
required: false
|
||||
outputToken:
|
||||
description: 'Whether or not to set the `vault_token` output to contain the Vault token after authentication.'
|
||||
default: 'false'
|
||||
required: false
|
||||
caCertificate:
|
||||
description: 'Base64 encoded CA certificate to verify the Vault server certificate.'
|
||||
description: 'Base64 encoded CA certificate the server certificate was signed with. Defaults to CAs provided by Mozilla.'
|
||||
required: false
|
||||
clientCertificate:
|
||||
description: 'Base64 encoded client certificate for mTLS communication with the Vault server.'
|
||||
@@ -85,8 +89,12 @@ inputs:
|
||||
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
|
||||
ignoreNotFound:
|
||||
description: 'Whether or not the action should exit successfully if some requested secrets were not found.'
|
||||
required: false
|
||||
default: 'false'
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'unlock'
|
||||
|
||||
Vendored
+9204
-13751
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
version: "3.0"
|
||||
services:
|
||||
vault:
|
||||
image: vault:latest
|
||||
image: hashicorp/vault:latest
|
||||
environment:
|
||||
VAULT_DEV_ROOT_TOKEN_ID: testtoken
|
||||
ports:
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
- 8200:8200
|
||||
privileged: true
|
||||
vault-tls:
|
||||
image: vault:latest
|
||||
image: hashicorp/vault:latest
|
||||
hostname: vault-tls
|
||||
environment:
|
||||
VAULT_CAPATH: /etc/vault/ca.crt
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const got = require('got');
|
||||
const { when } = require('jest-when');
|
||||
vi.mock('@actions/core');
|
||||
import core from '@actions/core';
|
||||
|
||||
const { exportSecrets } = require('../../src/action');
|
||||
import got from 'got';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
import { exportSecrets } from '../../src/action.js';
|
||||
|
||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
|
||||
@@ -102,7 +103,7 @@ describe('authenticate with approle', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('method', expect.anything())
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const got = require('got');
|
||||
const { when } = require('jest-when');
|
||||
vi.mock('@actions/core');
|
||||
import core from '@actions/core';
|
||||
|
||||
const { exportSecrets } = require('../../src/action');
|
||||
import got from 'got';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
import { exportSecrets } from '../../src/action.js';
|
||||
|
||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
|
||||
@@ -107,7 +108,7 @@ describe('integration', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('url', expect.anything())
|
||||
@@ -124,12 +125,34 @@ describe('integration', () => {
|
||||
.mockReturnValueOnce(secrets);
|
||||
}
|
||||
|
||||
function mockIgnoreNotFound(shouldIgnore) {
|
||||
when(core.getInput)
|
||||
.calledWith('ignoreNotFound', expect.anything())
|
||||
.mockReturnValueOnce(shouldIgnore);
|
||||
}
|
||||
|
||||
|
||||
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":[]}`));
|
||||
await expect(exportSecrets()).rejects.toEqual(Error(`Unable to retrieve result for "secret/data/notFound" because it was not found: {"errors":[]}`));
|
||||
})
|
||||
|
||||
it('does not error when secret not found and ignoreNotFound is true', async () => {
|
||||
mockInput(`secret/data/test secret ;
|
||||
secret/data/test secret | NAMED_SECRET ;
|
||||
secret/data/notFound kehe | NO_SIR ;`);
|
||||
|
||||
mockIgnoreNotFound("true");
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(2);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
|
||||
expect(core.exportVariable).toBeCalledWith('NAMED_SECRET', 'SUPERSECRET');
|
||||
})
|
||||
|
||||
it('get simple secret', async () => {
|
||||
@@ -171,6 +194,26 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard secrets', async () => {
|
||||
mockInput(`secret/data/test * ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard secrets with name prefix', async () => {
|
||||
mockInput(`secret/data/test * | GROUP_ ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET');
|
||||
});
|
||||
|
||||
it('leading slash kvv2', async () => {
|
||||
mockInput('/secret/data/foobar fookv2');
|
||||
|
||||
@@ -195,6 +238,34 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get K/V v1 wildcard secrets', async () => {
|
||||
mockInput(`secret-kv1/test * ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get K/V v1 wildcard secrets with name prefix', async () => {
|
||||
mockInput(`secret-kv1/test * | GROUP_ ;`);
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(1);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('get wildcard nested secret from K/V v1', async () => {
|
||||
mockInput('secret-kv1/nested/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
|
||||
});
|
||||
|
||||
it('leading slash kvv1', async () => {
|
||||
mockInput('/secret-kv1/foobar fookv1');
|
||||
|
||||
@@ -225,6 +296,17 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
|
||||
});
|
||||
|
||||
it('wildcard supports cubbyhole', async () => {
|
||||
mockInput('/cubbyhole/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledTimes(2);
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
|
||||
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
|
||||
});
|
||||
|
||||
it('caches responses', async () => {
|
||||
mockInput(`
|
||||
/cubbyhole/test foo ;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
verbose: true
|
||||
};
|
||||
@@ -1,17 +1,18 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
const rsasign = require('jsrsasign');
|
||||
const {
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
vi.mock('@actions/core');
|
||||
import core from '@actions/core';
|
||||
import rsasign from 'jsrsasign';
|
||||
import {
|
||||
privateRsaKey,
|
||||
privateRsaKeyBase64,
|
||||
publicRsaKey
|
||||
} = require('./rsa_keys');
|
||||
} from './rsa_keys.js';
|
||||
|
||||
const got = require('got');
|
||||
const { when } = require('jest-when');
|
||||
import got from 'got';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
const { exportSecrets } = require('../../src/action');
|
||||
import { exportSecrets } from '../../src/action.js';
|
||||
|
||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
|
||||
@@ -139,7 +140,7 @@ describe('jwt auth', () => {
|
||||
|
||||
describe('authenticate with private key', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('url', expect.anything())
|
||||
@@ -189,7 +190,7 @@ describe('jwt auth', () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('url', expect.anything())
|
||||
|
||||
@@ -42,7 +42,7 @@ LrJEY9tZaRF8xraMZiOcBcyAt6S/TS29HttJ6+zlhcWx34fItEZ8jA5gzhTmspOY
|
||||
-----END PUBLIC KEY-----
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
privateRsaKey,
|
||||
privateRsaKeyBase64,
|
||||
publicRsaKey
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const got = require('got');
|
||||
const { when } = require('jest-when');
|
||||
vi.mock('@actions/core');
|
||||
import core from '@actions/core';
|
||||
|
||||
const { exportSecrets } = require('../../src/action');
|
||||
import got from 'got';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
import { exportSecrets } from '../../src/action.js';
|
||||
|
||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
|
||||
@@ -84,7 +85,7 @@ describe('authenticate with userpass', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('method', expect.anything())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// required to make jest-when work with vitest
|
||||
globals: true,
|
||||
include: [
|
||||
'**/integrationTests/basic/**.{test,spec}.?(c|m)[jt]s?(x)',
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
|
||||
describe('e2e-tls', () => {
|
||||
it('verify', () => {
|
||||
expect(process.env.SECRET).toBe("SUPERSECRET");
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
verbose: true
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
const got = require('got');
|
||||
const core = require('@actions/core');
|
||||
import got from 'got';
|
||||
import core from '@actions/core';
|
||||
|
||||
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
|
||||
const caCertificateRaw = `${process.env.VAULTCA}`;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// required to make jest-when work with vitest
|
||||
globals: true,
|
||||
include: [
|
||||
'**/integrationTests/e2e-tls/**.{test,spec}.?(c|m)[jt]s?(x)',
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
|
||||
describe('e2e', () => {
|
||||
it('verify', () => {
|
||||
expect(process.env.SECRET).toBe("SUPERSECRET");
|
||||
@@ -10,5 +12,8 @@ describe('e2e', () => {
|
||||
expect(process.env.FOO).toBe("bar");
|
||||
expect(process.env.NAMED_CUBBYSECRET).toBe("zap");
|
||||
expect(process.env.SUBSEQUENT_TEST_SECRET).toBe("SUBSEQUENT_TEST_SECRET");
|
||||
expect(process.env.JSONSTRING).toBe('{"x":1,"y":"qux"}');
|
||||
expect(process.env.JSONSTRINGMULTILINE).toBe('{"x": 1, "y": "q\\nux"}');
|
||||
expect(process.env.JSONDATA).toBe('{"x":1,"y":"qux"}');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
verbose: true
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
const got = require('got');
|
||||
import got from 'got';
|
||||
|
||||
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.VAULT_TOKEN}` : "testtoken";
|
||||
|
||||
const jsonStringMultiline = '{"x": 1, "y": "q\\nux"}';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// Verify Connection
|
||||
@@ -36,6 +38,44 @@ const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.V
|
||||
}
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-string`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
// this is stored in Vault as a string
|
||||
jsonString: '{"x":1,"y":"qux"}',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-data`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
// this is stored in Vault as a map
|
||||
jsonData: {"x":1,"y":"qux"},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/secret/data/test-json-string-multiline`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Vault-Token': vaultToken,
|
||||
},
|
||||
json: {
|
||||
data: {
|
||||
jsonStringMultiline,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await got(`http://${vaultUrl}/v1/sys/mounts/my-secret`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// required to make jest-when work with vitest
|
||||
globals: true,
|
||||
include: [
|
||||
'**/integrationTests/e2e/**.{test,spec}.?(c|m)[jt]s?(x)',
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -1,11 +1,12 @@
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const got = require('got');
|
||||
const { when } = require('jest-when');
|
||||
vi.mock('@actions/core');
|
||||
import core from '@actions/core';
|
||||
|
||||
const { exportSecrets } = require('../../src/action');
|
||||
import got from 'got';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
import { exportSecrets } from '../../src/action.js';
|
||||
|
||||
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8201'}`;
|
||||
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
|
||||
@@ -41,7 +42,7 @@ describe('integration', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('url', expect.anything())
|
||||
@@ -72,6 +73,22 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets', async () => {
|
||||
mockInput('secret/data/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets with name prefix', async () => {
|
||||
mockInput('secret/data/test * | GROUP_');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get nested secret', async () => {
|
||||
mockInput('secret/data/nested/test otherSecret');
|
||||
|
||||
@@ -103,6 +120,22 @@ describe('integration', () => {
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets from K/V v1', async () => {
|
||||
mockInput('my-secret/test *');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get wildcard secrets from K/V v1 with name prefix', async () => {
|
||||
mockInput('my-secret/test * | GROUP_');
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
||||
});
|
||||
|
||||
it('get nested secret from K/V v1', async () => {
|
||||
mockInput('my-secret/nested/test otherSecret');
|
||||
|
||||
@@ -206,7 +239,7 @@ describe('authenticate with approle', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('method', expect.anything())
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
verbose: true
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// required to make jest-when work with vitest
|
||||
globals: true,
|
||||
include: [
|
||||
'**/integrationTests/enterprise/**.{test,spec}.?(c|m)[jt]s?(x)',
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/integrationTests/'],
|
||||
};
|
||||
+1
-1
@@ -7,4 +7,4 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2018"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+4849
-2085
File diff suppressed because it is too large
Load Diff
+14
-16
@@ -2,14 +2,15 @@
|
||||
"name": "vault-action",
|
||||
"version": "0.1.0",
|
||||
"description": "A Github Action that allows you to consume vault secrets as secure environment variables.",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "ncc build src/entry.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",
|
||||
"test:integration:e2e": "jest -c integrationTests/e2e/jest.config.js",
|
||||
"test:integration:e2e-tls": "jest -c integrationTests/e2e-tls/jest.config.js"
|
||||
"test": "vitest run",
|
||||
"test:integration:basic": "vitest run -c integrationTests/basic/vitest.config.js",
|
||||
"test:integration:enterprise": "vitest run -c integrationTests/enterprise/vitest.config.js",
|
||||
"test:integration:e2e": "vitest run -c integrationTests/e2e/vitest.config.js",
|
||||
"test:integration:e2e-tls": "vitest run -c integrationTests/e2e-tls/vitest.config.js"
|
||||
},
|
||||
"files": [
|
||||
"src/**/*",
|
||||
@@ -34,18 +35,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/hashicorp/vault-action#readme",
|
||||
"dependencies": {
|
||||
"got": "^11.8.5",
|
||||
"jsonata": "^2.0.3",
|
||||
"jsrsasign": "^10.8.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@actions/core": ">=1 <2"
|
||||
"@actions/core": "^1.10.1",
|
||||
"got": "^14.2.1",
|
||||
"jsonata": "^2.0.4",
|
||||
"jsrsasign": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-when": "^3.5.2",
|
||||
"mock-http-server": "^1.4.5"
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"jest-when": "^3.6.0",
|
||||
"mock-http-server": "^1.4.5",
|
||||
"vitest": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
+14
-24
@@ -1,9 +1,14 @@
|
||||
// @ts-check
|
||||
const core = require('@actions/core');
|
||||
const command = require('@actions/core/lib/command');
|
||||
const got = require('got').default;
|
||||
import core from '@actions/core';
|
||||
import got from 'got';
|
||||
|
||||
import { normalizeOutputKey } from './utils.js';
|
||||
import { WILDCARD } from './constants.js';
|
||||
import { retrieveToken } from './auth.js';
|
||||
import { getSecrets } from './secrets.js';
|
||||
|
||||
// ncc doesn't compile jsonata imports properly, so we must use our own custom require
|
||||
import require from "./cjs-require.js";
|
||||
const jsonata = require('jsonata');
|
||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
|
||||
|
||||
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
|
||||
const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
|
||||
@@ -171,7 +176,7 @@ function parseSecretsInput(secretsInput) {
|
||||
const selectorAst = jsonata(selectorQuoted).ast();
|
||||
const selector = selectorQuoted.replace(new RegExp('"', 'g'), '');
|
||||
|
||||
if ((selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
if (selector !== WILDCARD && (selectorAst.type !== "path" || selectorAst.steps[0].stages) && selectorAst.type !== "string" && !outputVarName) {
|
||||
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
|
||||
}
|
||||
|
||||
@@ -191,20 +196,6 @@ function parseSecretsInput(secretsInput) {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace('.', '__').replace(new RegExp('-', 'g'), '').replace(/[^\p{L}\p{N}_-]/gu, '');
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} inputKey
|
||||
* @param {any} inputOptions
|
||||
@@ -230,9 +221,8 @@ function parseHeadersInput(inputKey, inputOptions) {
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
exportSecrets,
|
||||
parseSecretsInput,
|
||||
normalizeOutputKey,
|
||||
parseHeadersInput
|
||||
};
|
||||
parseHeadersInput,
|
||||
}
|
||||
|
||||
+83
-11
@@ -1,17 +1,17 @@
|
||||
jest.mock('got');
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const command = require('@actions/core/lib/command');
|
||||
const core = require('@actions/core');
|
||||
const got = require('got');
|
||||
const {
|
||||
vi.mock('got');
|
||||
vi.mock('@actions/core');
|
||||
|
||||
import core from '@actions/core';
|
||||
import got from 'got';
|
||||
import {
|
||||
exportSecrets,
|
||||
parseSecretsInput,
|
||||
parseHeadersInput
|
||||
} = require('./action');
|
||||
} from './action.js';
|
||||
|
||||
const { when } = require('jest-when');
|
||||
import { when } from 'jest-when'
|
||||
|
||||
describe('parseSecretsInput', () => {
|
||||
it('parses simple secret', () => {
|
||||
@@ -132,7 +132,7 @@ describe('parseHeaders', () => {
|
||||
|
||||
describe('exportSecrets', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('url', expect.anything())
|
||||
@@ -220,6 +220,55 @@ describe('exportSecrets', () => {
|
||||
expect(core.setOutput).toBeCalledWith('key', '1');
|
||||
});
|
||||
|
||||
it('JSON data secret retrieval', async () => {
|
||||
const jsonData = {"x":1,"y":2};
|
||||
|
||||
let result = JSON.stringify(jsonData);
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonData,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', result);
|
||||
expect(core.setOutput).toBeCalledWith('key', result);
|
||||
});
|
||||
|
||||
it('JSON string secret retrieval', async () => {
|
||||
const jsonString = '{"x":1,"y":2}';
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonString,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
|
||||
expect(core.setOutput).toBeCalledWith('key', jsonString);
|
||||
});
|
||||
|
||||
it('multi-line JSON string secret retrieval', async () => {
|
||||
const jsonString = `
|
||||
{
|
||||
"x":1,
|
||||
"y":"bar"
|
||||
}
|
||||
`;
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: jsonString,
|
||||
});
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
|
||||
expect(core.setOutput).toBeCalledWith('key', jsonString);
|
||||
});
|
||||
|
||||
it('intl secret retrieval', async () => {
|
||||
mockInput('测试 测试');
|
||||
mockVaultData({
|
||||
@@ -334,7 +383,30 @@ describe('exportSecrets', () => {
|
||||
expect(core.setOutput).toBeCalledWith('key', 'secret');
|
||||
})
|
||||
|
||||
it('multi-line secret gets masked for each line', async () => {
|
||||
it('multi-line secret', async () => {
|
||||
const multiLineString = `ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
|
||||
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
|
||||
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
|
||||
NrRFi9wrf+M7Q==`;
|
||||
|
||||
mockInput('test key');
|
||||
mockVaultData({
|
||||
key: multiLineString
|
||||
});
|
||||
mockExportToken("false")
|
||||
|
||||
await exportSecrets();
|
||||
|
||||
expect(core.setSecret).toBeCalledTimes(5); // 1 for each non-empty line + VAULT_TOKEN
|
||||
|
||||
expect(core.setSecret).toBeCalledWith("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU");
|
||||
expect(core.setSecret).toBeCalledWith("GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3");
|
||||
expect(core.setSecret).toBeCalledWith("Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA");
|
||||
expect(core.setSecret).toBeCalledWith("NrRFi9wrf+M7Q==");
|
||||
expect(core.setOutput).toBeCalledWith('key', multiLineString);
|
||||
})
|
||||
|
||||
it('multi-line secret gets masked for each non-empty line', async () => {
|
||||
const multiLineString = `a multi-line string
|
||||
|
||||
with blank lines
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
// @ts-check
|
||||
const core = require('@actions/core');
|
||||
const rsasign = require('jsrsasign');
|
||||
const fs = require('fs');
|
||||
const { default: got } = require('got');
|
||||
import core from '@actions/core';
|
||||
import * as fs from 'fs';
|
||||
import got from 'got';
|
||||
import rsasign from 'jsrsasign';
|
||||
|
||||
const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token'
|
||||
/***
|
||||
@@ -17,7 +17,7 @@ async function retrieveToken(method, client) {
|
||||
switch (method) {
|
||||
case 'approle': {
|
||||
const vaultRoleId = core.getInput('roleId', { required: true });
|
||||
const vaultSecretId = core.getInput('secretId', { required: true });
|
||||
const vaultSecretId = core.getInput('secretId', { required: false });
|
||||
return await getClientToken(client, method, path, { role_id: vaultRoleId, secret_id: vaultSecretId });
|
||||
}
|
||||
case 'github': {
|
||||
@@ -154,6 +154,6 @@ async function getClientToken(client, method, path, payload) {
|
||||
* }} auth
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
retrieveToken,
|
||||
};
|
||||
|
||||
+15
-17
@@ -1,22 +1,20 @@
|
||||
jest.mock('got');
|
||||
jest.mock('@actions/core');
|
||||
jest.mock('@actions/core/lib/command');
|
||||
jest.mock('fs', () => ({
|
||||
stat: jest.fn().mockResolvedValue(null),
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
vi.mock('got');
|
||||
vi.mock('@actions/core');
|
||||
vi.mock('fs', () => ({
|
||||
stat: vi.fn().mockResolvedValue(null),
|
||||
promises: {
|
||||
access: jest.fn().mockResolvedValue(null),
|
||||
access: vi.fn().mockResolvedValue(null),
|
||||
}
|
||||
}));
|
||||
|
||||
const core = require('@actions/core');
|
||||
const got = require('got');
|
||||
const fs = require("fs")
|
||||
const { when } = require('jest-when');
|
||||
import core from '@actions/core';
|
||||
import got from 'got'
|
||||
import * as fs from 'fs';
|
||||
import { when } from 'jest-when'
|
||||
|
||||
|
||||
const {
|
||||
retrieveToken
|
||||
} = require('./auth');
|
||||
import { retrieveToken } from './auth.js';
|
||||
|
||||
|
||||
function mockInput(name, key) {
|
||||
@@ -27,7 +25,7 @@ function mockInput(name, key) {
|
||||
|
||||
function mockApiResponse() {
|
||||
const response = { body: { auth: { client_token: testToken, renewable: true, policies: [], accessor: "accessor" } } }
|
||||
got.post = jest.fn()
|
||||
got.post = vi.fn()
|
||||
got.post.mockReturnValue(response)
|
||||
}
|
||||
const testToken = "testoken";
|
||||
@@ -35,7 +33,7 @@ const testToken = "testoken";
|
||||
describe("test retrival for token", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("test retrival with approle", async () => {
|
||||
@@ -76,7 +74,7 @@ describe("test retrival for token", () => {
|
||||
mockInput("kubernetesTokenPath", testTokenPath)
|
||||
mockInput("role", testRole)
|
||||
mockInput("path", testPath)
|
||||
fs.readFileSync = jest.fn()
|
||||
fs.readFileSync = vi.fn()
|
||||
fs.readFileSync.mockReturnValueOnce(jwtToken)
|
||||
const token = await retrieveToken(method, got)
|
||||
expect(token).toEqual(testToken)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// This allows us to use `require` in our ECMAScript module
|
||||
// See: https://github.com/vercel/ncc/issues/791
|
||||
import { createRequire } from 'node:module';
|
||||
import url from 'node:url';
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
globalThis.require = createRequire(__filename);
|
||||
|
||||
export default require;
|
||||
@@ -0,0 +1,5 @@
|
||||
const WILDCARD = '*';
|
||||
|
||||
export {
|
||||
WILDCARD
|
||||
};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
const core = require('@actions/core');
|
||||
const { exportSecrets } = require('./action');
|
||||
import core from '@actions/core';
|
||||
import { exportSecrets } from './action.js';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
const auth = require('./auth');
|
||||
const secrets = require('./secrets');
|
||||
import auth from './auth.js';
|
||||
import secrets from './secrets.js';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
auth,
|
||||
secrets
|
||||
};
|
||||
};
|
||||
|
||||
+17
-15
@@ -1,17 +1,19 @@
|
||||
jest.mock('@actions/core');
|
||||
import { vi, describe, test, expect } from 'vitest';
|
||||
|
||||
const core = require('@actions/core');
|
||||
const ServerMock = require("mock-http-server");
|
||||
const { exportSecrets } = require("./action");
|
||||
const { when } = require('jest-when');
|
||||
vi.mock('@actions/core');
|
||||
|
||||
import core from '@actions/core';
|
||||
import ServerMock from 'mock-http-server';
|
||||
import { exportSecrets } from './action.js';
|
||||
import { when } from 'jest-when';
|
||||
|
||||
describe('exportSecrets retries', () => {
|
||||
var server = new ServerMock({ host: "127.0.0.1", port: 0 });
|
||||
var calls = 0;
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(() => new Promise(done => {
|
||||
calls = 0;
|
||||
jest.resetAllMocks();
|
||||
vi.resetAllMocks();
|
||||
|
||||
when(core.getInput)
|
||||
.calledWith('token', expect.anything())
|
||||
@@ -28,11 +30,11 @@ describe('exportSecrets retries', () => {
|
||||
.mockReturnValueOnce('http://127.0.0.1:' + server.getHttpPort());
|
||||
done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach((done) => {
|
||||
afterEach(() => new Promise(done => {
|
||||
server.stop(done);
|
||||
});
|
||||
}));
|
||||
|
||||
function mockStatusCodes(statusCodes) {
|
||||
server.on({
|
||||
@@ -51,19 +53,19 @@ describe('exportSecrets retries', () => {
|
||||
});
|
||||
}
|
||||
|
||||
it('retries on 412 status code', (done) => {
|
||||
it('retries on 412 status code', () => new Promise(done => {
|
||||
mockStatusCodes([412, 200])
|
||||
exportSecrets().then(() => {
|
||||
expect(calls).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('retries on 500 status code', (done) => {
|
||||
it('retries on 500 status code', () => new Promise(done => {
|
||||
mockStatusCodes([500, 200])
|
||||
exportSecrets().then(() => {
|
||||
expect(calls).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
+105
-22
@@ -1,4 +1,10 @@
|
||||
const jsonata = require("jsonata");
|
||||
import core from '@actions/core';
|
||||
import { WILDCARD } from './constants.js';
|
||||
import { normalizeOutputKey } from './utils.js';
|
||||
|
||||
// ncc doesn't compile jsonata imports properly, so we must use our own custom require
|
||||
import require from "./cjs-require.js";
|
||||
const jsonata = require('jsonata');
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,9 +27,10 @@ const jsonata = require("jsonata");
|
||||
* @param {import('got').Got} client
|
||||
* @return {Promise<SecretResponse<TRequest>[]>}
|
||||
*/
|
||||
async function getSecrets(secretRequests, client) {
|
||||
async function getSecrets(secretRequests, client, ignoreNotFound) {
|
||||
const responseCache = new Map();
|
||||
const results = [];
|
||||
let results = [];
|
||||
|
||||
for (const secretRequest of secretRequests) {
|
||||
let { path, selector } = secretRequest;
|
||||
|
||||
@@ -41,38 +48,77 @@ async function getSecrets(secretRequests, client) {
|
||||
} 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()}`)
|
||||
let notFoundMsg = `Unable to retrieve result for "${path}" because it was not found: ${response.body.trim()}`;
|
||||
const ignoreNotFound = (core.getInput('ignoreNotFound', { required: false }) || 'false').toLowerCase() != 'false';
|
||||
if (ignoreNotFound) {
|
||||
core.error(`✘ ${notFoundMsg}`);
|
||||
continue;
|
||||
} else {
|
||||
throw Error(notFoundMsg)
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"'
|
||||
}
|
||||
selector = "data." + selector
|
||||
body = JSON.parse(body)
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
results.push({
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse
|
||||
});
|
||||
body = JSON.parse(body);
|
||||
|
||||
if (selector == WILDCARD) {
|
||||
let keys = body.data;
|
||||
if (body.data["data"] != undefined) {
|
||||
keys = keys.data;
|
||||
}
|
||||
|
||||
for (let key in keys) {
|
||||
let newRequest = Object.assign({},secretRequest);
|
||||
newRequest.selector = key;
|
||||
|
||||
if (secretRequest.selector === secretRequest.outputVarName) {
|
||||
newRequest.outputVarName = key;
|
||||
newRequest.envVarName = key;
|
||||
}
|
||||
else {
|
||||
newRequest.outputVarName = secretRequest.outputVarName+key;
|
||||
newRequest.envVarName = secretRequest.envVarName+key;
|
||||
}
|
||||
|
||||
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
|
||||
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
|
||||
|
||||
selector = key;
|
||||
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
newRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
results = await selectAndAppendResults(
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a Jsonata selector retrieve a bit of data from the result
|
||||
* @param {object} data
|
||||
* @param {string} selector
|
||||
* @param {object} data
|
||||
* @param {string} selector
|
||||
*/
|
||||
async function selectData(data, selector) {
|
||||
const ata = jsonata(selector);
|
||||
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(await jsonata(`data.${selector}`).evaluate(data));
|
||||
@@ -86,7 +132,44 @@ async function selectData(data, selector) {
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Uses selectData with the selector to get the value and then appends it to the
|
||||
* results. Returns a new array with all of the results.
|
||||
* @param {string} selector
|
||||
* @param {object} body
|
||||
* @param {object} cachedResponse
|
||||
* @param {TRequest} secretRequest
|
||||
* @param {SecretResponse<TRequest>[]} results
|
||||
* @return {Promise<SecretResponse<TRequest>[]>}
|
||||
*/
|
||||
const selectAndAppendResults = async (
|
||||
selector,
|
||||
body,
|
||||
cachedResponse,
|
||||
secretRequest,
|
||||
results
|
||||
) => {
|
||||
if (!selector.match(/.*[\.].*/)) {
|
||||
selector = '"' + selector + '"';
|
||||
}
|
||||
selector = "data." + selector;
|
||||
|
||||
if (body.data["data"] != undefined) {
|
||||
selector = "data." + selector;
|
||||
}
|
||||
|
||||
const value = await selectData(body, selector);
|
||||
return [
|
||||
...results,
|
||||
{
|
||||
request: secretRequest,
|
||||
value,
|
||||
cachedResponse,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export {
|
||||
getSecrets,
|
||||
selectData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Replaces any dot chars to __ and removes non-ascii charts
|
||||
* @param {string} dataKey
|
||||
* @param {boolean=} isEnvVar
|
||||
*/
|
||||
function normalizeOutputKey(dataKey, isEnvVar = false) {
|
||||
let outputKey = dataKey
|
||||
.replace(".", "__")
|
||||
.replace(new RegExp("-", "g"), "")
|
||||
.replace(/[^\p{L}\p{N}_-]/gu, "");
|
||||
if (isEnvVar) {
|
||||
outputKey = outputKey.toUpperCase();
|
||||
}
|
||||
return outputKey;
|
||||
}
|
||||
|
||||
export {
|
||||
normalizeOutputKey
|
||||
};
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig, configDefaults } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// required to make jest-when work with vitest
|
||||
globals: true,
|
||||
exclude:[
|
||||
...configDefaults.exclude,
|
||||
'**/integrationTests/**'
|
||||
]
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user