mirror of
https://github.com/hashicorp/vault-action.git
synced 2026-07-26 00:13:16 +03:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1b77a0929 | |||
| 3b999aeea2 | |||
| c46b8b8822 | |||
| 33b70ff01a | |||
| 8b7eaceb79 | |||
| 148ee648cc | |||
| 0f302fb182 | |||
| 47dbc643a8 | |||
| 66531b2752 | |||
| 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
|
about: Create a report to help us improve
|
||||||
title: "[BUG] "
|
title: "[BUG] "
|
||||||
labels: 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.
|
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.
|
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.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Log Output**
|
## 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.
|
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.
|
Add any other context about the problem here.
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ name: Feature request
|
|||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: "[FEAT] "
|
title: "[FEAT] "
|
||||||
labels: enhancement
|
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 [...]
|
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.
|
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.
|
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.
|
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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
|
- 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"
|
||||||
+221
-248
@@ -1,314 +1,287 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, reopened, synchronize]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: "20.9.0"
|
||||||
|
|
||||||
- name: Setup NPM Cache
|
- name: Setup NPM Cache
|
||||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: NPM Build
|
- name: NPM Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: NPM Run Test
|
- name: NPM Run Test
|
||||||
run: npm run test
|
run: npm run test
|
||||||
|
|
||||||
integrationOSS:
|
integrationOSS:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Run docker-compose
|
- name: Run docker compose
|
||||||
run: docker-compose up -d vault
|
run: docker compose up -d vault
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: "20.9.0"
|
||||||
|
|
||||||
- name: Setup NPM Cache
|
- name: Setup NPM Cache
|
||||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: NPM Build
|
- name: NPM Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: NPM Run test;integration:basic
|
- name: NPM Run test;integration:basic
|
||||||
run: npm run test:integration:basic
|
run: npm run test:integration:basic
|
||||||
env:
|
env:
|
||||||
VAULT_HOST: localhost
|
VAULT_HOST: localhost
|
||||||
VAULT_PORT: 8200
|
VAULT_PORT: 8200
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
integrationEnterprise:
|
integrationEnterprise:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Run docker-compose
|
- name: Run docker compose
|
||||||
run: docker-compose up -d vault-enterprise
|
run: docker compose up -d vault-enterprise
|
||||||
env:
|
env:
|
||||||
VAULT_LICENSE_CI: ${{ secrets.VAULT_LICENSE_CI }}
|
VAULT_LICENSE_CI: ${{ secrets.VAULT_LICENSE_CI }}
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: "20.9.0"
|
||||||
|
|
||||||
- name: Setup NPM Cache
|
- name: Setup NPM Cache
|
||||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: NPM Build
|
- name: NPM Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: NPM Run test:integration:enterprise
|
- name: NPM Run test:integration:enterprise
|
||||||
run: npm run test:integration:enterprise
|
run: npm run test:integration:enterprise
|
||||||
env:
|
env:
|
||||||
VAULT_HOST: localhost
|
VAULT_HOST: localhost
|
||||||
VAULT_PORT: 8200
|
VAULT_PORT: 8200
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Run docker-compose
|
- name: Run docker compose
|
||||||
run: docker-compose up -d vault
|
run: docker compose up -d vault
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: "20.9.0"
|
||||||
|
|
||||||
- name: Setup NPM Cache
|
- name: Setup NPM Cache
|
||||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: NPM Build
|
- name: NPM Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Setup Vault
|
- name: Setup Vault
|
||||||
run: node ./integrationTests/e2e/setup.js
|
run: node ./integrationTests/e2e/setup.js
|
||||||
env:
|
env:
|
||||||
VAULT_HOST: localhost
|
VAULT_HOST: localhost
|
||||||
VAULT_PORT: 8200
|
VAULT_PORT: 8200
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V2)
|
- name: Test Vault Action (default KV V2)
|
||||||
uses: ./
|
uses: ./
|
||||||
id: kv-secrets
|
id: kv-secrets
|
||||||
with:
|
with:
|
||||||
url: http://localhost:8200
|
url: http://localhost:8200
|
||||||
token: testtoken
|
token: testtoken
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/data/test secret ;
|
secret/data/test secret ;
|
||||||
secret/data/test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
secret/data/nested/test otherSecret ;
|
secret/data/nested/test otherSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V1)
|
- name: Test Vault Action (default KV V1)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: http://localhost:8200
|
url: http://localhost:8200
|
||||||
token: testtoken
|
token: testtoken
|
||||||
secrets: |
|
secrets: |
|
||||||
my-secret/test altSecret ;
|
my-secret/test altSecret ;
|
||||||
my-secret/test altSecret | NAMED_ALTSECRET ;
|
my-secret/test altSecret | NAMED_ALTSECRET ;
|
||||||
my-secret/nested/test otherAltSecret ;
|
my-secret/nested/test otherAltSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (cubbyhole)
|
- name: Test Vault Action (cubbyhole)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: http://localhost:8200
|
url: http://localhost:8200
|
||||||
token: testtoken
|
token: testtoken
|
||||||
secrets: |
|
secrets: |
|
||||||
/cubbyhole/test foo ;
|
/cubbyhole/test foo ;
|
||||||
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
||||||
|
|
||||||
- name: Verify Vault Action Outputs
|
# The ordering of these two Test Vault Action Overwrites Env Vars In Subsequent Action steps matters
|
||||||
run: npm run test:integration:e2e
|
# They should come before the Verify Vault Action Outputs step
|
||||||
env:
|
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
|
||||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
uses: ./
|
||||||
|
with:
|
||||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
|
url: http://localhost:8200/
|
||||||
uses: ./
|
token: testtoken
|
||||||
with:
|
secrets: |
|
||||||
url: http://localhost:8200/
|
secret/data/test secret | SUBSEQUENT_TEST_SECRET;
|
||||||
token: testtoken
|
|
||||||
secrets: |
|
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 2/2)
|
||||||
secret/data/test secret | SUBSEQUENT_TEST_SECRET;
|
uses: ./
|
||||||
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 2/2)
|
with:
|
||||||
uses: ./
|
url: http://localhost:8200/
|
||||||
with:
|
token: testtoken
|
||||||
url: http://localhost:8200/
|
secrets: |
|
||||||
token: testtoken
|
secret/data/subsequent-test secret | SUBSEQUENT_TEST_SECRET;
|
||||||
secrets: |
|
|
||||||
secret/data/subsequent-test secret | SUBSEQUENT_TEST_SECRET;
|
- name: Test JSON Secrets
|
||||||
|
uses: ./
|
||||||
|
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:
|
e2e-tls:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Run docker-compose
|
- name: Run docker compose
|
||||||
run: docker-compose up -d vault-tls
|
run: docker compose up -d vault-tls
|
||||||
|
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: "20.9.0"
|
||||||
|
|
||||||
- name: Setup NPM Cache
|
- name: Setup NPM Cache
|
||||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: NPM Build
|
- name: NPM Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Setup Vault
|
- name: Setup Vault
|
||||||
run: node ./integrationTests/e2e-tls/setup.js
|
run: node ./integrationTests/e2e-tls/setup.js
|
||||||
env:
|
env:
|
||||||
VAULT_HOST: localhost
|
VAULT_HOST: localhost
|
||||||
VAULT_PORT: 8200
|
VAULT_PORT: 8200
|
||||||
VAULTCA: ${{ secrets.VAULTCA }}
|
VAULTCA: ${{ secrets.VAULTCA }}
|
||||||
VAULT_CLIENT_CERT: ${{ secrets.VAULT_CLIENT_CERT }}
|
VAULT_CLIENT_CERT: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
VAULT_CLIENT_KEY: ${{ secrets.VAULT_CLIENT_KEY }}
|
VAULT_CLIENT_KEY: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V2)
|
- name: Test Vault Action (default KV V2)
|
||||||
uses: ./
|
uses: ./
|
||||||
id: kv-secrets
|
id: kv-secrets-tls
|
||||||
with:
|
with:
|
||||||
url: https://localhost:8200
|
url: https://localhost:8200
|
||||||
token: ${{ env.VAULT_TOKEN }}
|
token: ${{ env.VAULT_TOKEN }}
|
||||||
caCertificate: ${{ secrets.VAULTCA }}
|
caCertificate: ${{ secrets.VAULTCA }}
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/data/test secret ;
|
secret/data/test secret ;
|
||||||
secret/data/test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
secret/data/nested/test otherSecret ;
|
secret/data/nested/test otherSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (tlsSkipVerify)
|
- name: Test Vault Action (tlsSkipVerify)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: https://localhost:8200
|
url: https://localhost:8200
|
||||||
token: ${{ env.VAULT_TOKEN }}
|
token: ${{ env.VAULT_TOKEN }}
|
||||||
tlsSkipVerify: true
|
tlsSkipVerify: true
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/data/tlsSkipVerify skip ;
|
secret/data/tlsSkipVerify skip ;
|
||||||
|
|
||||||
- name: Test Vault Action (default KV V1)
|
- name: Test Vault Action (default KV V1)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: https://localhost:8200
|
url: https://localhost:8200
|
||||||
token: ${{ env.VAULT_TOKEN }}
|
token: ${{ env.VAULT_TOKEN }}
|
||||||
caCertificate: ${{ secrets.VAULTCA }}
|
caCertificate: ${{ secrets.VAULTCA }}
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
secrets: |
|
secrets: |
|
||||||
my-secret/test altSecret ;
|
my-secret/test altSecret ;
|
||||||
my-secret/test altSecret | NAMED_ALTSECRET ;
|
my-secret/test altSecret | NAMED_ALTSECRET ;
|
||||||
my-secret/nested/test otherAltSecret ;
|
my-secret/nested/test otherAltSecret ;
|
||||||
|
|
||||||
- name: Test Vault Action (cubbyhole)
|
- name: Test Vault Action (cubbyhole)
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
url: https://localhost:8200
|
url: https://localhost:8200
|
||||||
token: ${{ env.VAULT_TOKEN }}
|
token: ${{ env.VAULT_TOKEN }}
|
||||||
secrets: |
|
secrets: |
|
||||||
/cubbyhole/test foo ;
|
/cubbyhole/test foo ;
|
||||||
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
|
||||||
caCertificate: ${{ secrets.VAULTCA }}
|
caCertificate: ${{ secrets.VAULTCA }}
|
||||||
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }}
|
||||||
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
|
||||||
|
|
||||||
- name: Verify Vault Action Outputs
|
- name: Verify Vault Action Outputs
|
||||||
run: npm run test:integration:e2e-tls
|
run: npm run test:integration:e2e-tls
|
||||||
env:
|
env:
|
||||||
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
|
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets-tls.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 }}
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ jobs:
|
|||||||
JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
|
JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
|
||||||
JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
|
JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
|
||||||
with:
|
with:
|
||||||
teams-array: '["applications-eco"]'
|
teams-array: '["ecosystem", "applications-eco"]'
|
||||||
|
|||||||
@@ -1,24 +1,73 @@
|
|||||||
# This is a sample workflow to help test contributions
|
# This is a sample workflow to help test contributions
|
||||||
# Change the branch name, url and token to fit with your own environment
|
# 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
|
# To run this locally with act use:
|
||||||
# If running locally with act, run the workflow with 'act local-test'
|
# 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
|
# Don't forget to revert the file changes and invalidate any tokens that were
|
||||||
on: local-test
|
# committed before opening a pull request.
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
name: local-test
|
name: local-test
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
local-test:
|
||||||
name: local-test
|
name: local-test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Import Secrets
|
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||||
uses: hashicorp/vault-action@YOUR_BRANCH_NAME
|
|
||||||
with:
|
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
url: http://localhost:8200
|
with:
|
||||||
method: token
|
node-version: '20.9.0'
|
||||||
token: testtoken
|
|
||||||
secrets: |
|
- name: NPM Install
|
||||||
secret/data/test secret | SAMPLE_SECRET;
|
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@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
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.js build output
|
||||||
.next
|
.next
|
||||||
|
|
||||||
|
# GoLand IDE project files
|
||||||
|
.idea
|
||||||
|
|||||||
+81
-1
@@ -1,4 +1,84 @@
|
|||||||
## Unreleased
|
## 3.1.0 (January 9, 2025)
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
|
||||||
|
* fix wildcard handling when field contains dot [GH-542](https://github.com/hashicorp/vault-action/pull/542)
|
||||||
|
* bump body-parser from 1.20.0 to 1.20.3
|
||||||
|
* bump braces from 3.0.2 to 3.0.3
|
||||||
|
* bump cross-spawn from 7.0.3 to 7.0.6
|
||||||
|
* bump micromatch from 4.0.5 to 4.0.8
|
||||||
|
|
||||||
|
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)
|
||||||
|
* Use `pki` configuration to generate certificates from Vault [GH-564](https://github.com/hashicorp/vault-action/pull/564)
|
||||||
|
|
||||||
|
## 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)
|
## 2.6.0 (June 7, 2023)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.PHONY: local-test
|
||||||
|
local-test:
|
||||||
|
docker compose down; docker compose up -d vault && act workflow_dispatch -j local-test -W .github/workflows/local-test.yaml
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
A helper action for easily pulling secrets from HashiCorp Vault™.
|
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 -->
|
<!-- TOC -->
|
||||||
|
|
||||||
- [Vault GitHub Action](#vault-github-action)
|
- [Vault GitHub Action](#vault-github-action)
|
||||||
@@ -22,10 +25,12 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
|
|||||||
- [Userpass](#userpass)
|
- [Userpass](#userpass)
|
||||||
- [Ldap](#ldap)
|
- [Ldap](#ldap)
|
||||||
- [Other Auth Methods](#other-auth-methods)
|
- [Other Auth Methods](#other-auth-methods)
|
||||||
|
- [Custom Path](#custom-path-name)
|
||||||
- [Key Syntax](#key-syntax)
|
- [Key Syntax](#key-syntax)
|
||||||
- [Simple Key](#simple-key)
|
- [Simple Key](#simple-key)
|
||||||
- [Set Output Variable Name](#set-output-variable-name)
|
- [Set Output Variable Name](#set-output-variable-name)
|
||||||
- [Multiple Secrets](#multiple-secrets)
|
- [Multiple Secrets](#multiple-secrets)
|
||||||
|
- [KV secrets engine version 2](#kv-secrets-engine-version-2)
|
||||||
- [Other Secret Engines](#other-secret-engines)
|
- [Other Secret Engines](#other-secret-engines)
|
||||||
- [Adding Extra Headers](#adding-extra-headers)
|
- [Adding Extra Headers](#adding-extra-headers)
|
||||||
- [HashiCorp Cloud Platform or Vault Enterprise](#hashicorp-cloud-platform-or-vault-enterprise)
|
- [HashiCorp Cloud Platform or Vault Enterprise](#hashicorp-cloud-platform-or-vault-enterprise)
|
||||||
@@ -41,46 +46,51 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
# ...
|
# ...
|
||||||
steps:
|
steps:
|
||||||
# ...
|
# ...
|
||||||
- name: Import Secrets
|
- name: Import Secrets
|
||||||
id: import-secrets
|
id: import-secrets
|
||||||
uses: hashicorp/vault-action@v2
|
uses: hashicorp/vault-action@v2
|
||||||
with:
|
with:
|
||||||
url: https://vault.mycompany.com:8200
|
url: https://vault.mycompany.com:8200
|
||||||
token: ${{ secrets.VAULT_TOKEN }}
|
token: ${{ secrets.VAULT_TOKEN }}
|
||||||
caCertificate: ${{ secrets.VAULT_CA_CERT }}
|
caCertificate: ${{ secrets.VAULT_CA_CERT }}
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||||
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
|
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
|
||||||
secret/data/ci npm_token
|
secret/data/ci npm_token
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Retrieved secrets are available as environment variables or outputs for subsequent steps:
|
Retrieved secrets are available as environment variables or outputs for subsequent steps:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
#...
|
#...
|
||||||
- name: Step following 'Import Secrets'
|
- name: Step following 'Import Secrets'
|
||||||
run: |
|
run: |
|
||||||
ACCESS_KEY_ID = "${{ env.AWS_ACCESS_KEY_ID }}"
|
ACCESS_KEY_ID = "${{ env.AWS_ACCESS_KEY_ID }}"
|
||||||
SECRET_ACCESS_KEY = "${{ steps.import-secrets.outputs.AWS_SECRET_ACCESS_KEY }}"
|
SECRET_ACCESS_KEY = "${{ steps.import-secrets.outputs.AWS_SECRET_ACCESS_KEY }}"
|
||||||
# ...
|
|
||||||
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
If your project needs a format other than env vars and step outputs, you can use additional steps to transform them into the desired format.
|
If your project needs a format other than env vars and step outputs, you can use additional steps to transform them into the desired format.
|
||||||
For example, a common pattern is to save all the secrets in a JSON file:
|
For example, a common pattern is to save all the secrets in a JSON file:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
#...
|
#...
|
||||||
- name: Step following 'Import Secrets'
|
- name: Step following 'Import Secrets'
|
||||||
run: |
|
run: |
|
||||||
touch secrets.json
|
touch secrets.json
|
||||||
echo "${{ toJson(steps.import-secrets.outputs) }}" >> secrets.json
|
echo '${{ toJson(steps.import-secrets.outputs) }}' >> secrets.json
|
||||||
# ...
|
|
||||||
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Which with our example would yield a file containing:
|
Which with our example would yield a file containing:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ACCESS_KEY_ID": "MY_KEY_ID",
|
"ACCESS_KEY_ID": "MY_KEY_ID",
|
||||||
@@ -91,7 +101,6 @@ Which with our example would yield a file containing:
|
|||||||
|
|
||||||
Note that all secrets are masked so programs need to read the file themselves otherwise all values will be replaced with a `***` placeholder.
|
Note that all secrets are masked so programs need to read the file themselves otherwise all values will be replaced with a `***` placeholder.
|
||||||
|
|
||||||
|
|
||||||
## Authentication Methods
|
## Authentication Methods
|
||||||
|
|
||||||
Consider using a [Vault authentication method](https://www.vaultproject.io/docs/auth) such as the JWT auth method with
|
Consider using a [Vault authentication method](https://www.vaultproject.io/docs/auth) such as the JWT auth method with
|
||||||
@@ -105,7 +114,7 @@ and Vault using the
|
|||||||
Each GitHub Actions workflow receives an auto-generated OIDC token with claims
|
Each GitHub Actions workflow receives an auto-generated OIDC token with claims
|
||||||
to establish the identity of the workflow.
|
to establish the identity of the workflow.
|
||||||
|
|
||||||
__Vault Configuration__
|
**Vault Configuration**
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click to toggle instructions for configuring Vault.</summary>
|
<summary>Click to toggle instructions for configuring Vault.</summary>
|
||||||
@@ -116,7 +125,6 @@ Pass the following parameters to your auth method configuration:
|
|||||||
- `oidc_discovery_url`: `https://token.actions.githubusercontent.com`
|
- `oidc_discovery_url`: `https://token.actions.githubusercontent.com`
|
||||||
- `bound_issuer`: `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.
|
Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) for the auth method.
|
||||||
|
|
||||||
- `role_type`: `jwt`
|
- `role_type`: `jwt`
|
||||||
@@ -132,12 +140,12 @@ Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) f
|
|||||||
|
|
||||||
- For wildcard (non-exact) matches, use `bound_claims`.
|
- For wildcard (non-exact) matches, use `bound_claims`.
|
||||||
|
|
||||||
- `bound_claims_type`: `glob`
|
- `bound_claims_type`: `glob`
|
||||||
|
|
||||||
- `bound_claims`: JSON object. Maps one or more claim names to corresponding wildcard values.
|
- `bound_claims`: JSON object. Maps one or more claim names to corresponding wildcard values.
|
||||||
```json
|
```json
|
||||||
{"sub": "repo:<orgName>/*"}
|
{ "sub": "repo:<orgName>/*" }
|
||||||
```
|
```
|
||||||
|
|
||||||
- For exact matches, use `bound_subject`.
|
- For exact matches, use `bound_subject`.
|
||||||
|
|
||||||
@@ -150,17 +158,17 @@ Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) f
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
__GitHub Actions Workflow__
|
**GitHub Actions Workflow**
|
||||||
|
|
||||||
In the GitHub Actions workflow, the workflow needs permissions to read contents
|
In the GitHub Actions workflow, the workflow needs permissions to read contents
|
||||||
and write the ID token.
|
and write the ID token.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
retrieve-secret:
|
retrieve-secret:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
```
|
```
|
||||||
|
|
||||||
In the action, provide the name of the Vault role you created to the `role` parameter.
|
In the action, provide the name of the Vault role you created to the `role` parameter.
|
||||||
@@ -297,11 +305,25 @@ with:
|
|||||||
If any other method is specified and you provide an `authPayload`, the action will
|
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.
|
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
|
## Key Syntax
|
||||||
|
|
||||||
The `secrets` parameter is a set of multiple secret requests separated by the `;` character.
|
The `secrets` parameter is a set of multiple secret requests separated by the `;` character.
|
||||||
|
|
||||||
Each secret request consists of the `path` and the `key` of the desired secret, and optionally the desired Env Var output name.
|
Each secret request consists of the `path` and the `key` of the desired secret, and optionally the desired Env Var output name.
|
||||||
Note that the selector is using [JSONata](https://docs.jsonata.org/overview.html) and certain characters in keys may need to be escaped.
|
Note that the selector is using [JSONata](https://docs.jsonata.org/overview.html) and certain characters in keys may need to be escaped.
|
||||||
|
|
||||||
```raw
|
```raw
|
||||||
@@ -314,7 +336,7 @@ To retrieve a key `npmToken` from path `secret/data/ci` that has value `somelong
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
with:
|
with:
|
||||||
secrets: secret/data/ci npmToken
|
secrets: secret/data/ci npmToken
|
||||||
```
|
```
|
||||||
|
|
||||||
`vault-action` will automatically normalize the given secret selector key, and set the follow as environment variables for the following steps in the current job:
|
`vault-action` will automatically normalize the given secret selector key, and set the follow as environment variables for the following steps in the current job:
|
||||||
@@ -327,12 +349,12 @@ You can also access the secret via outputs:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
# ...
|
# ...
|
||||||
- name: Import Secrets
|
- name: Import Secrets
|
||||||
id: secrets
|
id: secrets
|
||||||
# Import config...
|
# Import config...
|
||||||
- name: Sensitive Operation
|
- name: Sensitive Operation
|
||||||
run: "my-cli --token '${{ steps.secrets.outputs.npmToken }}'"
|
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`._
|
_**Note:** If you'd like to only use outputs and disable automatic environment variables, you can set the `exportEnv` option to `false`._
|
||||||
@@ -343,7 +365,7 @@ However, if you want to set it to a specific name, say `NPM_TOKEN`, you could do
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
with:
|
with:
|
||||||
secrets: secret/data/ci npmToken | NPM_TOKEN
|
secrets: secret/data/ci npmToken | NPM_TOKEN
|
||||||
```
|
```
|
||||||
|
|
||||||
With that, `vault-action` will now use your requested name and output:
|
With that, `vault-action` will now use your requested name and output:
|
||||||
@@ -360,7 +382,6 @@ steps:
|
|||||||
# Import config...
|
# Import config...
|
||||||
- name: Sensitive Operation
|
- name: Sensitive Operation
|
||||||
run: "my-cli --token '${{ steps.secrets.outputs.NPM_TOKEN }}'"
|
run: "my-cli --token '${{ steps.secrets.outputs.NPM_TOKEN }}'"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple Secrets
|
### Multiple Secrets
|
||||||
@@ -369,25 +390,72 @@ This action can take multi-line input, so say you had your AWS keys stored in a
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
with:
|
with:
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||||
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
|
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_ ;
|
||||||
|
```
|
||||||
|
|
||||||
|
### KV secrets engine version 2
|
||||||
|
|
||||||
|
When accessing secrets from the KV secrets engine version 2, Vault Action
|
||||||
|
requires the full path to the secret. This is the same path that would be used
|
||||||
|
in a Vault policy for the secret. You can find the full path to your secret by
|
||||||
|
performing a `kv get` command like the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ vault kv get secret/test
|
||||||
|
== Secret Path ==
|
||||||
|
secret/data/test
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the full path is not `secret/test`, but `secret/data/test`.
|
||||||
|
|
||||||
|
## PKI Certificate Requests
|
||||||
|
|
||||||
|
You can use the `pki` option to generate a certificate and private key for a given role.
|
||||||
|
|
||||||
|
````yaml
|
||||||
|
with:
|
||||||
|
pki: |
|
||||||
|
pki/issue/rolename {"common_name": "role.mydomain.com", "ttl": "1h"} ;
|
||||||
|
pki/issue/otherrole {"common_name": "otherrole.mydomain.com", "ttl": "1h"} ;
|
||||||
|
```
|
||||||
|
|
||||||
|
Resulting in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ROLENAME_CA=-----BEGIN CERTIFICATE-----...
|
||||||
|
ROLENAME_CERT=-----BEGIN CERTIFICATE-----...
|
||||||
|
ROLENAME_KEY=-----BEGIN RSA PRIVATE KEY-----...
|
||||||
|
ROLENAME_CA_CHAIN=-----BEGIN CERTIFICATE-----...
|
||||||
|
OTHERROLE_CA=-----BEGIN CERTIFICATE-----...
|
||||||
|
OTHERROLE_CERT=-----BEGIN CERTIFICATE-----...
|
||||||
|
OTHERROLE_KEY=-----BEGIN RSA PRIVATE KEY-----...
|
||||||
|
OTHERROLE_CA_CHAIN=-----BEGIN CERTIFICATE-----...
|
||||||
|
````
|
||||||
|
|
||||||
## Other Secret Engines
|
## Other Secret Engines
|
||||||
|
|
||||||
Vault Action currently supports retrieving secrets from any engine where secrets
|
Vault Action currently supports retrieving secrets from any engine where secrets
|
||||||
are retrieved via `GET` requests. This means secret engines such as PKI are currently
|
are retrieved via `GET` requests, except for the PKI engine as noted above.
|
||||||
not supported due to their requirement of sending parameters along with the request
|
|
||||||
(such as `common_name`).
|
|
||||||
|
|
||||||
For example, to request a secret from the `cubbyhole` secret engine:
|
For example, to request a secret from the `cubbyhole` secret engine:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
with:
|
with:
|
||||||
secrets: |
|
secrets: |
|
||||||
/cubbyhole/foo foo ;
|
/cubbyhole/foo foo ;
|
||||||
/cubbyhole/foo zip | MY_KEY ;
|
/cubbyhole/foo zip | MY_KEY ;
|
||||||
```
|
```
|
||||||
|
|
||||||
Resulting in:
|
Resulting in:
|
||||||
@@ -415,12 +483,12 @@ If you ever need to add extra headers to the vault request, say if you need to a
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
with:
|
with:
|
||||||
secrets: |
|
secrets: |
|
||||||
secret/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||||
secret/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
|
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
|
||||||
extraHeaders: |
|
extraHeaders: |
|
||||||
X-Secure-Id: ${{ secrets.SECURE_ID }}
|
X-Secure-Id: ${{ secrets.SECURE_ID }}
|
||||||
X-Secure-Secret: ${{ secrets.SECURE_SECRET }}
|
X-Secure-Secret: ${{ secrets.SECURE_SECRET }}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to Vault.
|
This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to Vault.
|
||||||
@@ -438,53 +506,216 @@ parameter specifying the namespace. In HCP Vault, the namespace defaults to `adm
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
# ...
|
# ...
|
||||||
- name: Import Secrets
|
- name: Import Secrets
|
||||||
uses: hashicorp/vault-action
|
uses: hashicorp/vault-action
|
||||||
with:
|
with:
|
||||||
url: https://vault-enterprise.mycompany.com:8200
|
url: https://vault-enterprise.mycompany.com:8200
|
||||||
caCertificate: ${{ secrets.VAULT_CA_CERT }}
|
method: token
|
||||||
method: token
|
token: ${{ secrets.VAULT_TOKEN }}
|
||||||
token: ${{ secrets.VAULT_TOKEN }}
|
namespace: admin
|
||||||
namespace: admin
|
secrets: |
|
||||||
secrets: |
|
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||||
secret/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
|
||||||
secret/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
|
secret/data/ci npm_token
|
||||||
secret/ci npm_token
|
```
|
||||||
|
|
||||||
|
Alternatively, you may need to authenticate to the root namespace and retrieve
|
||||||
|
a secret from a different namespace. To do this, do not set the `namespace`
|
||||||
|
parameter. Instead set the namespace in the secret path. For example, `<NAMESPACE>/secret/data/app`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
# ...
|
||||||
|
- name: Import Secrets
|
||||||
|
uses: hashicorp/vault-action
|
||||||
|
with:
|
||||||
|
url: https://vault-enterprise.mycompany.com:8200
|
||||||
|
method: token
|
||||||
|
token: ${{ secrets.VAULT_TOKEN }}
|
||||||
|
secrets: |
|
||||||
|
namespace-1/secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
|
||||||
|
namespace-1/secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
|
||||||
|
namespace-1/secret/data/ci npm_token
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
Here are all the inputs available through `with`:
|
Here are all the inputs available through `with`:
|
||||||
|
|
||||||
| Input | Description | Default | Required |
|
### `url`
|
||||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
|
|
||||||
| `url` | The URL for the vault endpoint | | ✔ |
|
**Type: `string`**\
|
||||||
| `secrets` | A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details | | |
|
**Required**
|
||||||
| `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` | |
|
The URL for the Vault endpoint.
|
||||||
| `role` | Vault role for specified auth method | | |
|
|
||||||
| `path` | Custom vault path, if the auth method was enabled at a different path | | |
|
### `secrets`
|
||||||
| `token` | The Vault Token to be used to authenticate with Vault | | |
|
|
||||||
| `roleId` | The Role Id for App Role authentication | | |
|
**Type: `string`**
|
||||||
| `secretId` | The Secret Id for App Role authentication | | |
|
|
||||||
| `githubToken` | The Github Token to be used to authenticate with Vault | | |
|
A semicolon-separated list of secrets to retrieve. These will automatically be
|
||||||
| `jwtPrivateKey` | Base64 encoded Private key to sign JWT | | |
|
converted to environmental variable keys. See [Key Syntax](#key-syntax) for
|
||||||
| `jwtKeyPassword` | Password for key stored in jwtPrivateKey (if needed) | | |
|
more details.
|
||||||
| `jwtGithubAudience` | Identifies the recipient ("aud" claim) that the JWT is intended for |`sigstore`| |
|
|
||||||
| `jwtTtl` | Time in seconds, after which token expires | | 3600 |
|
### `namespace`
|
||||||
| `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 | | |
|
**Type: `string`**
|
||||||
| `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. | | |
|
The Vault namespace from which to query secrets. Vault Enterprise only, unset by default.
|
||||||
| `extraHeaders` | A string of newline separated extra headers to include on every request. | | |
|
|
||||||
| `exportEnv` | Whether or not export secrets as environment variables. | `true` | |
|
### `method`
|
||||||
| `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` | |
|
**Type: `string`**\
|
||||||
| `caCertificate` | Base64 encoded CA certificate the server certificate was signed with. | | |
|
**Default: `token`**
|
||||||
| `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. | | |
|
The method to use to authenticate with Vault.
|
||||||
| `tlsSkipVerify` | When set to true, disables verification of server certificates when testing the action. | `false` | |
|
|
||||||
|
### `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
|
## Masking - Hiding Secrets from Logs
|
||||||
|
|
||||||
@@ -498,9 +729,10 @@ To make it simpler to consume certain secrets as env vars, if no Env/Output Var
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you wish to contribute to this project, the following dependencies are recommended for local development:
|
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
|
- [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](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
|
- [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
|
- [act](https://github.com/nektos/act) to run the vault-action locally
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
@@ -513,11 +745,11 @@ $ npm install && npm run build
|
|||||||
|
|
||||||
### Vault test instance
|
### Vault test instance
|
||||||
|
|
||||||
The Github Action needs access to a working Vault instance to function.
|
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.
|
Multiple docker configurations are available via the docker-compose.yml file to run containers compatible with the various acceptance test suites.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker-compose up -d vault # Choose one of: vault, vault-enterprise, vault-tls depending on which tests you would like to run
|
$ 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:
|
Instead of using one of the dockerized instance, you can also use your own local or remote Vault instance by exporting these environment variables:
|
||||||
@@ -546,18 +778,23 @@ $ npm run test:integration:basic # Choose one of: basic, enterprise, e2e, e2e-tl
|
|||||||
|
|
||||||
### Running the action locally
|
### 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
|
You can use the [act](https://github.com/nektos/act) command to test your
|
||||||
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.
|
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
|
```sh
|
||||||
$ git checkout -b my-feature-branch
|
act workflow_dispatch -j local-test
|
||||||
$ 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.
|
Or use the provided make target which will also spin up a Vault container:
|
||||||
Run your feature branch locally.
|
|
||||||
```sh
|
```sh
|
||||||
$ act local-test
|
make local-test
|
||||||
```
|
```
|
||||||
|
|||||||
+18
-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'
|
description: 'A Github Action that allows you to consume HashiCorp Vault™ secrets as secure environment variables'
|
||||||
inputs:
|
inputs:
|
||||||
url:
|
url:
|
||||||
@@ -7,6 +7,9 @@ inputs:
|
|||||||
secrets:
|
secrets:
|
||||||
description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details'
|
description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details'
|
||||||
required: false
|
required: false
|
||||||
|
pki:
|
||||||
|
description: 'A semicolon-separated list of certificates to generate. These will automatically be converted to environment variable keys. Cannot be used with "secrets". See README for more details'
|
||||||
|
required: false
|
||||||
namespace:
|
namespace:
|
||||||
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
|
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
|
||||||
required: false
|
required: false
|
||||||
@@ -18,16 +21,16 @@ inputs:
|
|||||||
description: 'Vault role for specified auth method'
|
description: 'Vault role for specified auth method'
|
||||||
required: false
|
required: false
|
||||||
path:
|
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
|
required: false
|
||||||
token:
|
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
|
required: false
|
||||||
roleId:
|
roleId:
|
||||||
description: 'The Role Id for App Role authentication'
|
description: 'The role ID for App Role authentication'
|
||||||
required: false
|
required: false
|
||||||
secretId:
|
secretId:
|
||||||
description: 'The Secret Id for App Role authentication'
|
description: 'The secret ID for App Role authentication'
|
||||||
required: false
|
required: false
|
||||||
githubToken:
|
githubToken:
|
||||||
description: 'The Github Token to be used to authenticate with Vault'
|
description: 'The Github Token to be used to authenticate with Vault'
|
||||||
@@ -56,8 +59,12 @@ inputs:
|
|||||||
description: 'Whether or not export Vault token as environment variables.'
|
description: 'Whether or not export Vault token as environment variables.'
|
||||||
default: 'false'
|
default: 'false'
|
||||||
required: 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:
|
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
|
required: false
|
||||||
clientCertificate:
|
clientCertificate:
|
||||||
description: 'Base64 encoded client certificate for mTLS communication with the Vault server.'
|
description: 'Base64 encoded client certificate for mTLS communication with the Vault server.'
|
||||||
@@ -85,8 +92,12 @@ inputs:
|
|||||||
secretEncodingType:
|
secretEncodingType:
|
||||||
description: 'The encoding type of the secret to decode. If not specified, the secret will not be decoded. Supported values: base64, hex, utf8'
|
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
|
required: false
|
||||||
|
ignoreNotFound:
|
||||||
|
description: 'Whether or not the action should exit successfully if some requested secrets were not found.'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
runs:
|
runs:
|
||||||
using: 'node16'
|
using: 'node20'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
branding:
|
branding:
|
||||||
icon: 'unlock'
|
icon: 'unlock'
|
||||||
|
|||||||
Vendored
+320
-59
File diff suppressed because one or more lines are too long
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
version: "3.0"
|
version: "3.0"
|
||||||
services:
|
services:
|
||||||
vault:
|
vault:
|
||||||
image: vault:latest
|
image: hashicorp/vault:latest
|
||||||
environment:
|
environment:
|
||||||
VAULT_DEV_ROOT_TOKEN_ID: testtoken
|
VAULT_DEV_ROOT_TOKEN_ID: testtoken
|
||||||
ports:
|
ports:
|
||||||
@@ -17,7 +17,7 @@ services:
|
|||||||
- 8200:8200
|
- 8200:8200
|
||||||
privileged: true
|
privileged: true
|
||||||
vault-tls:
|
vault-tls:
|
||||||
image: vault:latest
|
image: hashicorp/vault:latest
|
||||||
hostname: vault-tls
|
hostname: vault-tls
|
||||||
environment:
|
environment:
|
||||||
VAULT_CAPATH: /etc/vault/ca.crt
|
VAULT_CAPATH: /etc/vault/ca.crt
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ describe('integration', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await got(`${vaultUrl}/v1/secret/data/test-with-dot-char`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
body: `{"data":{"secret.foo":"SUPERSECRET"}}`
|
||||||
|
});
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/secret/data/nested/test`, {
|
await got(`${vaultUrl}/v1/secret/data/nested/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -104,6 +112,69 @@ describe('integration', () => {
|
|||||||
"other-Secret-dash": 'OTHERCUSTOMSECRET',
|
"other-Secret-dash": 'OTHERCUSTOMSECRET',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enable pki engine
|
||||||
|
try {
|
||||||
|
await got(`${vaultUrl}/v1/sys/mounts/pki`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
type: 'pki'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const {response} = error;
|
||||||
|
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
|
||||||
|
// Engine might already be enabled from previous test runs
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Root CA
|
||||||
|
try {
|
||||||
|
await got(`${vaultUrl}/v1/pki/root/generate/internal`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
common_name: 'test',
|
||||||
|
ttl: '24h',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const {response} = error;
|
||||||
|
if (response.statusCode === 400 && response.body.includes("already exists")) {
|
||||||
|
// Root CA might already be configured from previous test runs
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure PKI Role
|
||||||
|
try {
|
||||||
|
await got(`${vaultUrl}/v1/pki/roles/Test`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
allowed_domains: ['test'],
|
||||||
|
allow_bare_domains: true,
|
||||||
|
max_ttl: '1h',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const {response} = error;
|
||||||
|
if (response.statusCode === 400 && response.body.includes("already exists")) {
|
||||||
|
// Role might already be configured from previous test runs
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -124,14 +195,55 @@ describe('integration', () => {
|
|||||||
.mockReturnValueOnce(secrets);
|
.mockReturnValueOnce(secrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockPkiInput(pki) {
|
||||||
|
when(core.getInput)
|
||||||
|
.calledWith('pki', expect.anything())
|
||||||
|
.mockReturnValueOnce(pki);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockIgnoreNotFound(shouldIgnore) {
|
||||||
|
when(core.getInput)
|
||||||
|
.calledWith('ignoreNotFound', expect.anything())
|
||||||
|
.mockReturnValueOnce(shouldIgnore);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
it('prints a nice error message when secret not found', async () => {
|
it('prints a nice error message when secret not found', async () => {
|
||||||
mockInput(`secret/data/test secret ;
|
mockInput(`secret/data/test secret ;
|
||||||
secret/data/test secret | NAMED_SECRET ;
|
secret/data/test secret | NAMED_SECRET ;
|
||||||
secret/data/notFound kehe | NO_SIR ;`);
|
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('gets a pki certificate', async () => {
|
||||||
|
mockPkiInput('pki/issue/Test {"common_name":"test","ttl":"1h"}');
|
||||||
|
|
||||||
|
await exportSecrets();
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledTimes(4);
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledWith('TEST_KEY', expect.anything());
|
||||||
|
expect(core.exportVariable).toBeCalledWith('TEST_CERT', expect.anything());
|
||||||
|
expect(core.exportVariable).toBeCalledWith('TEST_CA', expect.anything());
|
||||||
|
expect(core.exportVariable).toBeCalledWith('TEST_CA_CHAIN', expect.anything());
|
||||||
|
});
|
||||||
|
|
||||||
it('get simple secret', async () => {
|
it('get simple secret', async () => {
|
||||||
mockInput('secret/data/test secret');
|
mockInput('secret/data/test secret');
|
||||||
|
|
||||||
@@ -171,6 +283,36 @@ describe('integration', () => {
|
|||||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
|
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('get wildcard secrets with dot char', async () => {
|
||||||
|
mockInput(`secret/data/test-with-dot-char * ;`);
|
||||||
|
|
||||||
|
await exportSecrets();
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
expect(core.exportVariable).toBeCalledWith('SECRET__FOO', 'SUPERSECRET');
|
||||||
|
});
|
||||||
|
|
||||||
|
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 () => {
|
it('leading slash kvv2', async () => {
|
||||||
mockInput('/secret/data/foobar fookv2');
|
mockInput('/secret/data/foobar fookv2');
|
||||||
|
|
||||||
@@ -195,6 +337,34 @@ describe('integration', () => {
|
|||||||
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERCUSTOMSECRET');
|
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 () => {
|
it('leading slash kvv1', async () => {
|
||||||
mockInput('/secret-kv1/foobar fookv1');
|
mockInput('/secret-kv1/foobar fookv1');
|
||||||
|
|
||||||
@@ -225,6 +395,17 @@ describe('integration', () => {
|
|||||||
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
|
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 () => {
|
it('caches responses', async () => {
|
||||||
mockInput(`
|
mockInput(`
|
||||||
/cubbyhole/test foo ;
|
/cubbyhole/test foo ;
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ describe('jwt auth', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// write the jwt config, the jwt role will be written on a per-test
|
||||||
|
// basis since the audience may vary
|
||||||
await got(`${vaultUrl}/v1/auth/jwt/config`, {
|
await got(`${vaultUrl}/v1/auth/jwt/config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -108,22 +110,6 @@ describe('jwt auth', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-Vault-Token': vaultToken,
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
role_type: 'jwt',
|
|
||||||
bound_audiences: null,
|
|
||||||
bound_claims: {
|
|
||||||
iss: 'vault-action'
|
|
||||||
},
|
|
||||||
user_claim: 'iss',
|
|
||||||
policies: ['reader']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/secret/data/test`, {
|
await got(`${vaultUrl}/v1/secret/data/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -138,6 +124,24 @@ describe('jwt auth', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('authenticate with private key', () => {
|
describe('authenticate with private key', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
role_type: 'jwt',
|
||||||
|
bound_audiences: null,
|
||||||
|
bound_claims: {
|
||||||
|
iss: 'vault-action'
|
||||||
|
},
|
||||||
|
user_claim: 'iss',
|
||||||
|
policies: ['reader']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
|
||||||
@@ -170,6 +174,22 @@ describe('jwt auth', () => {
|
|||||||
|
|
||||||
describe('authenticate with Github OIDC', () => {
|
describe('authenticate with Github OIDC', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Vault-Token': vaultToken,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
role_type: 'jwt',
|
||||||
|
bound_audiences: 'https://github.com/hashicorp/vault-action',
|
||||||
|
bound_claims: {
|
||||||
|
iss: 'vault-action'
|
||||||
|
},
|
||||||
|
user_claim: 'iss',
|
||||||
|
policies: ['reader']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, {
|
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -177,7 +197,7 @@ describe('jwt auth', () => {
|
|||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
role_type: 'jwt',
|
role_type: 'jwt',
|
||||||
bound_audiences: null,
|
bound_audiences: 'sigstore',
|
||||||
bound_claims: {
|
bound_claims: {
|
||||||
iss: 'vault-action',
|
iss: 'vault-action',
|
||||||
aud: 'sigstore',
|
aud: 'sigstore',
|
||||||
|
|||||||
@@ -10,5 +10,8 @@ describe('e2e', () => {
|
|||||||
expect(process.env.FOO).toBe("bar");
|
expect(process.env.FOO).toBe("bar");
|
||||||
expect(process.env.NAMED_CUBBYSECRET).toBe("zap");
|
expect(process.env.NAMED_CUBBYSECRET).toBe("zap");
|
||||||
expect(process.env.SUBSEQUENT_TEST_SECRET).toBe("SUBSEQUENT_TEST_SECRET");
|
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"}');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ const got = require('got');
|
|||||||
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
|
const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
|
||||||
const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.VAULT_TOKEN}` : "testtoken";
|
const vaultToken = `${process.env.VAULT_TOKEN}` === undefined ? `${process.env.VAULT_TOKEN}` : "testtoken";
|
||||||
|
|
||||||
|
const jsonStringMultiline = '{"x": 1, "y": "q\\nux"}';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
// Verify Connection
|
// 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`, {
|
await got(`http://${vaultUrl}/v1/sys/mounts/my-secret`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -72,6 +72,22 @@ describe('integration', () => {
|
|||||||
expect(core.exportVariable).toBeCalledWith('TEST_KEY', 'SUPERSECRET_IN_NAMESPACE');
|
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 () => {
|
it('get nested secret', async () => {
|
||||||
mockInput('secret/data/nested/test otherSecret');
|
mockInput('secret/data/nested/test otherSecret');
|
||||||
|
|
||||||
@@ -103,6 +119,22 @@ describe('integration', () => {
|
|||||||
expect(core.exportVariable).toBeCalledWith('SECRET', 'CUSTOMSECRET_IN_NAMESPACE');
|
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 () => {
|
it('get nested secret from K/V v1', async () => {
|
||||||
mockInput('my-secret/nested/test otherSecret');
|
mockInput('my-secret/nested/test otherSecret');
|
||||||
|
|
||||||
|
|||||||
Generated
+2058
-1489
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -34,18 +34,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/hashicorp/vault-action#readme",
|
"homepage": "https://github.com/hashicorp/vault-action#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"got": "^11.8.5",
|
"got": "^11.8.6",
|
||||||
"jsonata": "^2.0.3",
|
"jsonata": "^2.0.3",
|
||||||
"jsrsasign": "^10.8.6"
|
"jsrsasign": "^11.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@actions/core": ">=1 <2"
|
"@actions/core": ">=1 <2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.1",
|
||||||
"@vercel/ncc": "^0.36.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.7.0",
|
||||||
"jest-when": "^3.5.2",
|
"jest-when": "^3.6.0",
|
||||||
"mock-http-server": "^1.4.5"
|
"mock-http-server": "^1.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-24
@@ -3,7 +3,10 @@ const core = require('@actions/core');
|
|||||||
const command = require('@actions/core/lib/command');
|
const command = require('@actions/core/lib/command');
|
||||||
const got = require('got').default;
|
const got = require('got').default;
|
||||||
const jsonata = require('jsonata');
|
const jsonata = require('jsonata');
|
||||||
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
|
const { normalizeOutputKey } = require('./utils');
|
||||||
|
const { WILDCARD } = require('./constants');
|
||||||
|
|
||||||
|
const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = require('./index');
|
||||||
|
|
||||||
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
|
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
|
||||||
const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
|
const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
|
||||||
@@ -19,6 +22,16 @@ async function exportSecrets() {
|
|||||||
const secretsInput = core.getInput('secrets', { required: false });
|
const secretsInput = core.getInput('secrets', { required: false });
|
||||||
const secretRequests = parseSecretsInput(secretsInput);
|
const secretRequests = parseSecretsInput(secretsInput);
|
||||||
|
|
||||||
|
const pkiInput = core.getInput('pki', { required: false });
|
||||||
|
let pkiRequests = [];
|
||||||
|
if (pkiInput) {
|
||||||
|
if (secretsInput) {
|
||||||
|
throw Error('You cannot provide both "secrets" and "pki" inputs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
pkiRequests = parsePkiInput(pkiInput);
|
||||||
|
}
|
||||||
|
|
||||||
const secretEncodingType = core.getInput('secretEncodingType', { required: false });
|
const secretEncodingType = core.getInput('secretEncodingType', { required: false });
|
||||||
|
|
||||||
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
|
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
|
||||||
@@ -81,12 +94,12 @@ async function exportSecrets() {
|
|||||||
core.exportVariable('VAULT_TOKEN', `${vaultToken}`);
|
core.exportVariable('VAULT_TOKEN', `${vaultToken}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const requests = secretRequests.map(request => {
|
let results = [];
|
||||||
const { path, selector } = request;
|
if (pkiRequests.length > 0) {
|
||||||
return request;
|
results = await getCertificates(pkiRequests, client);
|
||||||
});
|
} else {
|
||||||
|
results = await getSecrets(secretRequests, client);
|
||||||
const results = await getSecrets(requests, client);
|
}
|
||||||
|
|
||||||
|
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
@@ -125,6 +138,43 @@ async function exportSecrets() {
|
|||||||
* @property {string} selector
|
* @property {string} selector
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a pki input string into key paths and the request parameters.
|
||||||
|
* @param {string} pkiInput
|
||||||
|
*/
|
||||||
|
function parsePkiInput(pkiInput) {
|
||||||
|
if (!pkiInput) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const secrets = pkiInput
|
||||||
|
.split(';')
|
||||||
|
.filter(key => !!key)
|
||||||
|
.map(key => key.trim())
|
||||||
|
.filter(key => key.length !== 0);
|
||||||
|
|
||||||
|
return secrets.map(secret => {
|
||||||
|
const path = secret.substring(0, secret.indexOf(' '));
|
||||||
|
const parameters = secret.substring(secret.indexOf(' ') + 1);
|
||||||
|
|
||||||
|
core.debug(`ℹ Parsing PKI: ${path} with parameters: ${parameters}`);
|
||||||
|
|
||||||
|
if (!path || !parameters) {
|
||||||
|
throw Error(`You must provide a valid path and parameters. Input: "${secret}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputVarName = path.split('/').pop();
|
||||||
|
let envVarName = normalizeOutputKey(outputVarName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
envVarName,
|
||||||
|
outputVarName,
|
||||||
|
parameters: JSON.parse(parameters),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a secrets input string into key paths and their resulting environment variable name.
|
* Parses a secrets input string into key paths and their resulting environment variable name.
|
||||||
* @param {string} secretsInput
|
* @param {string} secretsInput
|
||||||
@@ -171,7 +221,7 @@ function parseSecretsInput(secretsInput) {
|
|||||||
const selectorAst = jsonata(selectorQuoted).ast();
|
const selectorAst = jsonata(selectorQuoted).ast();
|
||||||
const selector = selectorQuoted.replace(new RegExp('"', 'g'), '');
|
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}"`);
|
throw Error(`You must provide a name for the output key when using json selectors. Input: "${secret}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,20 +241,6 @@ function parseSecretsInput(secretsInput) {
|
|||||||
return output;
|
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 {string} inputKey
|
||||||
* @param {any} inputOptions
|
* @param {any} inputOptions
|
||||||
@@ -233,6 +269,6 @@ function parseHeadersInput(inputKey, inputOptions) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
exportSecrets,
|
exportSecrets,
|
||||||
parseSecretsInput,
|
parseSecretsInput,
|
||||||
normalizeOutputKey,
|
parseHeadersInput,
|
||||||
parseHeadersInput
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+73
-1
@@ -220,6 +220,55 @@ describe('exportSecrets', () => {
|
|||||||
expect(core.setOutput).toBeCalledWith('key', '1');
|
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 () => {
|
it('intl secret retrieval', async () => {
|
||||||
mockInput('测试 测试');
|
mockInput('测试 测试');
|
||||||
mockVaultData({
|
mockVaultData({
|
||||||
@@ -334,7 +383,30 @@ describe('exportSecrets', () => {
|
|||||||
expect(core.setOutput).toBeCalledWith('key', 'secret');
|
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
|
const multiLineString = `a multi-line string
|
||||||
|
|
||||||
with blank lines
|
with blank lines
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ async function retrieveToken(method, client) {
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case 'approle': {
|
case 'approle': {
|
||||||
const vaultRoleId = core.getInput('roleId', { required: true });
|
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 });
|
return await getClientToken(client, method, path, { role_id: vaultRoleId, secret_id: vaultSecretId });
|
||||||
}
|
}
|
||||||
case 'github': {
|
case 'github': {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const WILDCARD = '*';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
WILDCARD
|
||||||
|
};
|
||||||
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
const auth = require('./auth');
|
const auth = require('./auth');
|
||||||
const secrets = require('./secrets');
|
const secrets = require('./secrets');
|
||||||
|
const pki = require('./pki');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
auth,
|
auth,
|
||||||
secrets
|
secrets,
|
||||||
|
pki
|
||||||
};
|
};
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
const { normalizeOutputKey } = require('./utils');
|
||||||
|
const core = require('@actions/core');
|
||||||
|
|
||||||
|
/** A map of postfix values mapped to the key in the certificate response and a transformer function */
|
||||||
|
const outputMap = {
|
||||||
|
cert: { key: 'certificate', tx: (v) => v },
|
||||||
|
key: { key: 'private_key', tx: (v) => v },
|
||||||
|
ca: { key: 'issuing_ca', tx: (v) => v },
|
||||||
|
ca_chain: { key: 'ca_chain', tx: (v) => v.join('\n') },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef PkiRequest
|
||||||
|
* @type {object}
|
||||||
|
* @property {string} path - The path to the PKI endpoint
|
||||||
|
* @property {Record<string, any>} parameters - The parameters to send to the PKI endpoint
|
||||||
|
* @property {string} envVarName - The name of the environment variable to set
|
||||||
|
* @property {string} outputVarName - The name of the output variable to set
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PkiResponse
|
||||||
|
* @property {PkiRequest} request
|
||||||
|
* @property {string} value
|
||||||
|
* @property {boolean} cachedResponse
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and return the certificates from the PKI engine
|
||||||
|
* @param {Array<PkiRequest>} pkiRequests
|
||||||
|
* @param {import('got').Got} client
|
||||||
|
* @return {Promise<Array<PkiResponse>>}
|
||||||
|
*/
|
||||||
|
async function getCertificates(pkiRequests, client) {
|
||||||
|
/** @type Array<PkiResponse> */
|
||||||
|
let results = [];
|
||||||
|
|
||||||
|
for (const pkiRequest of pkiRequests) {
|
||||||
|
const { path, parameters } = pkiRequest;
|
||||||
|
|
||||||
|
const requestPath = `v1/${path}`;
|
||||||
|
let body;
|
||||||
|
try {
|
||||||
|
const result = await client.post(requestPath, {
|
||||||
|
body: JSON.stringify(parameters),
|
||||||
|
});
|
||||||
|
body = result.body;
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`✘ ${error.response?.body ?? error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
body = JSON.parse(body);
|
||||||
|
|
||||||
|
core.info(`✔ Successfully generated certificate (serial number ${body.data.serial_number})`);
|
||||||
|
|
||||||
|
Object.entries(outputMap).forEach(([key, value]) => {
|
||||||
|
const val = value.tx(body.data[value.key]);
|
||||||
|
results.push({
|
||||||
|
request: {
|
||||||
|
...pkiRequest,
|
||||||
|
envVarName: normalizeOutputKey(`${pkiRequest.envVarName}_${key}`, true),
|
||||||
|
outputVarName: normalizeOutputKey(`${pkiRequest.outputVarName}_${key}`),
|
||||||
|
},
|
||||||
|
value: val,
|
||||||
|
cachedResponse: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCertificates,
|
||||||
|
};
|
||||||
+1
-1
@@ -66,4 +66,4 @@ describe('exportSecrets retries', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+106
-21
@@ -1,5 +1,7 @@
|
|||||||
const jsonata = require("jsonata");
|
const jsonata = require("jsonata");
|
||||||
|
const { WILDCARD } = require("./constants");
|
||||||
|
const { normalizeOutputKey } = require("./utils");
|
||||||
|
const core = require('@actions/core');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} SecretRequest
|
* @typedef {Object} SecretRequest
|
||||||
@@ -21,9 +23,10 @@ const jsonata = require("jsonata");
|
|||||||
* @param {import('got').Got} client
|
* @param {import('got').Got} client
|
||||||
* @return {Promise<SecretResponse<TRequest>[]>}
|
* @return {Promise<SecretResponse<TRequest>[]>}
|
||||||
*/
|
*/
|
||||||
async function getSecrets(secretRequests, client) {
|
async function getSecrets(secretRequests, client, ignoreNotFound) {
|
||||||
const responseCache = new Map();
|
const responseCache = new Map();
|
||||||
const results = [];
|
let results = [];
|
||||||
|
|
||||||
for (const secretRequest of secretRequests) {
|
for (const secretRequest of secretRequests) {
|
||||||
let { path, selector } = secretRequest;
|
let { path, selector } = secretRequest;
|
||||||
|
|
||||||
@@ -41,38 +44,83 @@ async function getSecrets(secretRequests, client) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const {response} = error;
|
const {response} = error;
|
||||||
if (response?.statusCode === 404) {
|
if (response?.statusCode === 404) {
|
||||||
throw Error(`Unable to retrieve result for "${path}" because it was not found: ${response.body.trim()}`)
|
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
|
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);
|
body = JSON.parse(body);
|
||||||
results.push({
|
|
||||||
request: secretRequest,
|
if (selector == WILDCARD) {
|
||||||
value,
|
let keys = body.data;
|
||||||
cachedResponse
|
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);
|
||||||
|
|
||||||
|
// JSONata field references containing reserved tokens should
|
||||||
|
// be enclosed in backticks
|
||||||
|
// https://docs.jsonata.org/simple#examples
|
||||||
|
if (key.includes(".")) {
|
||||||
|
const backtick = '`';
|
||||||
|
key = backtick.concat(key, backtick);
|
||||||
|
}
|
||||||
|
selector = key;
|
||||||
|
|
||||||
|
results = await selectAndAppendResults(
|
||||||
|
selector,
|
||||||
|
body,
|
||||||
|
cachedResponse,
|
||||||
|
newRequest,
|
||||||
|
results
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
results = await selectAndAppendResults(
|
||||||
|
selector,
|
||||||
|
body,
|
||||||
|
cachedResponse,
|
||||||
|
secretRequest,
|
||||||
|
results
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses a Jsonata selector retrieve a bit of data from the result
|
* Uses a Jsonata selector retrieve a bit of data from the result
|
||||||
* @param {object} data
|
* @param {object} data
|
||||||
* @param {string} selector
|
* @param {string} selector
|
||||||
*/
|
*/
|
||||||
async function selectData(data, selector) {
|
async function selectData(data, selector) {
|
||||||
const ata = jsonata(selector);
|
const ata = jsonata(selector);
|
||||||
let result = JSON.stringify(await ata.evaluate(data));
|
let result = JSON.stringify(await ata.evaluate(data));
|
||||||
|
|
||||||
// Compat for custom engines
|
// Compat for custom engines
|
||||||
if (!result && ((ata.ast().type === "path" && ata.ast()['steps'].length === 1) || ata.ast().type === "string") && selector !== 'data' && 'data' in data) {
|
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));
|
result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
|
||||||
@@ -86,7 +134,44 @@ async function selectData(data, selector) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getSecrets,
|
getSecrets,
|
||||||
selectData
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
normalizeOutputKey
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user