Compare commits

...

54 Commits

Author SHA1 Message Date
Nicholas Farley 7b8899915e Bump got from 11.8.5 to 11.8.6 2023-09-18 13:54:54 -04:00
Mike F a5f6c67fe1 Update README.md (#491) 2023-09-15 16:09:04 -04:00
keattang d9197ec2d2 Implemented wildcard selector (based on #238) (#488)
* Initial check-in of wildcard to get all secrets in path (Issue#234)
* Fix wildcard for K/V v2 and Cubbyhole.  Add more tests
* Refactored out selectAndAppendResults
* Use selectAndAppendResults for wildcard
* Use normalizeOutputKey in action.js
* Refactored wildcard

---------

Co-authored-by: Scott Lemme <68233981+slemme1@users.noreply.github.com>
Co-authored-by: Lemme <slemme@massmutual.com>
2023-09-15 08:56:07 -04:00
John-Michael Faircloth cb841f2c86 Update to v2.7.3 (#479) 2023-07-13 14:21:01 -05:00
John-Michael Faircloth 0010502df7 fix secrets stored in JSON format, revert #473 (#478) 2023-07-13 14:00:44 -05:00
John-Michael Faircloth 65d7a12a80 Update to v2.7.2 (#475) 2023-07-06 11:42:20 -05:00
John-Michael Faircloth b138504969 fix secrets stored in JSON format (#473)
* fix secrets stored in JSON format

* add more tests

* fix lint and pass token to build

* add test cases

* add debug

* fix ordering of build steps

* fix test string format

* update test check

* fix test string format

* final cleanup

* remove comment

* remove unused var assignment

* simplify more

* simplify code and add more comments
2023-07-06 10:51:26 -05:00
John-Michael Faircloth e926631bb2 Update to v2.7.1 (#472)
* Update to v2.7.1

* update changelog
2023-07-03 11:09:52 -05:00
John-Michael Faircloth 5213b69445 Revert "fix secrets stored in json format (#466)" (#471)
* Revert "fix secrets stored in json format (#466)"

This reverts commit b9f4d16071.

* fix build: use new Verified Publisher image hashicorp/vault
2023-07-03 10:31:51 -05:00
Robert 357cb9c034 Update to v2.7.0 (#468) 2023-06-21 14:23:47 -05:00
John-Michael Faircloth b9f4d16071 fix secrets stored in json format (#466)
* fix secrets in json format

* fix actionlint

* add more comments and docs

* revert build.yml test

* add test for json

* fix selector

* fix e2e test

* fix e2e test 2

* remove test

* remove isNaN check

* update changelog
2023-06-21 11:55:50 -05:00
Thy Ton 62aa8bb4c4 Fix e2e CI failure from "Test that overwrite env var works in nested action (#453)" (#467)
Fix e2e CI failure VAULT-17057
Co-authored-by: Leonardo Villela <leonardo.villela37@gmail.com>
2023-06-13 11:22:43 -07:00
John-Michael Faircloth ec2980c187 chore: update jira teams and add actionlint (#465)
* chore: update jira teams and add actionlint

* update local test to workflow_dispatch

* define outputs

* try actionlint's skip_outputs setting

* move skip_outputs to action definition in tests

* move skip_outpus to action.yml

* remove skip_outputs

* move outputs

* call actionlint directly to pass args

* try to fix quoting

* truncate ignore

* try escaping

* add comment
2023-06-12 14:58:04 -05:00
John-Michael Faircloth 166100bd2a fix warning about outputToken being an unexpected input (#461) 2023-06-08 10:31:49 -05:00
John-Michael Faircloth dc4f72debb Revert "Test that overwrite env var works in nested action (#453)" (#464)
This reverts commit d27529ebde.
2023-06-08 10:25:58 -05:00
John-Michael Faircloth a87a71c289 ensure we run the build for all branches (#463)
* ensure we run the build for all branches

* remove pull_request_target since push is sufficient

* remove github.ref for checkouts
2023-06-08 09:43:08 -05:00
John-Michael Faircloth bb61006b6d Update to v2.6.0 (#458) 2023-06-07 15:40:06 -05:00
dependabot[bot] 14a4a058b4 chore(deps): bump jsrsasign from 10.6.1 to 10.8.6 (#451)
Bumps [jsrsasign](https://github.com/kjur/jsrsasign) from 10.6.1 to 10.8.6.
- [Release notes](https://github.com/kjur/jsrsasign/releases)
- [Changelog](https://github.com/kjur/jsrsasign/blob/master/ChangeLog.txt)
- [Commits](https://github.com/kjur/jsrsasign/compare/10.6.1...10.8.6)

---
updated-dependencies:
- dependency-name: jsrsasign
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-07 13:54:43 -05:00
Robert 2d9c2b9f1b Update Jira workflow (#456)
* Migrate jira sync to common workflow

---------

Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
2023-05-23 18:51:32 -05:00
Leonardo Villela d27529ebde Test that overwrite env var works in nested action (#453) 2023-05-19 17:54:24 -07:00
TomNorth cd5a8995f3 Scoped token access (#441)
* feat: Always allow scoped access to vault token through outputs
* Make optional instead of always, in case of untrusted steps
---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Thomas <thomas.north@dazn.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 14:11:33 -04:00
Max Coulombe 72c092c8af * document that the selector uses JSONata (#455) 2023-05-19 13:24:23 -04:00
hashicorp-tsccr[bot] 9c1dce9ef6 Result of tsccr-helper -pin-all-workflows . (#450)
Co-authored-by: hashicorp-tsccr[bot] <hashicorp-tsccr[bot]@users.noreply.github.com>
2023-05-19 10:34:26 -04:00
dependabot[bot] 9866ce3e18 chore(deps): bump jsonata from 2.0.2 to 2.0.3 (#448)
* chore(deps): bump jsonata from 2.0.2 to 2.0.3

Bumps [jsonata](https://github.com/jsonata-js/jsonata) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/jsonata-js/jsonata/releases)
- [Changelog](https://github.com/jsonata-js/jsonata/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsonata-js/jsonata/compare/v2.0.2...v2.0.3)

---
updated-dependencies:
- dependency-name: jsonata
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: maxcoulombe <max.coulombe@hashicorp.com>
2023-05-19 10:29:27 -04:00
Max Wagner 1f5b7d55d8 Set output for the error message (#446)
* Set output for the error message

In the event vault-action throws an error, the error message is
sent to the workflow log when with core.setFailed, but that output
is not accessible to other steps in the workflow.

This change sets an output for the error message, called errorMessage,
than can be read by other steps within the workflow and parsed.

* Add hashicorp/vault-action#446 to improvements in CHANGELOG
2023-05-19 09:21:14 -04:00
Sai Pranav d1655aec40 chore(readme): adding readme for #440 (#444) 2023-05-19 09:17:46 -04:00
Sai Pranav 1d767e3957 Add userpass auth and ldap auth support (#440)
* fix(auth): added approle test in basic integration

* feat(auth): adding userpass and and ldap auth

* chore(changelog): added support for userpass and ldap auth
2023-03-31 17:58:12 -05:00
dependabot[bot] c253c155ba chore(deps-dev): bump jest from 29.4.3 to 29.5.0 (#438)
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.4.3 to 29.5.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.5.0/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 10:50:10 -04:00
Max Coulombe 3a9100e7d5 Enhanced "Example Usage" section (#435)
+added documentation on how to use the retrieved secrets
+ added an example of converting the vault-action outputs to json
* fix e2e test setup
2023-03-02 16:06:42 -05:00
Mike Dahl 256bfb9e6a handle undefined response in getSecrets error handler (#431) 2023-03-02 14:39:28 -05:00
Max Coulombe 3bbbc68bd0 Development flow enhancements (#430)
+ added a contribution section to the readme
+ added a local workflow to test changes
+ made the vault token configurable for tests
* bumped action/checkout
2023-02-28 15:28:33 -05:00
dependabot[bot] 74bc2a617b chore(deps): bump jsonata from 1.8.6 to 2.0.2 (#426)
* chore(deps): bump jsonata from 1.8.6 to 2.0.2

Bumps [jsonata](https://github.com/jsonata-js/jsonata) from 1.8.6 to 2.0.2.
- [Release notes](https://github.com/jsonata-js/jsonata/releases)
- [Changelog](https://github.com/jsonata-js/jsonata/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsonata-js/jsonata/compare/v1.8.6...v2.0.2)

---
updated-dependencies:
- dependency-name: jsonata
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* * waiting for result of evaluate to account for jsonata v2 changes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: maxcoulombe <max.coulombe@hashicorp.com>
2023-02-28 08:29:18 -05:00
dependabot[bot] 76780d43f5 chore(deps-dev): bump @vercel/ncc from 0.36.0 to 0.36.1 (#417)
Bumps [@vercel/ncc](https://github.com/vercel/ncc) from 0.36.0 to 0.36.1.
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.36.0...0.36.1)

---
updated-dependencies:
- dependency-name: "@vercel/ncc"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 17:44:55 -05:00
dependabot[bot] 46540966f1 chore(deps-dev): bump jest from 29.3.1 to 29.4.3 (#425)
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.3.1 to 29.4.3.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.4.3/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 17:21:06 -05:00
dependabot[bot] cc5270ec14 chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#422)
* chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1

Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

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

* * built change

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: maxcoulombe <max.coulombe@hashicorp.com>
2023-02-27 17:19:17 -05:00
Austin Gebauer 130d1f5f4f Update to v2.5.0 (#419) 2023-01-26 13:40:24 -08:00
Austin Gebauer d34ee148bc Removes unused devDependencies from package.json (#416)
* Removes unused devDependencies from package.json

* Removes release section of package.json
2023-01-25 09:24:04 -08:00
dependabot[bot] 77bab83f42 chore(deps-dev): bump semantic-release from 19.0.5 to 20.0.4 (#415)
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 19.0.5 to 20.0.4.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v19.0.5...v20.0.4)

---
updated-dependencies:
- dependency-name: semantic-release
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 14:53:51 -08:00
dependabot[bot] 5e3dd4f01b chore(deps-dev): bump @types/jest from 29.2.2 to 29.2.6 (#413)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.2.2 to 29.2.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 14:47:35 -08:00
Kevin Schoonover 7318a98db7 print vault error message on authentication failure (#409)
Co-authored-by: Austin Gebauer <34121980+austingebauer@users.noreply.github.com>
2023-01-23 15:52:40 -08:00
dependabot[bot] b08bc4993d chore(deps): bump minimatch from 3.0.4 to 3.1.2 (#410)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-17 08:50:47 -08:00
dependabot[bot] 579f9fd8c2 chore(deps): bump json5 from 2.2.1 to 2.2.3 (#404)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-17 08:49:50 -08:00
dependabot[bot] 1226471c04 chore(deps): bump jsrsasign from 10.5.27 to 10.6.1 (#401)
Bumps [jsrsasign](https://github.com/kjur/jsrsasign) from 10.5.27 to 10.6.1.
- [Release notes](https://github.com/kjur/jsrsasign/releases)
- [Changelog](https://github.com/kjur/jsrsasign/blob/master/ChangeLog.txt)
- [Commits](https://github.com/kjur/jsrsasign/compare/10.5.27...10.6.1)

---
updated-dependencies:
- dependency-name: jsrsasign
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-17 08:48:14 -08:00
dependabot[bot] fdaeeffa26 chore(deps-dev): bump jest and @types/jest (#397)
Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) and [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest). These dependencies needed to be updated together.

Updates `jest` from 28.1.1 to 29.3.1
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v29.3.1/packages/jest)

Updates `@types/jest` from 28.1.3 to 29.2.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-type: direct:development
  update-type: version-update:semver-major
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-17 08:46:47 -08:00
Luis (LT) Carbonell 0f409d4023 Add Decoding to Secrets (#408)
* Add decoding to secrets

* remove index.js

* Add test case, and other updates
2023-01-17 10:42:49 -06:00
Austin Gebauer 8fa61e9099 Update to v2.4.3 (#395) 2022-11-08 15:23:50 -08:00
dependabot[bot] 132f1c6930 chore(deps-dev): bump jest-when from 3.5.1 to 3.5.2 (#388)
Bumps [jest-when](https://github.com/timkindberg/jest-when) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/timkindberg/jest-when/releases)
- [Commits](https://github.com/timkindberg/jest-when/compare/v3.5.1...v3.5.2)

---
updated-dependencies:
- dependency-name: jest-when
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-26 16:28:06 -07:00
dependabot[bot] f558cc7838 chore(deps-dev): bump semantic-release from 19.0.3 to 19.0.5 (#360)
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 19.0.3 to 19.0.5.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v19.0.3...v19.0.5)

---
updated-dependencies:
- dependency-name: semantic-release
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-26 16:25:24 -07:00
dependabot[bot] d0e05af6a3 chore(deps): bump jsrsasign from 10.5.25 to 10.5.27 (#358)
Bumps [jsrsasign](https://github.com/kjur/jsrsasign) from 10.5.25 to 10.5.27.
- [Release notes](https://github.com/kjur/jsrsasign/releases)
- [Changelog](https://github.com/kjur/jsrsasign/blob/master/ChangeLog.txt)
- [Commits](https://github.com/kjur/jsrsasign/compare/10.5.25...10.5.27)

---
updated-dependencies:
- dependency-name: jsrsasign
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-26 16:24:39 -07:00
Theron Voran 1f8e723e55 change example to vault-action@v2 (#391)
so we don't have to keep remembering to update the version tag in the
readme.
2022-10-26 11:48:17 -07:00
vinay-gopalan 32d00a142f update changelog (#386) 2022-10-24 16:46:21 -07:00
Theron Voran 32838a0d48 jira-sync: update the team to applications (#380) 2022-10-13 17:01:10 -07:00
dependabot[bot] ed59bea637 chore(deps-dev): bump @actions/core from 1.9.0 to 1.10.0 (#371)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 16:09:15 -07:00
prizov 2537991e61 use node16 in action.yml (#375) 2022-10-13 16:07:48 -07:00
28 changed files with 12501 additions and 21801 deletions
+22
View File
@@ -0,0 +1,22 @@
name: Lint GitHub Actions Workflows
on:
push:
paths:
- '.github/workflows/**'
jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: "Lint workflow files"
uses: docker://docker.mirror.hashicorp.services/rhysd/actionlint:latest
with:
# Ignore actionlint errors from strict typing for outputs that we use
# in our e2e tests.
# This error occurs because vault-action's outputs are dynamic but
# actionlint expects action.yml to define them.
args: >
-ignore "property \"othersecret\" is not defined in object type"
-ignore "property \"jsonstring\" is not defined in object type"
-ignore "property \"jsonstringmultiline\" is not defined in object type"
+48 -49
View File
@@ -1,23 +1,19 @@
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@v2 - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- uses: actions/setup-node@v3 - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
node-version: '16.14.0' node-version: '16.14.0'
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -37,17 +33,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Run docker-compose - name: Run docker-compose
run: docker-compose up -d vault run: docker-compose up -d vault
- uses: actions/setup-node@v3 - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
node-version: '16.14.0' node-version: '16.14.0'
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -71,19 +67,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- 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@v3 - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
node-version: '16.14.0' node-version: '16.14.0'
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -107,17 +103,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Run docker-compose - name: Run docker-compose
run: docker-compose up -d vault run: docker-compose up -d vault
- uses: actions/setup-node@v3 - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
node-version: '16.14.0' node-version: '16.14.0'
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -166,26 +162,55 @@ jobs:
/cubbyhole/test foo ; /cubbyhole/test foo ;
/cubbyhole/test zip | NAMED_CUBBYSECRET ; /cubbyhole/test zip | NAMED_CUBBYSECRET ;
# The ordering of these two Test Vault Action Overwrites Env Vars In Subsequent Action steps matters
# They should come before the Verify Vault Action Outputs step
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
uses: ./
with:
url: http://localhost:8200/
token: testtoken
secrets: |
secret/data/test secret | SUBSEQUENT_TEST_SECRET;
- name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 2/2)
uses: ./
with:
url: http://localhost:8200/
token: testtoken
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 - name: Verify Vault Action Outputs
run: npm run test:e2e run: npm run test:integration:e2e
env: env:
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }} 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@v1 - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- 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@v3 - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
node-version: '16.14.0' node-version: '16.14.0'
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
@@ -209,7 +234,7 @@ jobs:
- 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 }}
@@ -258,32 +283,6 @@ jobs:
clientKey: ${{ secrets.VAULT_CLIENT_KEY }} clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
- name: Verify Vault Action Outputs - name: Verify Vault Action Outputs
run: npm run test: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 }}
+7 -62
View File
@@ -1,3 +1,4 @@
name: JIRA Sync
on: on:
issues: issues:
types: [opened, closed, deleted, reopened] types: [opened, closed, deleted, reopened]
@@ -5,68 +6,12 @@ on:
types: [opened, closed, reopened] types: [opened, closed, reopened]
issue_comment: # Also triggers when commenting on a PR from the conversation view issue_comment: # Also triggers when commenting on a PR from the conversation view
types: [created] types: [created]
name: Jira Sync
jobs: jobs:
sync: sync:
runs-on: ubuntu-latest uses: hashicorp/vault-workflows-common/.github/workflows/jira.yaml@main
name: Jira sync secrets:
steps: JIRA_SYNC_BASE_URL: ${{ secrets.JIRA_SYNC_BASE_URL }}
- name: Login JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
uses: atlassian/gajira-login@v2.0.0 JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
env:
JIRA_BASE_URL: ${{ secrets.JIRA_SYNC_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }}
- name: Preprocess
if: github.event.action == 'opened' || github.event.action == 'created'
id: preprocess
run: |
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
echo "::set-output name=type::PR"
else
echo "::set-output name=type::ISS"
fi
- name: Create ticket
if: github.event.action == 'opened'
uses: tomhjp/gh-action-jira-create@v0.2.0
with: with:
project: VAULT teams-array: '["ecosystem", "applications-eco"]'
issuetype: "GH Issue"
summary: "${{ github.event.repository.name }} [${{ steps.preprocess.outputs.type }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}"
description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created from GitHub Action for ${{ github.event.issue.html_url || github.event.pull_request.html_url }} from ${{ github.actor }}_"
# customfield_10089 is Issue Link custom field
# customfield_10091 is team custom field
extraFields: '{"fixVersions": [{"name": "TBD"}], "customfield_10091": ["ecosystem", "foundations"], "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"}'
- name: Search
if: github.event.action != 'opened'
id: search
uses: tomhjp/gh-action-jira-search@v0.2.1
with:
# cf[10089] is Issue Link custom field
jql: 'project = "VAULT" and cf[10089]="${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
- name: Sync comment
if: github.event.action == 'created' && steps.search.outputs.issue
uses: tomhjp/gh-action-jira-comment@v0.2.0
with:
issue: ${{ steps.search.outputs.issue }}
comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}"
- name: Close ticket
if: (github.event.action == 'closed' || github.event.action == 'deleted') && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: Closed
- name: Reopen ticket
if: github.event.action == 'reopened' && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: "Pending Triage"
+61
View File
@@ -0,0 +1,61 @@
# This is a sample workflow to help test contributions
# Change the branch name, url and token to fit with your own environment
# To run this locally with act use:
# act workflow_dispatch -j local-test
#
# If you have permissions, you can run this workflow via the GitHub UI.
# Otherwise, use 'on: push' instead of 'on: workflow_dispatch'.
# Don't forget to revert the file changes and invalidate any tokens that were
# committed before opening a pull request.
on: workflow_dispatch
name: local-test
jobs:
local-test:
name: local-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version: '16.14.0'
- name: NPM Install
run: npm ci
- name: NPM Build
run: npm run build
- name: Setup Vault
run: node ./integrationTests/e2e/setup.js
env:
VAULT_HOST: localhost
VAULT_PORT: 8200
- name: Import Secrets
id: import-secrets
# use the local changes
uses: ./
# run against a specific version of vault-action
# uses: hashicorp/vault-action@v2.1.2
with:
url: http://localhost:8200
method: token
token: testtoken
secrets: |
secret/data/test-json-string jsonString;
- name: Check Secrets
run: |
touch secrets.json
echo "${{ steps.import-secrets.outputs.jsonString }}" >> secrets.json
- name: Check json file format
run: |
echo
cat secrets.json
jq -c . < secrets.json
+3
View File
@@ -59,3 +59,6 @@ typings/
# next.js build output # next.js build output
.next .next
# GoLand IDE project files
.idea
+67
View File
@@ -1,5 +1,72 @@
## Unreleased ## Unreleased
* Add changes here
## 2.7.3 (July 13, 2023)
Bugs:
* Revert to the handling of secrets in JSON format since v2.1.2 [GH-478](https://github.com/hashicorp/vault-action/pull/478)
## 2.7.2 (July 6, 2023)
Bugs:
* Fix a regression that broke support for secrets in JSON format [GH-473](https://github.com/hashicorp/vault-action/pull/473)
## 2.7.1 (July 3, 2023)
Bugs:
* Revert [GH-466](https://github.com/hashicorp/vault-action/pull/466) which caused a regression in secrets stored as JSON strings [GH-471](https://github.com/hashicorp/vault-action/pull/471)
## 2.7.0 (June 21, 2023)
Bugs:
* Fix a regression that broke support for secrets in JSON format [GH-466](https://github.com/hashicorp/vault-action/pull/466)
Improvements:
* Fix a warning about outputToken being an unexpected input [GH-461](https://github.com/hashicorp/vault-action/pull/461)
## 2.6.0 (June 7, 2023)
Features:
* Add ability to set the `vault_token` output to contain the Vault token after authentication [GH-441](https://github.com/hashicorp/vault-action/pull/441)
* Add support for userpass and ldap authentication methods [GH-440](https://github.com/hashicorp/vault-action/pull/440)
* Define an output, `errorMessage`, for vault-action's error messages so subsequent steps can read the errors [GH-446](https://github.com/hashicorp/vault-action/pull/446)
Bugs:
* Handle undefined response in getSecrets error handler [GH-431](https://github.com/hashicorp/vault-action/pull/431)
## 2.5.0 (Jan 26th, 2023)
Features:
* Adds ability to automatically decode secrets from base64, hex, and utf8 encodings. [GH-408](https://github.com/hashicorp/vault-action/pull/408)
Improvements:
* Improves error messages for Vault authentication failures [GH-409](https://github.com/hashicorp/vault-action/pull/409)
* bump jest from 28.1.1 to 29.3.1 [GH-397](https://github.com/hashicorp/vault-action/pull/397)
* bump @types/jest from 28.1.3 to 29.2.6 [GH-397](https://github.com/hashicorp/vault-action/pull/397), [GH-413](https://github.com/hashicorp/vault-action/pull/413)
* bump jsrsasign from 10.5.27 to 10.6.1 [GH-401](https://github.com/hashicorp/vault-action/pull/401)
* bump json5 from 2.2.1 to 2.2.3 [GH-404](https://github.com/hashicorp/vault-action/pull/404)
* bump minimatch from 3.0.4 to 3.1.2 [GH-410](https://github.com/hashicorp/vault-action/pull/410)
## 2.4.3 (Nov 8th, 2022)
Improvements:
* bump jest-when from 3.5.1 to 3.5.2 [GH-388](https://github.com/hashicorp/vault-action/pull/388)
* bump semantic-release from 19.0.3 to 19.0.5 [GH-360](https://github.com/hashicorp/vault-action/pull/360)
* bump jsrsasign from 10.5.25 to 10.5.27 [GH-358](https://github.com/hashicorp/vault-action/pull/358)
* bump @actions/core from 1.9.0 to 1.10.0 [GH-371](https://github.com/hashicorp/vault-action/pull/371)
* update runtime to node16 for action [GH-375](https://github.com/hashicorp/vault-action/pull/375)
## 2.4.2 (Aug 15, 2022) ## 2.4.2 (Aug 15, 2022)
Bugs: Bugs:
+3
View File
@@ -0,0 +1,3 @@
.PHONY: local-test
local-test:
docker compose down; docker-compose up -d vault && act workflow_dispatch -j local-test
+155 -1
View File
@@ -19,6 +19,8 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
- [GitHub](#github) - [GitHub](#github)
- [JWT with OIDC Provider](#jwt-with-oidc-provider) - [JWT with OIDC Provider](#jwt-with-oidc-provider)
- [Kubernetes](#kubernetes) - [Kubernetes](#kubernetes)
- [Userpass](#userpass)
- [Ldap](#ldap)
- [Other Auth Methods](#other-auth-methods) - [Other Auth Methods](#other-auth-methods)
- [Key Syntax](#key-syntax) - [Key Syntax](#key-syntax)
- [Simple Key](#simple-key) - [Simple Key](#simple-key)
@@ -31,6 +33,7 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
- [Reference](#reference) - [Reference](#reference)
- [Masking - Hiding Secrets from Logs](#masking---hiding-secrets-from-logs) - [Masking - Hiding Secrets from Logs](#masking---hiding-secrets-from-logs)
- [Normalization](#normalization) - [Normalization](#normalization)
- [Contributing](#contributing)
<!-- /TOC --> <!-- /TOC -->
@@ -43,7 +46,8 @@ jobs:
steps: steps:
# ... # ...
- name: Import Secrets - name: Import Secrets
uses: hashicorp/vault-action@v2.4.0 id: import-secrets
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 }}
@@ -55,6 +59,39 @@ jobs:
# ... # ...
``` ```
Retrieved secrets are available as environment variables or outputs for subsequent steps:
```yaml
#...
- name: Step following 'Import Secrets'
run: |
ACCESS_KEY_ID = "${{ env.AWS_ACCESS_KEY_ID }}"
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.
For example, a common pattern is to save all the secrets in a JSON file:
```yaml
#...
- name: Step following 'Import Secrets'
run: |
touch secrets.json
echo '${{ toJson(steps.import-secrets.outputs) }}' >> secrets.json
# ...
```
Which with our example would yield a file containing:
```json
{
"ACCESS_KEY_ID": "MY_KEY_ID",
"SECRET_ACCESS_KEY": "MY_SECRET_KEY",
"NPM_TOKEN": "MY_NPM_TOKEN"
}
```
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
@@ -221,6 +258,40 @@ with:
kubernetesTokenPath: /var/run/secrets/kubernetes.io/serviceaccount/token # default token path kubernetesTokenPath: /var/run/secrets/kubernetes.io/serviceaccount/token # default token path
``` ```
### Userpass
The [Userpass auth method](https://developer.hashicorp.com/vault/docs/auth/userpass) allows
your GitHub Actions workflow to authenticate to Vault with a username and password.
Set the username and password as GitHub secrets and pass them to the
`username` and `password` parameters.
This is not the same as ldap or okta auth methods.
```yaml
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: userpass
username: ${{ secrets.VAULT_USERNAME }}
password: ${{ secrets.VAULT_PASSWORD }}
```
### Ldap
The [LDAP auth method](https://developer.hashicorp.com/vault/docs/auth/ldap) allows
your GitHub Actions workflow to authenticate to Vault with a username and password inturn verfied with ldap servers.
Set the username and password as GitHub secrets and pass them to the
`username` and `password` parameters.
```yaml
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
method: ldap
username: ${{ secrets.VAULT_USERNAME }}
password: ${{ secrets.VAULT_PASSWORD }}
```
### Other Auth Methods ### Other Auth Methods
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
@@ -231,6 +302,7 @@ attempt to `POST` to `auth/${method}/login` with the provided payload and parse
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.
```raw ```raw
{{ Secret Path }} {{ Secret Key or Selector }} | {{ Env/Output Variable Name }} {{ Secret Path }} {{ Secret Key or Selector }} | {{ Env/Output Variable Name }}
@@ -301,6 +373,13 @@ with:
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_ ;
```
## Other Secret Engines ## Other Secret Engines
@@ -402,10 +481,13 @@ Here are all the inputs available through `with`:
| `jwtGithubAudience` | Identifies the recipient ("aud" claim) that the JWT is intended for |`sigstore`| | | `jwtGithubAudience` | Identifies the recipient ("aud" claim) that the JWT is intended for |`sigstore`| |
| `jwtTtl` | Time in seconds, after which token expires | | 3600 | | `jwtTtl` | Time in seconds, after which token expires | | 3600 |
| `kubernetesTokenPath` | The path to the service-account secret with the jwt token for kubernetes based authentication |`/var/run/secrets/kubernetes.io/serviceaccount/token` | | | `kubernetesTokenPath` | The path to the service-account secret with the jwt token for kubernetes based authentication |`/var/run/secrets/kubernetes.io/serviceaccount/token` | |
| `username` | The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods | | |
| `password` | The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods | | |
| `authPayload` | The JSON payload to be sent to Vault when using a custom authentication method. | | | | `authPayload` | The JSON payload to be sent to Vault when using a custom authentication method. | | |
| `extraHeaders` | A string of newline separated extra headers to include on every request. | | | | `extraHeaders` | A string of newline separated extra headers to include on every request. | | |
| `exportEnv` | Whether or not export secrets as environment variables. | `true` | | | `exportEnv` | Whether or not export secrets as environment variables. | `true` | |
| `exportToken` | Whether or not export Vault token as environment variables (i.e VAULT_TOKEN). | `false` | | | `exportToken` | Whether or not export Vault token as environment variables (i.e VAULT_TOKEN). | `false` | |
| `outputToken` | Whether or not to set the `vault_token` output to contain the Vault token after authentication. | `false` | |
| `caCertificate` | Base64 encoded CA certificate the server certificate was signed with. | | | | `caCertificate` | Base64 encoded CA certificate the server certificate was signed with. | | |
| `clientCertificate` | Base64 encoded client certificate the action uses to authenticate with Vault when mTLS is enabled. | | | | `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. | | | | `clientKey` | Base64 encoded client key the action uses to authenticate with Vault when mTLS is enabled. | | |
@@ -419,3 +501,75 @@ This action uses GitHub Action's built-in masking, so all variables will automat
## Normalization ## Normalization
To make it simpler to consume certain secrets as env vars, if no Env/Output Var Name is specified `vault-action` will replace and `.` chars with `__`, remove any other non-letter or number characters. If you're concerned about the result, it's recommended to provide an explicit Output Var Key. To make it simpler to consume certain secrets as env vars, if no Env/Output Var Name is specified `vault-action` will replace and `.` chars with `__`, remove any other non-letter or number characters. If you're concerned about the result, it's recommended to provide an explicit Output Var Key.
## Contributing
If you wish to contribute to this project, the following dependencies are recommended for local development:
- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) to install dependencies, build project and run tests
- [docker](https://docs.docker.com/get-docker/) to run the pre-configured vault containers for acceptance tests
- [docker-compose](https://docs.docker.com/compose/) to spin up the pre-configured vault containers for acceptance tests
- [act](https://github.com/nektos/act) to run the vault-action locally
### Build
Use npm to install dependencies and build the project:
```sh
$ npm install && npm run build
```
### Vault test instance
The Github Action needs access to a working Vault instance to function.
Multiple docker configurations are available via the docker-compose.yml file to run containers compatible with the various acceptance test suites.
```sh
$ docker-compose up -d vault # Choose one of: vault, vault-enterprise, vault-tls depending on which tests you would like to run
```
Instead of using one of the dockerized instance, you can also use your own local or remote Vault instance by exporting these environment variables:
```sh
$ export VAULT_HOST=<YOUR VAULT CLUSTER LOCATION> # localhost if undefined
$ export VAULT_PORT=<YOUR VAULT PORT> # 8200 if undefined
$ export VAULT_TOKEN=<YOUR VAULT TOKEN> # testtoken if undefined
```
### Running unit tests
Unit tests can be executed at any time with no dependencies or prior setup.
```sh
$ npm test
```
### Running acceptance tests
With a succesful build to take your local changes into account and a working Vault instance configured, you can now run acceptance tests to validate if any regressions were introduced.
```sh
$ npm run test:integration:basic # Choose one of: basic, enterprise, e2e, e2e-tls
```
### Running the action locally
You can use the [act](https://github.com/nektos/act) command to test your
changes locally.
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:
```sh
act workflow_dispatch -j local-test
```
Or use the provided make target which will also spin up a Vault container:
```sh
make local-test
```
+14 -1
View File
@@ -36,6 +36,12 @@ inputs:
description: 'The path to the Kubernetes service account secret' description: 'The path to the Kubernetes service account secret'
required: false required: false
default: '/var/run/secrets/kubernetes.io/serviceaccount/token' default: '/var/run/secrets/kubernetes.io/serviceaccount/token'
username:
description: 'The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods'
required: false
password:
description: 'The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods'
required: false
authPayload: authPayload:
description: 'The JSON payload to be sent to Vault when using a custom authentication method.' description: 'The JSON payload to be sent to Vault when using a custom authentication method.'
required: false required: false
@@ -50,6 +56,10 @@ 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 to verify the Vault server certificate.'
required: false required: false
@@ -76,8 +86,11 @@ inputs:
description: 'Time in seconds, after which token expires' description: 'Time in seconds, after which token expires'
required: false required: false
default: 3600 default: 3600
secretEncodingType:
description: 'The encoding type of the secret to decode. If not specified, the secret will not be decoded. Supported values: base64, hex, utf8'
required: false
runs: runs:
using: 'node12' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'
branding: branding:
icon: 'unlock' icon: 'unlock'
+9775 -8935
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -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
+134
View File
@@ -0,0 +1,134 @@
jest.mock('@actions/core');
jest.mock('@actions/core/lib/command');
const core = require('@actions/core');
const got = require('got');
const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('authenticate with approle', () => {
let roleId;
let secretId;
beforeAll(async () => {
try {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': vaultToken,
},
});
await got(`${vaultUrl}/v1/secret/data/approle-test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
secret: 'SUPERSECRET_WITH_APPROLE',
},
},
});
// Enable approle
try {
await got(`${vaultUrl}/v1/sys/auth/approle`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
type: 'approle'
},
});
} catch (error) {
const {response} = error;
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
// Approle might already be enabled from previous test runs
} else {
throw error;
}
}
// Create policies
await got(`${vaultUrl}/v1/sys/policies/acl/test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
"name":"test",
"policy":"path \"auth/approle/*\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"auth/approle/role/my-role/role-id\"\n{\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\npath \"auth/approle/role/my-role/secret-id\"\n{\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\n\npath \"secret/data/*\" {\n capabilities = [\"list\"]\n}\npath \"secret/metadata/*\" {\n capabilities = [\"list\"]\n}\n\npath \"secret/data/approle-test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/approle-test\" {\n capabilities = [\"read\", \"list\"]\n}\n"
},
});
// Create approle
await got(`${vaultUrl}/v1/auth/approle/role/my-role`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
policies: 'test'
},
});
// Get role-id
const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, {
headers: {
'X-Vault-Token': vaultToken
},
responseType: 'json',
});
roleId = roldIdResponse.body.data.role_id;
// Get secret-id
const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
responseType: 'json',
});
secretId = secretIdResponse.body.data.secret_id;
} catch(err) {
console.warn('Create approle', err.response.body);
throw err;
}
});
beforeEach(() => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('method', expect.anything())
.mockReturnValueOnce('approle');
when(core.getInput)
.calledWith('roleId', expect.anything())
.mockReturnValueOnce(roleId);
when(core.getInput)
.calledWith('secretId', expect.anything())
.mockReturnValueOnce(secretId);
when(core.getInput)
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
});
function mockInput(secrets) {
when(core.getInput)
.calledWith('secrets', expect.anything())
.mockReturnValueOnce(secrets);
}
it('authenticate with approle', async() => {
mockInput('secret/data/approle-test secret');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_WITH_APPROLE');
})
});
+70 -10
View File
@@ -8,20 +8,21 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action'); const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`; const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('integration', () => { describe('integration', () => {
beforeAll(async () => { beforeAll(async () => {
// Verify Connection // Verify Connection
await got(`${vaultUrl}/v1/secret/config`, { await got(`${vaultUrl}/v1/secret/config`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
}); });
await got(`${vaultUrl}/v1/secret/data/test`, { await got(`${vaultUrl}/v1/secret/data/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -33,7 +34,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret/data/nested/test`, { await got(`${vaultUrl}/v1/secret/data/nested/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -45,7 +46,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret/data/foobar`, { await got(`${vaultUrl}/v1/secret/data/foobar`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -59,7 +60,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/sys/mounts/secret-kv1`, { await got(`${vaultUrl}/v1/sys/mounts/secret-kv1`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
type: 'kv' type: 'kv'
@@ -77,7 +78,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/test`, { await got(`${vaultUrl}/v1/secret-kv1/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
secret: 'CUSTOMSECRET', secret: 'CUSTOMSECRET',
@@ -87,7 +88,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/foobar`, { await got(`${vaultUrl}/v1/secret-kv1/foobar`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
fookv1: 'bar', fookv1: 'bar',
@@ -97,7 +98,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/secret-kv1/nested/test`, { await got(`${vaultUrl}/v1/secret-kv1/nested/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
"other-Secret-dash": 'OTHERCUSTOMSECRET', "other-Secret-dash": 'OTHERCUSTOMSECRET',
@@ -114,7 +115,7 @@ describe('integration', () => {
when(core.getInput) when(core.getInput)
.calledWith('token', expect.anything()) .calledWith('token', expect.anything())
.mockReturnValueOnce('testtoken'); .mockReturnValueOnce(vaultToken);
}); });
function mockInput(secrets) { function mockInput(secrets) {
@@ -170,6 +171,26 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET'); expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
}); });
it('get wildcard secrets', async () => {
mockInput(`secret/data/test * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
});
it('get wildcard secrets with name prefix', async () => {
mockInput(`secret/data/test * | GROUP_ ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET');
});
it('leading slash kvv2', async () => { it('leading slash kvv2', async () => {
mockInput('/secret/data/foobar fookv2'); mockInput('/secret/data/foobar fookv2');
@@ -194,6 +215,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');
@@ -207,7 +256,7 @@ describe('integration', () => {
await got(`${vaultUrl}/v1/cubbyhole/test`, { await got(`${vaultUrl}/v1/cubbyhole/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
foo: "bar", foo: "bar",
@@ -224,6 +273,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 ;
+8 -7
View File
@@ -14,6 +14,7 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action'); const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`; const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
/** /**
* Returns Github OIDC response mock * Returns Github OIDC response mock
@@ -59,7 +60,7 @@ describe('jwt auth', () => {
// Verify Connection // Verify Connection
await got(`${vaultUrl}/v1/secret/config`, { await got(`${vaultUrl}/v1/secret/config`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
}); });
@@ -67,7 +68,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/sys/auth/jwt`, { await got(`${vaultUrl}/v1/sys/auth/jwt`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
type: 'jwt' type: 'jwt'
@@ -85,7 +86,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/sys/policy/reader`, { await got(`${vaultUrl}/v1/sys/policy/reader`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
policy: ` policy: `
@@ -99,7 +100,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/auth/jwt/config`, { await got(`${vaultUrl}/v1/auth/jwt/config`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
jwt_validation_pubkeys: publicRsaKey, jwt_validation_pubkeys: publicRsaKey,
@@ -110,7 +111,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/auth/jwt/role/default`, { await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
role_type: 'jwt', role_type: 'jwt',
@@ -126,7 +127,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/secret/data/test`, { await got(`${vaultUrl}/v1/secret/data/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -172,7 +173,7 @@ describe('jwt auth', () => {
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, { await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
role_type: 'jwt', role_type: 'jwt',
@@ -0,0 +1,116 @@
jest.mock('@actions/core');
jest.mock('@actions/core/lib/command');
const core = require('@actions/core');
const got = require('got');
const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8200'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('authenticate with userpass', () => {
const username = `testUsername`;
const password = `testPassword`;
beforeAll(async () => {
try {
// Verify Connection
await got(`${vaultUrl}/v1/secret/config`, {
headers: {
'X-Vault-Token': vaultToken,
},
});
await got(`${vaultUrl}/v1/secret/data/userpass-test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
secret: 'SUPERSECRET_WITH_USERPASS',
},
},
});
// Enable userpass
try {
await got(`${vaultUrl}/v1/sys/auth/userpass`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
type: 'userpass'
},
});
} catch (error) {
const {response} = error;
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
// Userpass might already be enabled from previous test runs
} else {
throw error;
}
}
// Create policies
await got(`${vaultUrl}/v1/sys/policies/acl/userpass-test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
"name":"userpass-test",
"policy":`path \"auth/userpass/*\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"auth/userpass/users/${username}\"\n{\n capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\n\npath \"secret/data/*\" {\n capabilities = [\"list\"]\n}\npath \"secret/metadata/*\" {\n capabilities = [\"list\"]\n}\n\npath \"secret/data/userpass-test\" {\n capabilities = [\"read\", \"list\"]\n}\npath \"secret/metadata/userpass-test\" {\n capabilities = [\"read\", \"list\"]\n}\n`
},
});
// Create user
await got(`${vaultUrl}/v1/auth/userpass/users/${username}`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken
},
json: {
password: `${password}`,
policies: 'userpass-test'
},
});
} catch(err) {
console.warn('Create user in userpass', err.response.body);
throw err;
}
});
beforeEach(() => {
jest.resetAllMocks();
when(core.getInput)
.calledWith('method', expect.anything())
.mockReturnValueOnce('userpass');
when(core.getInput)
.calledWith('username', expect.anything())
.mockReturnValueOnce(username);
when(core.getInput)
.calledWith('password', expect.anything())
.mockReturnValueOnce(password);
when(core.getInput)
.calledWith('url', expect.anything())
.mockReturnValueOnce(`${vaultUrl}`);
});
function mockInput(secrets) {
when(core.getInput)
.calledWith('secrets', expect.anything())
.mockReturnValueOnce(secrets);
}
it('authenticate with userpass', async() => {
mockInput('secret/data/userpass-test secret');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET_WITH_USERPASS');
})
});
+4
View File
@@ -9,5 +9,9 @@ describe('e2e', () => {
expect(process.env.OTHERALTSECRET).toBe("OTHERCUSTOMSECRET"); expect(process.env.OTHERALTSECRET).toBe("OTHERCUSTOMSECRET");
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.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"}');
}); });
}); });
+60 -7
View File
@@ -1,20 +1,23 @@
const got = require('got'); 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 jsonStringMultiline = '{"x": 1, "y": "q\\nux"}';
(async () => { (async () => {
try { try {
// Verify Connection // Verify Connection
await got(`http://${vaultUrl}/v1/secret/config`, { await got(`http://${vaultUrl}/v1/secret/config`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
}); });
await got(`http://${vaultUrl}/v1/secret/data/test`, { await got(`http://${vaultUrl}/v1/secret/data/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -26,7 +29,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/secret/data/nested/test`, { await got(`http://${vaultUrl}/v1/secret/data/nested/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -35,10 +38,48 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
} }
}); });
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: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
type: 'kv' type: 'kv'
@@ -48,7 +89,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/my-secret/test`, { await got(`http://${vaultUrl}/v1/my-secret/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
altSecret: 'CUSTOMSECRET', altSecret: 'CUSTOMSECRET',
@@ -58,7 +99,7 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/my-secret/nested/test`, { await got(`http://${vaultUrl}/v1/my-secret/nested/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
otherAltSecret: 'OTHERCUSTOMSECRET', otherAltSecret: 'OTHERCUSTOMSECRET',
@@ -68,13 +109,25 @@ const vaultUrl = `${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`;
await got(`http://${vaultUrl}/v1/cubbyhole/test`, { await got(`http://${vaultUrl}/v1/cubbyhole/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
foo: 'bar', foo: 'bar',
zip: 'zap', zip: 'zap',
}, },
}); });
await got(`http://${vaultUrl}/v1/secret/data/subsequent-test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
data: {
secret: 'SUBSEQUENT_TEST_SECRET',
},
},
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
process.exit(1); process.exit(1);
+44 -11
View File
@@ -8,6 +8,7 @@ const { when } = require('jest-when');
const { exportSecrets } = require('../../src/action'); const { exportSecrets } = require('../../src/action');
const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8201'}`; const vaultUrl = `http://${process.env.VAULT_HOST || 'localhost'}:${process.env.VAULT_PORT || '8201'}`;
const vaultToken = `${process.env.VAULT_TOKEN || 'testtoken'}`
describe('integration', () => { describe('integration', () => {
beforeAll(async () => { beforeAll(async () => {
@@ -15,7 +16,7 @@ describe('integration', () => {
// Verify Connection // Verify Connection
await got(`${vaultUrl}/v1/secret/config`, { await got(`${vaultUrl}/v1/secret/config`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
}); });
@@ -48,7 +49,7 @@ describe('integration', () => {
when(core.getInput) when(core.getInput)
.calledWith('token', expect.anything()) .calledWith('token', expect.anything())
.mockReturnValueOnce('testtoken'); .mockReturnValueOnce(vaultToken);
when(core.getInput) when(core.getInput)
.calledWith('namespace', expect.anything()) .calledWith('namespace', expect.anything())
@@ -71,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');
@@ -102,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');
@@ -119,7 +152,7 @@ describe('authenticate with approle', () => {
// Verify Connection // Verify Connection
await got(`${vaultUrl}/v1/secret/config`, { await got(`${vaultUrl}/v1/secret/config`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
}); });
@@ -137,7 +170,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/sys/auth/approle`, { await got(`${vaultUrl}/v1/sys/auth/approle`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2', 'X-Vault-Namespace': 'ns2',
}, },
json: { json: {
@@ -157,7 +190,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/sys/policies/acl/test`, { await got(`${vaultUrl}/v1/sys/policies/acl/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2', 'X-Vault-Namespace': 'ns2',
}, },
json: { json: {
@@ -170,7 +203,7 @@ describe('authenticate with approle', () => {
await got(`${vaultUrl}/v1/auth/approle/role/my-role`, { await got(`${vaultUrl}/v1/auth/approle/role/my-role`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2', 'X-Vault-Namespace': 'ns2',
}, },
json: { json: {
@@ -181,7 +214,7 @@ describe('authenticate with approle', () => {
// Get role-id // Get role-id
const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, { const roldIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/role-id`, {
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2', 'X-Vault-Namespace': 'ns2',
}, },
responseType: 'json', responseType: 'json',
@@ -192,7 +225,7 @@ describe('authenticate with approle', () => {
const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, { const secretIdResponse = await got(`${vaultUrl}/v1/auth/approle/role/my-role/secret-id`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': 'ns2', 'X-Vault-Namespace': 'ns2',
}, },
responseType: 'json', responseType: 'json',
@@ -238,7 +271,7 @@ async function enableNamespace(name) {
await got(`${vaultUrl}/v1/sys/namespaces/${name}`, { await got(`${vaultUrl}/v1/sys/namespaces/${name}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
} }
}); });
} catch (error) { } catch (error) {
@@ -256,7 +289,7 @@ async function enableEngine(path, namespace, version) {
await got(`${vaultUrl}/v1/sys/mounts/${path}`, { await got(`${vaultUrl}/v1/sys/mounts/${path}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': namespace, 'X-Vault-Namespace': namespace,
}, },
json: { type: 'kv', config: {}, options: { version }, generate_signing_key: true }, json: { type: 'kv', config: {}, options: { version }, generate_signing_key: true },
@@ -277,7 +310,7 @@ async function writeSecret(engine, path, namespace, version, data) {
await got(`${vaultUrl}/v1/${secretPath}`, { await got(`${vaultUrl}/v1/${secretPath}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
'X-Vault-Namespace': namespace, 'X-Vault-Namespace': namespace,
}, },
json: secretPayload json: secretPayload
+1615 -12636
View File
File diff suppressed because it is too large Load Diff
+10 -23
View File
@@ -8,23 +8,13 @@
"test": "jest", "test": "jest",
"test:integration:basic": "jest -c integrationTests/basic/jest.config.js", "test:integration:basic": "jest -c integrationTests/basic/jest.config.js",
"test:integration:enterprise": "jest -c integrationTests/enterprise/jest.config.js", "test:integration:enterprise": "jest -c integrationTests/enterprise/jest.config.js",
"test:e2e": "jest -c integrationTests/e2e/jest.config.js", "test:integration:e2e": "jest -c integrationTests/e2e/jest.config.js",
"test:e2e-tls": "jest -c integrationTests/e2e-tls/jest.config.js" "test:integration:e2e-tls": "jest -c integrationTests/e2e-tls/jest.config.js"
}, },
"files": [ "files": [
"src/**/*", "src/**/*",
"dist/**/*" "dist/**/*"
], ],
"release": {
"branch": "main",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/npm"
],
"ci": false
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/hashicorp/vault-action.git" "url": "git+https://github.com/hashicorp/vault-action.git"
@@ -44,21 +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": "^1.8.6", "jsonata": "^2.0.3",
"jsrsasign": "^10.5.25" "jsrsasign": "^10.8.6"
}, },
"peerDependencies": { "peerDependencies": {
"@actions/core": ">=1 <2" "@actions/core": ">=1 <2"
}, },
"devDependencies": { "devDependencies": {
"@actions/core": "^1.9.0", "@actions/core": "^1.10.0",
"@types/got": "^9.6.11", "@vercel/ncc": "^0.36.1",
"@types/jest": "^28.1.3", "jest": "^29.5.0",
"@zeit/ncc": "^0.22.3", "jest-when": "^3.5.2",
"jest": "^28.1.1", "mock-http-server": "^1.4.5"
"jest-when": "^3.5.1",
"mock-http-server": "^1.4.5",
"semantic-release": "^19.0.3"
} }
} }
+29 -21
View File
@@ -3,20 +3,27 @@ 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 { normalizeOutputKey } = require('./utils');
const { WILDCARD } = require('./constants');
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index'); const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index');
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes']; const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
async function exportSecrets() { async function exportSecrets() {
const vaultUrl = core.getInput('url', { required: true }); const vaultUrl = core.getInput('url', { required: true });
const vaultNamespace = core.getInput('namespace', { required: false }); const vaultNamespace = core.getInput('namespace', { required: false });
const extraHeaders = parseHeadersInput('extraHeaders', { required: false }); const extraHeaders = parseHeadersInput('extraHeaders', { required: false });
const exportEnv = core.getInput('exportEnv', { required: false }) != 'false'; const exportEnv = core.getInput('exportEnv', { required: false }) != 'false';
const outputToken = (core.getInput('outputToken', { required: false }) || 'false').toLowerCase() != 'false';
const exportToken = (core.getInput('exportToken', { required: false }) || 'false').toLowerCase() != 'false'; const exportToken = (core.getInput('exportToken', { required: false }) || 'false').toLowerCase() != 'false';
const secretsInput = core.getInput('secrets', { required: false }); const secretsInput = core.getInput('secrets', { required: false });
const secretRequests = parseSecretsInput(secretsInput); const secretRequests = parseSecretsInput(secretsInput);
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();
const authPayload = core.getInput('authPayload', { required: false }); const authPayload = core.getInput('authPayload', { required: false });
if (!AUTH_METHODS.includes(vaultMethod) && !authPayload) { if (!AUTH_METHODS.includes(vaultMethod) && !authPayload) {
@@ -66,11 +73,14 @@ async function exportSecrets() {
} }
const vaultToken = await retrieveToken(vaultMethod, got.extend(defaultOptions)); const vaultToken = await retrieveToken(vaultMethod, got.extend(defaultOptions));
core.setSecret(vaultToken)
defaultOptions.headers['X-Vault-Token'] = vaultToken; defaultOptions.headers['X-Vault-Token'] = vaultToken;
const client = got.extend(defaultOptions); const client = got.extend(defaultOptions);
if (outputToken === true) {
core.setOutput('vault_token', `${vaultToken}`);
}
if (exportToken === true) { if (exportToken === true) {
command.issue('add-mask', vaultToken);
core.exportVariable('VAULT_TOKEN', `${vaultToken}`); core.exportVariable('VAULT_TOKEN', `${vaultToken}`);
} }
@@ -81,14 +91,26 @@ async function exportSecrets() {
const results = await getSecrets(requests, client); const results = await getSecrets(requests, client);
for (const result of results) { for (const result of results) {
const { value, request, cachedResponse } = result; // Output the result
var value = result.value;
const request = result.request;
const cachedResponse = result.cachedResponse;
if (cachedResponse) { if (cachedResponse) {
core.debug(' using cached response'); core.debug(' using cached response');
} }
// if a secret is encoded, decode it
if (ENCODING_TYPES.includes(secretEncodingType)) {
value = Buffer.from(value, secretEncodingType).toString();
}
for (const line of value.replace(/\r/g, '').split('\n')) { for (const line of value.replace(/\r/g, '').split('\n')) {
if (line.length > 0) { if (line.length > 0) {
command.issue('add-mask', line); core.setSecret(line);
} }
} }
if (exportEnv) { if (exportEnv) {
@@ -152,7 +174,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}"`);
} }
@@ -172,20 +194,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
@@ -214,6 +222,6 @@ function parseHeadersInput(inputKey, inputOptions) {
module.exports = { module.exports = {
exportSecrets, exportSecrets,
parseSecretsInput, parseSecretsInput,
normalizeOutputKey, parseHeadersInput,
parseHeadersInput
}; };
+111 -6
View File
@@ -184,6 +184,17 @@ describe('exportSecrets', () => {
.mockReturnValueOnce(doExport); .mockReturnValueOnce(doExport);
} }
function mockOutputToken(doOutput) {
when(core.getInput)
.calledWith('outputToken', expect.anything())
.mockReturnValueOnce(doOutput);
}
function mockEncodeType(doEncode) {
when(core.getInput)
.calledWith('secretEncodingType', expect.anything())
.mockReturnValueOnce(doEncode);
}
it('simple secret retrieval', async () => { it('simple secret retrieval', async () => {
mockInput('test key'); mockInput('test key');
mockVaultData({ mockVaultData({
@@ -196,6 +207,68 @@ describe('exportSecrets', () => {
expect(core.setOutput).toBeCalledWith('key', '1'); expect(core.setOutput).toBeCalledWith('key', '1');
}); });
it('encoded secret retrieval', async () => {
mockInput('test key');
mockVaultData({
key: 'MQ=='
});
mockEncodeType('base64');
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', '1');
expect(core.setOutput).toBeCalledWith('key', '1');
});
it('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({
@@ -304,13 +377,36 @@ describe('exportSecrets', () => {
await exportSecrets(); await exportSecrets();
expect(command.issue).toBeCalledTimes(1); expect(core.setSecret).toBeCalledTimes(2);
expect(command.issue).toBeCalledWith('add-mask', 'secret'); expect(core.setSecret).toBeCalledWith('secret');
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
@@ -324,10 +420,10 @@ with blank lines
await exportSecrets(); await exportSecrets();
expect(command.issue).toBeCalledTimes(2); // 1 for each non-empty line. expect(core.setSecret).toBeCalledTimes(3); // 1 for each non-empty line.
expect(command.issue).toBeCalledWith('add-mask', 'a multi-line string'); expect(core.setSecret).toBeCalledWith('a multi-line string');
expect(command.issue).toBeCalledWith('add-mask', 'with blank lines'); expect(core.setSecret).toBeCalledWith('with blank lines');
expect(core.setOutput).toBeCalledWith('key', multiLineString); expect(core.setOutput).toBeCalledWith('key', multiLineString);
}) })
@@ -339,4 +435,13 @@ with blank lines
expect(core.exportVariable).toBeCalledTimes(1); expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('VAULT_TOKEN', 'EXAMPLE'); expect(core.exportVariable).toBeCalledWith('VAULT_TOKEN', 'EXAMPLE');
}) })
it('output only Vault token, no secrets', async () => {
mockOutputToken("true")
await exportSecrets();
expect(core.setOutput).toBeCalledTimes(1);
expect(core.setOutput).toBeCalledWith('vault_token', 'EXAMPLE');
})
}); });
+21 -3
View File
@@ -2,6 +2,7 @@
const core = require('@actions/core'); const core = require('@actions/core');
const rsasign = require('jsrsasign'); const rsasign = require('jsrsasign');
const fs = require('fs'); const fs = require('fs');
const { default: got } = require('got');
const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token' const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token'
/*** /***
@@ -10,7 +11,8 @@ const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccoun
* @param {import('got').Got} client * @param {import('got').Got} client
*/ */
async function retrieveToken(method, client) { async function retrieveToken(method, client) {
const path = core.getInput('path', { required: false }) || method; let path = core.getInput('path', { required: false }) || method;
path = `v1/auth/${path}/login`
switch (method) { switch (method) {
case 'approle': { case 'approle': {
@@ -49,6 +51,13 @@ async function retrieveToken(method, client) {
} }
return await getClientToken(client, method, path, { jwt: data, role: role }) return await getClientToken(client, method, path, { jwt: data, role: role })
} }
case 'userpass':
case 'ldap': {
const username = core.getInput('username', { required: true });
const password = core.getInput('password', { required: true });
path = path + `/${username}`
return await getClientToken(client, method, path, { password: password })
}
default: { default: {
if (!method || method === 'token') { if (!method || method === 'token') {
@@ -106,10 +115,19 @@ async function getClientToken(client, method, path, payload) {
responseType, responseType,
}; };
core.debug(`Retrieving Vault Token from v1/auth/${path}/login endpoint`); core.debug(`Retrieving Vault Token from ${path} endpoint`);
/** @type {import('got').Response<VaultLoginResponse>} */ /** @type {import('got').Response<VaultLoginResponse>} */
const response = await client.post(`v1/auth/${path}/login`, options); let response;
try {
response = await client.post(`${path}`, options);
} catch (err) {
if (err instanceof got.HTTPError) {
throw Error(`failed to retrieve vault token. code: ${err.code}, message: ${err.message}, vaultResponse: ${JSON.stringify(err.response.body)}`)
} else {
throw err
}
}
if (response && response.body && response.body.auth && response.body.auth.client_token) { if (response && response.body && response.body.auth && response.body.auth.client_token) {
core.debug('✔ Vault Token successfully retrieved'); core.debug('✔ Vault Token successfully retrieved');
+5
View File
@@ -0,0 +1,5 @@
const WILDCARD = '*';
module.exports = {
WILDCARD
};
+1
View File
@@ -5,6 +5,7 @@ const { exportSecrets } = require('./action');
try { try {
await core.group('Get Vault Secrets', exportSecrets); await core.group('Get Vault Secrets', exportSecrets);
} catch (error) { } catch (error) {
core.setOutput("errorMessage", error.message);
core.setFailed(error.message); core.setFailed(error.message);
} }
})(); })();
+89 -19
View File
@@ -1,6 +1,6 @@
const jsonata = require("jsonata"); const jsonata = require("jsonata");
const { WILDCARD } = require("./constants");
const { normalizeOutputKey } = require("./utils");
/** /**
* @typedef {Object} SecretRequest * @typedef {Object} SecretRequest
* @property {string} path * @property {string} path
@@ -23,7 +23,8 @@ const jsonata = require("jsonata");
*/ */
async function getSecrets(secretRequests, client) { async function getSecrets(secretRequests, client) {
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;
@@ -40,28 +41,59 @@ async function getSecrets(secretRequests, client) {
responseCache.set(requestPath, body); responseCache.set(requestPath, body);
} 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()}`) throw Error(`Unable to retrieve result for "${path}" because it was not found: ${response.body.trim()}`)
} }
throw error throw error
} }
} }
if (!selector.match(/.*[\.].*/)) {
selector = '"' + selector + '"' body = JSON.parse(body);
}
selector = "data." + selector if (selector == WILDCARD) {
body = JSON.parse(body) let keys = body.data;
if (body.data["data"] != undefined) { if (body.data["data"] != undefined) {
selector = "data." + selector keys = keys.data;
} }
const value = selectData(body, selector); for (let key in keys) {
results.push({ let newRequest = Object.assign({},secretRequest);
request: secretRequest, newRequest.selector = key;
value,
cachedResponse if (secretRequest.selector === secretRequest.outputVarName) {
}); newRequest.outputVarName = key;
newRequest.envVarName = key;
} }
else {
newRequest.outputVarName = secretRequest.outputVarName+key;
newRequest.envVarName = secretRequest.envVarName+key;
}
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName,true);
selector = key;
results = await selectAndAppendResults(
selector,
body,
cachedResponse,
newRequest,
results
);
}
}
else {
results = await selectAndAppendResults(
selector,
body,
cachedResponse,
secretRequest,
results
);
}
}
return results; return results;
} }
@@ -70,12 +102,13 @@ async function getSecrets(secretRequests, client) {
* @param {object} data * @param {object} data
* @param {string} selector * @param {string} selector
*/ */
function selectData(data, selector) { async function selectData(data, selector) {
const ata = jsonata(selector); const ata = jsonata(selector);
let result = JSON.stringify(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(jsonata(`data.${selector}`).evaluate(data)); result = JSON.stringify(await jsonata(`data.${selector}`).evaluate(data));
} else if (!result) { } else if (!result) {
throw Error(`Unable to retrieve result for ${selector}. No match data was found. Double check your Key or Selector.`); throw Error(`Unable to retrieve result for ${selector}. No match data was found. Double check your Key or Selector.`);
} }
@@ -86,6 +119,43 @@ 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
+19
View File
@@ -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
};