Compare commits

...

85 Commits

Author SHA1 Message Date
Srikrishna Iyer 892a26828f Update copywrite headers for v.4.0.0 release (#607)
Update copywrite headers
2026-05-12 20:12:44 +05:30
Srikrishna Iyer a7ffa26e2e Prepare for release v4.0.0 (#606) 2026-05-12 19:57:17 +05:30
hashicorp-copywrite[bot] a049f01838 [COMPLIANCE] Add/Update Copyright Headers (#605)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com>
Co-authored-by: Srikrishna Iyer <srikrishna.iyer@hashicorp.com>
2026-05-12 19:03:34 +05:30
Vijayavel Sekar 95977a3e23 Adding team-vault-consumption as CODEOWNERS (#600)
* Adding vault consumption team in code owner file

* Add newline

---------

Co-authored-by: Srikrishna Iyer <srikrishna.iyer@hashicorp.com>
2026-05-12 19:01:39 +05:30
Srikrishna Iyer 7e48e563b6 Upgrade Node.js to 24 and update dependencies (#604)
* chore: upgrade Node.js to 24 and update dependencies

- Upgrade Node.js from 20 to 24.15.0 across all CI jobs and workflows
- Run npm audit fix to resolve CVEs in dependencies
- Generate TLS certs dynamically via scripts/gen-tls-certs.sh instead of using static certs
- Add Makefile targets for running each integration test suite locally

* add GOPATH/bin to PATH before running gen-tls-certs.sh

* Add changelog entry

* refactor makefile

* Refine e2e-enterprise pipeline and scripts
2026-05-12 10:21:00 +05:30
oss-core-libraries-dashboard[bot] 79632e33d6 [COMPLIANCE] Add Copyright and License Headers (Batch 1 of 1) (#589)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2026-01-22 12:54:31 -05:00
Travis Rutledge 734c523c4f README.md: Removing jwtGithubAudience default (#590)
Removing the documented default value for the jwtGithubAudience parameter in the README.md to match the action.
2025-12-18 12:40:12 -06:00
compliance-pr-automation-bot[bot] 2c5827061f [Compliance] - PR Template Changes Required (#586)
Co-authored-by: compliance-pr-automation[bot] <1425255+compliance-pr-automation-bot[bot]@users.noreply.github.com>
2025-07-28 16:10:23 -07:00
John-Michael Faircloth 4c06c5ccf5 Prepare for v3.4.0 release (#584)
* Prepare for v3.4.0 release

* update dist
2025-06-13 11:30:03 -05:00
dependabot[bot] d07b4dc505 chore(deps): bump jsrsasign from 11.0.0 to 11.1.0 (#521)
Bumps [jsrsasign](https://github.com/kjur/jsrsasign) from 11.0.0 to 11.1.0.
- [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/11.0.0...11.1.0)

---
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>
2025-06-13 10:30:05 -05:00
John-Michael Faircloth 8ab17d80fa Prevent possible DoS via polynomial regex (#583) 2025-06-13 09:17:23 -05:00
John-Michael Faircloth b022ecdb0c fix: replace all dot chars during normalization (#580)
* fix: replace all dot chars during normalization

* changelog
2025-05-14 09:32:58 -05:00
Tom Chwojko-Frank 4d5899dd0e Adding codeowners file (#578) 2025-04-14 16:20:35 -07:00
Rory 7709c60978 feat: added double asterisk wildcard selector to prevent uppercasing of keys before exporting envs (#545)
* feat: added double asterisk wildcard selector to prevent uppercasing of keys before exporting envs

* chore: update changelog

---------

Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>
2025-03-03 15:31:00 -06:00
John-Michael Faircloth 4b1f32b395 Prepare for v3.2.0 release (#575)
* Prepare for v3.2.0 release

* changelog update
2025-03-03 14:18:41 -06:00
Andre Fuentes 5d06ce836f Retry core.getIDToken for JWT Auth Method (#574)
* adding generic async retry function and retry core.getIDToken

* adding test and build
2025-03-03 12:02:57 -06:00
kpcraig a1b77a0929 Prepare for v3.1.0 release (#571) 2025-01-09 13:09:19 -05:00
Dave Sewell 3b999aeea2 chore: Update dist/index for latest code (#568) 2024-12-20 17:41:11 -08:00
Dave Sewell c46b8b8822 chore: Update docker compose command (#567) 2024-12-06 12:47:10 -05:00
Dave Sewell 33b70ff01a feat: Add PKI capability (#564) 2024-12-05 12:22:45 -05:00
John-Michael Faircloth 8b7eaceb79 docs: add namespace example to readme (#562)
* docs: add namespace example to readme

* fix integration test jwt audience
2024-08-01 08:50:57 -05:00
John-Michael Faircloth 148ee648cc chore: add details on kvv2 path (#554) 2024-05-22 15:22:30 -05:00
John-Michael Faircloth 0f302fb182 chore: bump and pin github actions (#552) 2024-05-14 16:12:19 -05:00
John-Michael Faircloth 47dbc643a8 fix wildcard handling when field contains dot (#542)
* fix wildcard handling when field contains dot

* changelog
2024-04-15 16:42:20 -05:00
John-Michael Faircloth 66531b2752 Revert convert to esm (#544)
* Revert "update got dependency and convert to esm module (#533)"

This reverts commit 77efb36ae3.

* keep new local test file changes

* keep changes to PR template

* update changelog
2024-04-15 16:39:55 -05:00
dependabot[bot] ee41aa2fcf chore(deps): bump jsonata from 2.0.3 to 2.0.4 (#531)
* chore(deps): bump jsonata from 2.0.3 to 2.0.4

Bumps [jsonata](https://github.com/jsonata-js/jsonata) from 2.0.3 to 2.0.4.
- [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.3...v2.0.4)

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

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

* changelog

* changelog++

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JM Faircloth <jmfaircloth@hashicorp.com>
2024-03-22 09:18:20 -05:00
John-Michael Faircloth 77efb36ae3 update got dependency and convert to esm module (#533)
* update require got to import got

* convert remaining to esm

* wip: replace jest with vitest

* fix test imports and vitest config

* remove dist package.json

* fix import in ent test

* add dist

* move actions/core to prod dependency

* remove unused import that was breaking esm compilation

* simplify imports

* use module.createRequire to import jsonata

* add doc link comment

* add comments on import insanity

* add more comments

* update PR tempalte

* bump got and remove jest deps

* revert debug npm run command

* fix fs import

* simplify vitest config for each test suite
2024-03-19 10:42:34 -05:00
John-Michael Faircloth a727ce205a approle: do not require secret_id (#522)
* approle: support bind_secret_id

* add changelog
2024-02-28 11:15:56 -06:00
John-Michael Faircloth d1720f055e fix changelog link (#530) 2024-02-15 11:55:33 -06:00
John-Michael Faircloth 92626383ce Prepare for v3.0.0 release (#529) 2024-02-15 11:53:53 -06:00
John-Michael Faircloth 9c2d817b85 Fix changelog link (#528) 2024-02-15 11:42:04 -06:00
John-Michael Faircloth b477844b5f Prepare for v2.8.1 release (#527) 2024-02-15 11:38:07 -06:00
John-Michael Faircloth 9f522b8598 Update to v2.8.0 (#519) 2024-02-01 08:48:51 -06:00
John-Michael Faircloth efab57ede0 feature: add ignoreNotFound option (#518)
* add ignoreNotFound option

* update README
2024-02-01 08:42:56 -06:00
dependabot[bot] d523bb05b2 chore(deps-dev): bump @vercel/ncc from 0.36.1 to 0.38.1 (#503)
Bumps [@vercel/ncc](https://github.com/vercel/ncc) from 0.36.1 to 0.38.1.
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.36.1...0.38.1)

---
updated-dependencies:
- dependency-name: "@vercel/ncc"
  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>
2024-01-31 15:52:19 -06:00
dependabot[bot] 11845b19f6 chore(deps-dev): bump jest from 29.5.0 to 29.7.0 (#490)
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 29.5.0 to 29.7.0.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v29.7.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>
2024-01-31 15:33:32 -06:00
dependabot[bot] 7a6258bb0b chore(deps-dev): bump jest-when from 3.5.2 to 3.6.0 (#484)
Bumps [jest-when](https://github.com/timkindberg/jest-when) from 3.5.2 to 3.6.0.
- [Release notes](https://github.com/timkindberg/jest-when/releases)
- [Commits](https://github.com/timkindberg/jest-when/compare/v3.5.2...v3.6.0)

---
updated-dependencies:
- dependency-name: jest-when
  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>
2024-01-31 15:28:35 -06:00
dependabot[bot] a0b66b1cc3 chore(deps-dev): bump @actions/core from 1.10.0 to 1.10.1 (#489)
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.10.0 to 1.10.1.
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  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>
2024-01-31 15:26:53 -06:00
dependabot[bot] c616aba63e chore(deps): bump jsrsasign from 10.8.6 to 11.0.0 (#513)
Bumps [jsrsasign](https://github.com/kjur/jsrsasign) from 10.8.6 to 11.0.0.
- [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.8.6...11.0.0)

---
updated-dependencies:
- dependency-name: jsrsasign
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 15:26:26 -06:00
John-Michael Faircloth e3d5714d59 Update to v2.7.5 (#517) 2024-01-30 16:42:42 -06:00
Max Winslow 00bce0da9c Add path parameter example (#511)
* x

* Update README.md

Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>

---------

Co-authored-by: John-Michael Faircloth <fairclothjm@users.noreply.github.com>
2024-01-03 14:11:01 -06:00
Jean-François Roche 6853090cd9 Use nodejs 20 (#509)
Node 16 has reached its end of life

More details here: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2023-12-22 09:34:55 -05:00
John-Michael Faircloth 45dc5344f1 Update README.md (#510) 2023-12-21 11:14:42 -06:00
Calvin Leung Huang 2fb925f14c action: update marketplace name (#500) 2023-11-03 10:17:35 -07:00
Nicholas Farley caba6efd0e Bump got from 11.8.5 to 11.8.6 (#492)
* Bump got from 11.8.5 to 11.8.6
* Revert changes to dist/index.js
2023-11-01 10:27:02 -04:00
John-Michael Faircloth affa6f04da Update to v2.7.4 (#499) 2023-10-26 09:47:04 -05:00
John-Michael Faircloth 4727f0b168 chore: update readme reference for readability (#497) 2023-10-23 13:46:51 -05:00
Calvin Leung Huang 86c7f837eb Revert "Update action.yml (#495)" (#496)
This reverts commit 375956aa33.
2023-09-26 14:35:18 -07:00
Calvin Leung Huang 375956aa33 Update action.yml (#495)
Updates the Vault Secrets name to Vault in the GH Marketplace
2023-09-26 14:15:43 -07:00
Calvin Leung Huang 1328cd9fa9 Revert "Update Vault Action name in package.json (#493)" (#494)
This reverts commit d4437ee96c.
2023-09-26 14:14:07 -07:00
Calvin Leung Huang d4437ee96c Update Vault Action name in package.json (#493) 2023-09-26 14:12:24 -07: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
52 changed files with 5464 additions and 5428 deletions
+15 -7
View File
@@ -3,21 +3,29 @@ name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "[BUG] " title: "[BUG] "
labels: bug labels: bug
assignees: RichiCoder1
--- ---
**Describe the bug** ## Vault server version
v0.0.0
## vault-action version
v0.0.0
## Describe the bug
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To Reproduce** ## To Reproduce
The yaml of the `vault-action` step, with any sensitive information masked or removed. The yaml of the `vault-action` step, with any sensitive information masked or removed.
**Expected behavior** ## Expected behavior
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Log Output** ## Log Output
For the most verbose logs, [add a secret called `ACTIONS_STEP_DEBUG` with the value `true`](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md). Then, re-run the workflow if possible and post the *raw logs* for the step here with any sensitive information masked or removed. For the most verbose logs, add a secret called
[`ACTIONS_STEP_DEBUG`](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md)
with the value `true`. Then, re-run the workflow if possible and post the *raw
logs* for the step here with any sensitive information masked or removed.
**Additional context** ## Additional context
Add any other context about the problem here. Add any other context about the problem here.
+4 -5
View File
@@ -3,18 +3,17 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "[FEAT] " title: "[FEAT] "
labels: enhancement labels: enhancement
assignees: RichiCoder1
--- ---
**Is your feature request related to a problem? Please describe.** ## Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like** ## Describe the solution you'd like
A clear and concise description of what you want to happen. A clear and concise description of what you want to happen.
**Describe alternatives you've considered** ## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or features you've considered.
**Additional context** ## Additional context
Add any other context or screenshots about the feature request here. Add any other context or screenshots about the feature request here.
+32
View File
@@ -0,0 +1,32 @@
### Description
<!--- Description of the change. For example: This PR updates ABC resource so that we can XYZ --->
<!--- If your PR fully resolves and should automatically close the linked issue, use Closes. Otherwise, use Relates --->
Relates OR Closes #0000
### Checklist
- [ ] Added [CHANGELOG](https://github.com/hashicorp/vault-action/blob/master/CHANGELOG.md) entry (only for user-facing changes)
### Community Note
* Please vote on this pull request by adding a 👍
[reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)
to the original pull request comment to help the community and maintainers
prioritize this request
* Please do not leave "+1" comments, they generate extra noise for pull request
followers and do not help prioritize the request
## PCI review checklist
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
- [ ] I have documented a clear reason for, and description of, the change I am making.
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
- [ ] If applicable, I've documented the impact of any changes to security controls.
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
+8 -5
View File
@@ -1,11 +1,14 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options: # Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" # See documentation for possible values - package-ecosystem: "npm"
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
open-pull-requests-limit: 0 # only require security updates and exclude version updates
schedule: schedule:
interval: "daily" interval: "weekly"
# For got, ignore all updates since it is now native ESM
# see https://github.com/hashicorp/vault-action/pull/457#issuecomment-1601445634
ignore:
- dependency-name: "got"
+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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.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"
+237 -222
View File
@@ -1,289 +1,304 @@
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@v3 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: '16.14.0' node-version: "24.15.0"
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: NPM Install - name: NPM Install
run: npm ci run: npm ci
- name: NPM Build - name: NPM Build
run: npm run build run: npm run build
- name: NPM Run Test - name: NPM Run Test
run: npm run test run: npm run test
integrationOSS: integrationOSS:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run docker-compose - name: Run docker compose
run: docker-compose up -d vault run: docker compose up -d --wait vault
- uses: actions/setup-node@v3 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: '16.14.0' node-version: "24.15.0"
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: NPM Install - name: NPM Install
run: npm ci run: npm ci
- name: NPM Build - name: NPM Build
run: npm run build run: npm run build
- name: NPM Run test;integration:basic - name: NPM Run test;integration:basic
run: npm run test:integration:basic run: npm run test:integration:basic
env: env:
VAULT_HOST: localhost VAULT_HOST: 127.0.0.1
VAULT_PORT: 8200 VAULT_PORT: 8200
CI: true CI: true
integrationEnterprise: integrationEnterprise:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run docker-compose - name: Run docker compose
run: docker-compose up -d vault-enterprise if: ${{ !env.ACT }}
env: run: docker compose up -d --wait vault-enterprise
VAULT_LICENSE_CI: ${{ secrets.VAULT_LICENSE_CI }} env:
VAULT_LICENSE_CI: ${{ secrets.VAULT_LICENSE_CI }}
- uses: actions/setup-node@v3 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: '16.14.0' node-version: "24.15.0"
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: NPM Install - name: NPM Install
run: npm ci run: npm ci
- name: NPM Build - name: NPM Build
run: npm run build run: npm run build
- name: NPM Run test:integration:enterprise - name: NPM Run test:integration:enterprise
run: npm run test:integration:enterprise run: npm run test:integration:enterprise
env: env:
VAULT_HOST: localhost VAULT_HOST: 127.0.0.1
VAULT_PORT: 8200 VAULT_PORT: 8200
CI: true CI: true
e2e: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run docker-compose - name: Run docker compose
run: docker-compose up -d vault if: ${{ !env.ACT }}
run: docker compose up -d --wait vault
- uses: actions/setup-node@v3 - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with: with:
node-version: '16.14.0' node-version: "24.15.0"
- name: Setup NPM Cache - name: Setup NPM Cache
uses: actions/cache@v1 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: | restore-keys: |
${{ runner.os }}-node- ${{ runner.os }}-node-
- name: NPM Install - name: NPM Install
run: npm ci run: npm ci
- name: NPM Build - name: NPM Build
run: npm run build run: npm run build
- name: Setup Vault - name: Setup Vault
run: node ./integrationTests/e2e/setup.js run: node ./integrationTests/e2e/setup.js
env: env:
VAULT_HOST: localhost VAULT_HOST: localhost
VAULT_PORT: 8200 VAULT_PORT: 8200
- name: Test Vault Action (default KV V2) - name: Test Vault Action (default KV V2)
uses: ./ uses: ./
id: kv-secrets id: kv-secrets
with: with:
url: http://localhost:8200 url: http://localhost:8200
token: testtoken token: testtoken
secrets: | secrets: |
secret/data/test secret ; secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ; secret/data/test secret | NAMED_SECRET ;
secret/data/nested/test otherSecret ; secret/data/nested/test otherSecret ;
- name: Test Vault Action (default KV V1) - name: Test Vault Action (default KV V1)
uses: ./ uses: ./
with: with:
url: http://localhost:8200 url: http://localhost:8200
token: testtoken token: testtoken
secrets: | secrets: |
my-secret/test altSecret ; my-secret/test altSecret ;
my-secret/test altSecret | NAMED_ALTSECRET ; my-secret/test altSecret | NAMED_ALTSECRET ;
my-secret/nested/test otherAltSecret ; my-secret/nested/test otherAltSecret ;
- name: Test Vault Action (cubbyhole) - name: Test Vault Action (cubbyhole)
uses: ./ uses: ./
with: with:
url: http://localhost:8200 url: http://localhost:8200
token: testtoken token: testtoken
secrets: | secrets: |
/cubbyhole/test foo ; /cubbyhole/test foo ;
/cubbyhole/test zip | NAMED_CUBBYSECRET ; /cubbyhole/test zip | NAMED_CUBBYSECRET ;
- name: Verify Vault Action Outputs # The ordering of these two Test Vault Action Overwrites Env Vars In Subsequent Action steps matters
run: npm run test:e2e # They should come before the Verify Vault Action Outputs step
env: - name: Test Vault Action Overwrites Env Vars In Subsequent Action (part 1/2)
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }} uses: ./
with:
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
run: npm run test:integration:e2e
env:
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }}
e2e-tls: e2e-tls:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run docker-compose - name: Generate TLS Certificates
run: docker-compose up -d vault-tls if: ${{ !env.ACT }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_GET_RETRIES: 3
run: |
# Source the getGH function for authenticated GitHub downloads with retries
source ./scripts/.functions
getGH https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssl_1.6.5_linux_amd64 /usr/local/bin/cfssl
getGH https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssljson_1.6.5_linux_amd64 /usr/local/bin/cfssljson
chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson
./scripts/gen-tls-certs.sh
cat .build/e2e-tls.env >> "$GITHUB_ENV"
- uses: actions/setup-node@v3 - name: Run docker compose
with: if: ${{ !env.ACT }}
node-version: '16.14.0' run: docker compose up -d --wait vault-tls
- name: Setup NPM Cache - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
uses: actions/cache@v1 with:
with: node-version: "24.15.0"
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: NPM Install - name: Setup NPM Cache
run: npm ci uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: NPM Build - name: NPM Install
run: npm run build run: npm ci
- name: Setup Vault - name: NPM Build
run: node ./integrationTests/e2e-tls/setup.js run: npm run build
env:
VAULT_HOST: localhost
VAULT_PORT: 8200
VAULTCA: ${{ secrets.VAULTCA }}
VAULT_CLIENT_CERT: ${{ secrets.VAULT_CLIENT_CERT }}
VAULT_CLIENT_KEY: ${{ secrets.VAULT_CLIENT_KEY }}
- name: Test Vault Action (default KV V2) - name: Setup Vault
uses: ./ run: node ./integrationTests/e2e-tls/setup.js
id: kv-secrets env:
with: VAULT_HOST: localhost
url: https://localhost:8200 VAULT_PORT: 8200
token: ${{ env.VAULT_TOKEN }} VAULTCA: ${{ env.VAULTCA }}
caCertificate: ${{ secrets.VAULTCA }} VAULT_CLIENT_CERT: ${{ env.VAULT_CLIENT_CERT }}
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }} VAULT_CLIENT_KEY: ${{ env.VAULT_CLIENT_KEY }}
clientKey: ${{ secrets.VAULT_CLIENT_KEY }}
secrets: |
secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ;
secret/data/nested/test otherSecret ;
- name: Test Vault Action (tlsSkipVerify) - name: Test Vault Action (default KV V2)
uses: ./ uses: ./
with: id: kv-secrets-tls
url: https://localhost:8200 with:
token: ${{ env.VAULT_TOKEN }} url: https://localhost:8200
tlsSkipVerify: true token: ${{ env.VAULT_TOKEN }}
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }} caCertificate: ${{ env.VAULTCA }}
clientKey: ${{ secrets.VAULT_CLIENT_KEY }} clientCertificate: ${{ env.VAULT_CLIENT_CERT }}
secrets: | clientKey: ${{ env.VAULT_CLIENT_KEY }}
secret/data/tlsSkipVerify skip ; secrets: |
secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ;
secret/data/nested/test otherSecret ;
- name: Test Vault Action (default KV V1) - name: Test Vault Action (tlsSkipVerify)
uses: ./ uses: ./
with: with:
url: https://localhost:8200 url: https://localhost:8200
token: ${{ env.VAULT_TOKEN }} token: ${{ env.VAULT_TOKEN }}
caCertificate: ${{ secrets.VAULTCA }} tlsSkipVerify: true
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }} clientCertificate: ${{ env.VAULT_CLIENT_CERT }}
clientKey: ${{ secrets.VAULT_CLIENT_KEY }} clientKey: ${{ env.VAULT_CLIENT_KEY }}
secrets: | secrets: |
my-secret/test altSecret ; secret/data/tlsSkipVerify skip ;
my-secret/test altSecret | NAMED_ALTSECRET ;
my-secret/nested/test otherAltSecret ;
- name: Test Vault Action (cubbyhole) - name: Test Vault Action (default KV V1)
uses: ./ uses: ./
with: with:
url: https://localhost:8200 url: https://localhost:8200
token: ${{ env.VAULT_TOKEN }} token: ${{ env.VAULT_TOKEN }}
secrets: | caCertificate: ${{ env.VAULTCA }}
/cubbyhole/test foo ; clientCertificate: ${{ env.VAULT_CLIENT_CERT }}
/cubbyhole/test zip | NAMED_CUBBYSECRET ; clientKey: ${{ env.VAULT_CLIENT_KEY }}
caCertificate: ${{ secrets.VAULTCA }} secrets: |
clientCertificate: ${{ secrets.VAULT_CLIENT_CERT }} my-secret/test altSecret ;
clientKey: ${{ secrets.VAULT_CLIENT_KEY }} my-secret/test altSecret | NAMED_ALTSECRET ;
my-secret/nested/test otherAltSecret ;
- name: Verify Vault Action Outputs - name: Test Vault Action (cubbyhole)
run: npm run test:e2e-tls uses: ./
env: with:
OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets.outputs.otherSecret }} url: https://localhost:8200
token: ${{ env.VAULT_TOKEN }}
secrets: |
/cubbyhole/test foo ;
/cubbyhole/test zip | NAMED_CUBBYSECRET ;
caCertificate: ${{ env.VAULTCA }}
clientCertificate: ${{ env.VAULT_CLIENT_CERT }}
clientKey: ${{ env.VAULT_CLIENT_KEY }}
# Removing publish step for now. - name: Verify Vault Action Outputs
# publish: run: npm run test:integration:e2e-tls
# if: github.event_name == 'push' && contains(github.ref, 'main') env:
# runs-on: ubuntu-latest OTHER_SECRET_OUTPUT: ${{ steps.kv-secrets-tls.outputs.otherSecret }}
# 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 }}
+8 -63
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: with:
JIRA_BASE_URL: ${{ secrets.JIRA_SYNC_BASE_URL }} teams-array: '["ecosystem", "applications-eco"]'
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:
project: VAULT
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", "applications"], "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"
+73
View File
@@ -0,0 +1,73 @@
# 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
with:
node-version: '24.15.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: 127.0.0.1
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://127.0.0.1:8200
method: token
token: testtoken
secrets: |
secret/data/test-json-string jsonString;
secret/data/test-json-data jsonData;
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: "foobar"
script: |
const { JSONSTRING, JSONDATA } = process.env
console.log(`string ${JSONSTRING}`)
console.log(`data ${JSONDATA}`)
const str = JSONDATA
let valid = true
try {
JSON.parse(str)
} catch (e) {
valid = false
}
if (valid) {
console.log("valid json")
} else {
console.log("not valid json")
}
+6
View File
@@ -5,6 +5,9 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
# Build artifacts
.build/
# Runtime data # Runtime data
pids pids
*.pid *.pid
@@ -59,3 +62,6 @@ typings/
# next.js build output # next.js build output
.next .next
# GoLand IDE project files
.idea
+133 -7
View File
@@ -1,4 +1,130 @@
## Unreleased ## 4.0.0 (May 12, 2026)
Improvements:
* Bump node runtime from node20 to node24 [GH-604](https://github.com/hashicorp/vault-action/pull/604)
* Fix leading slash in secret paths causing HTTP 400 errors (e.g. `/cubbyhole/test``v1/cubbyhole/test` instead of `v1//cubbyhole/test`)
* bump jsrsasign from 11.1.0 to 11.1.3
* bump body-parser from 1.20.3 to 1.20.5
* bump qs from 6.13.0 to 6.15.1
* bump http-errors from 2.0.0 to 2.0.1
* bump minimatch from 3.1.2 to 3.1.5
* bump underscore from 1.13.4 to 1.13.8
## 3.4.0 (June 13, 2025)
Bugs:
* replace all dot chars during normalization (https://github.com/hashicorp/vault-action/pull/580)
Improvements:
* Prevent possible DoS via polynomial regex (https://github.com/hashicorp/vault-action/pull/583)
## 3.3.0 (March 3, 2025)
Features:
* Wildcard secret imports can use `**` to retain case of exported env keys [GH-545](https://github.com/hashicorp/vault-action/pull/545)
## 3.2.0 (March 3, 2025)
Improvements:
* Add retry for jwt auth login to fix intermittent login failures [GH-574](https://github.com/hashicorp/vault-action/pull/574)
## 3.1.0 (January 9, 2025)
Improvements:
* fix wildcard handling when field contains dot [GH-542](https://github.com/hashicorp/vault-action/pull/542)
* bump body-parser from 1.20.0 to 1.20.3
* bump braces from 3.0.2 to 3.0.3
* bump cross-spawn from 7.0.3 to 7.0.6
* bump micromatch from 4.0.5 to 4.0.8
Features:
* `secretId` is no longer required for approle to support advanced use cases like machine login when `bind_secret_id` is false. [GH-522](https://github.com/hashicorp/vault-action/pull/522)
* Use `pki` configuration to generate certificates from Vault [GH-564](https://github.com/hashicorp/vault-action/pull/564)
## 3.0.0 (February 15, 2024)
Improvements:
* Bump node runtime from node16 to node20 [GH-529](https://github.com/hashicorp/vault-action/pull/529)
## 2.8.1 (February 15, 2024)
Bugs:
* Revert [GH-509](https://github.com/hashicorp/vault-action/pull/509) which made a backwards incompatible bump of the node runtime from node16 to node20 [GH-527](https://github.com/hashicorp/vault-action/pull/527)
## 2.8.0 (February 1, 2024)
Features:
* Add `ignoreNotFound` input (default: false) to prevent the action from failing when a secret does not exist [GH-518](https://github.com/hashicorp/vault-action/pull/518)
Improvements:
* bump jsrsasign from 10.8.6 to 11.0.0 [GH-513](https://github.com/hashicorp/vault-action/pull/513)
* bump @actions/core from 1.10.0 to 1.10.1 [GH-489](https://github.com/hashicorp/vault-action/pull/489)
* bump jest-when from 3.5.2 to 3.6.0 [GH-484](https://github.com/hashicorp/vault-action/pull/484)
* bump jest from 29.5.0 to 29.7.0 [GH-490](https://github.com/hashicorp/vault-action/pull/490)
* bump @vercel/ncc from 0.36.1 to 0.38.1 [GH-503](https://github.com/hashicorp/vault-action/pull/503)
## 2.7.5 (January 30, 2024)
Improvements:
* Bump node runtime from node16 to node20 [GH-509](https://github.com/hashicorp/vault-action/pull/509)
* Bump got from 11.8.5 to 11.8.6 [GH-492](https://github.com/hashicorp/vault-action/pull/492)
## 2.7.4 (October 26, 2023)
Features:
* Add ability to specify a wildcard for the key name to get all keys in the path [GH-488](https://github.com/hashicorp/vault-action/pull/488)
## 2.7.3 (July 13, 2023)
Bugs:
* Revert to the handling of secrets in JSON format since v2.1.2 [GH-478](https://github.com/hashicorp/vault-action/pull/478)
## 2.7.2 (July 6, 2023)
Bugs:
* Fix a regression that broke support for secrets in JSON format [GH-473](https://github.com/hashicorp/vault-action/pull/473)
## 2.7.1 (July 3, 2023)
Bugs:
* Revert [GH-466](https://github.com/hashicorp/vault-action/pull/466) which caused a regression in secrets stored as JSON strings [GH-471](https://github.com/hashicorp/vault-action/pull/471)
## 2.7.0 (June 21, 2023)
Bugs:
* Fix a regression that broke support for secrets in JSON format [GH-466](https://github.com/hashicorp/vault-action/pull/466)
Improvements:
* Fix a warning about outputToken being an unexpected input [GH-461](https://github.com/hashicorp/vault-action/pull/461)
## 2.6.0 (June 7, 2023)
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) ## 2.5.0 (Jan 26th, 2023)
@@ -32,7 +158,7 @@ Bugs:
* Errors due to replication delay for tokens will now be retried [GH-333](https://github.com/hashicorp/vault-action/pull/333) * Errors due to replication delay for tokens will now be retried [GH-333](https://github.com/hashicorp/vault-action/pull/333)
Improvements: Improvements:
* bump got from 11.5.1 to 11.8.5 [GH-344](https://github.com/hashicorp/vault-action/pull/344) * bump got from 11.5.1 to 11.8.5 [GH-344](https://github.com/hashicorp/vault-action/pull/344)
## 2.4.1 (April 28th, 2022) ## 2.4.1 (April 28th, 2022)
@@ -40,11 +166,11 @@ Improvements:
* Make secrets parameter optional [GH-299](https://github.com/hashicorp/vault-action/pull/299) * Make secrets parameter optional [GH-299](https://github.com/hashicorp/vault-action/pull/299)
* auth/jwt: make "role" input optional [GH-291](https://github.com/hashicorp/vault-action/pull/291) * auth/jwt: make "role" input optional [GH-291](https://github.com/hashicorp/vault-action/pull/291)
* Write a better error message when secret not found [GH-306](https://github.com/hashicorp/vault-action/pull/306) * Write a better error message when secret not found [GH-306](https://github.com/hashicorp/vault-action/pull/306)
* bump jest-when from 2.7.2 to 3.5.1 [GH-294](https://github.com/hashicorp/vault-action/pull/294) * bump jest-when from 2.7.2 to 3.5.1 [GH-294](https://github.com/hashicorp/vault-action/pull/294)
* bump node-fetch from 2.6.1 to 2.6.7 [GH-308](https://github.com/hashicorp/vault-action/pull/308) * bump node-fetch from 2.6.1 to 2.6.7 [GH-308](https://github.com/hashicorp/vault-action/pull/308)
* bump @types/jest from 26.0.23 to 27.4.1 [GH-297](https://github.com/hashicorp/vault-action/pull/297) * bump @types/jest from 26.0.23 to 27.4.1 [GH-297](https://github.com/hashicorp/vault-action/pull/297)
* bump trim-off-newlines from 1.0.1 to 1.0.3 [GH-309](https://github.com/hashicorp/vault-action/pull/309) * bump trim-off-newlines from 1.0.1 to 1.0.3 [GH-309](https://github.com/hashicorp/vault-action/pull/309)
* bump moment from 2.28.0 to 2.29.2 [GH-304](https://github.com/hashicorp/vault-action/pull/304) * bump moment from 2.28.0 to 2.29.2 [GH-304](https://github.com/hashicorp/vault-action/pull/304)
* bump @types/got from 9.6.11 to 9.6.12 [GH-266](https://github.com/hashicorp/vault-action/pull/266) * bump @types/got from 9.6.11 to 9.6.12 [GH-266](https://github.com/hashicorp/vault-action/pull/266)
## 2.4.0 (October 21st, 2021) ## 2.4.0 (October 21st, 2021)
+1
View File
@@ -0,0 +1 @@
* @hashicorp/vault-ecosystem @hashicorp/team-vault-consumption
+40
View File
@@ -0,0 +1,40 @@
.PHONY: clean
clean:
rm -rf .build
.PHONY: local-test
local-test: clean
docker compose down --volumes; docker compose up --wait vault && \
act workflow_dispatch --job local-test --workflows .github/workflows/local-test.yaml
.PHONY: test-npm
test-npm:
npm ci && npm run build && npm run test
.PHONY: test-basic
test-basic: clean
docker compose down --volumes; docker compose up --wait vault && \
npm run test:integration:basic
.PHONY: test-e2e
test-e2e: clean
docker compose down --volumes; docker compose up --wait vault && \
act workflow_dispatch --job e2e --workflows .github/workflows/build.yml
.PHONY: test-e2e-tls
test-e2e-tls: clean
./scripts/gen-tls-certs.sh
docker compose down --volumes; docker compose up --wait vault-tls && \
act workflow_dispatch --job e2e-tls --workflows .github/workflows/build.yml --env-file .build/e2e-tls.env
.PHONY: test-enterprise
test-enterprise: clean
@if [ -z "$(VAULT_LICENSE_CI)" ]; then \
echo "Skipping enterprise tests: VAULT_LICENSE_CI not set"; \
else \
docker compose down --volumes; docker compose up --wait vault-enterprise && \
act workflow_dispatch --job integrationEnterprise --workflows .github/workflows/build.yml; \
fi
.PHONY: test-all
test-all: clean test-npm test-basic test-e2e test-e2e-tls test-enterprise
+476 -89
View File
@@ -8,6 +8,9 @@
A helper action for easily pulling secrets from HashiCorp Vault™. A helper action for easily pulling secrets from HashiCorp Vault™.
Note: The Vault Github Action is a read-only action, and in general
is not meant to modify Vaults state.
<!-- TOC --> <!-- TOC -->
- [Vault GitHub Action](#vault-github-action) - [Vault GitHub Action](#vault-github-action)
@@ -19,11 +22,15 @@ 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)
- [Custom Path](#custom-path-name)
- [Key Syntax](#key-syntax) - [Key Syntax](#key-syntax)
- [Simple Key](#simple-key) - [Simple Key](#simple-key)
- [Set Output Variable Name](#set-output-variable-name) - [Set Output Variable Name](#set-output-variable-name)
- [Multiple Secrets](#multiple-secrets) - [Multiple Secrets](#multiple-secrets)
- [KV secrets engine version 2](#kv-secrets-engine-version-2)
- [Other Secret Engines](#other-secret-engines) - [Other Secret Engines](#other-secret-engines)
- [Adding Extra Headers](#adding-extra-headers) - [Adding Extra Headers](#adding-extra-headers)
- [HashiCorp Cloud Platform or Vault Enterprise](#hashicorp-cloud-platform-or-vault-enterprise) - [HashiCorp Cloud Platform or Vault Enterprise](#hashicorp-cloud-platform-or-vault-enterprise)
@@ -31,6 +38,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 -->
@@ -38,23 +46,61 @@ A helper action for easily pulling secrets from HashiCorp Vault™.
```yaml ```yaml
jobs: jobs:
build: build:
# ... # ...
steps: steps:
# ... # ...
- name: Import Secrets - name: Import Secrets
uses: hashicorp/vault-action@v2 id: import-secrets
with: uses: hashicorp/vault-action@v2
url: https://vault.mycompany.com:8200 with:
token: ${{ secrets.VAULT_TOKEN }} url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }} token: ${{ secrets.VAULT_TOKEN }}
secrets: | caCertificate: ${{ secrets.VAULT_CA_CERT }}
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ; secrets: |
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ; secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/data/ci npm_token secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
# ... secret/data/ci npm_token
# ...
``` ```
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
@@ -68,7 +114,7 @@ and Vault using the
Each GitHub Actions workflow receives an auto-generated OIDC token with claims Each GitHub Actions workflow receives an auto-generated OIDC token with claims
to establish the identity of the workflow. to establish the identity of the workflow.
__Vault Configuration__ **Vault Configuration**
<details> <details>
<summary>Click to toggle instructions for configuring Vault.</summary> <summary>Click to toggle instructions for configuring Vault.</summary>
@@ -79,7 +125,6 @@ Pass the following parameters to your auth method configuration:
- `oidc_discovery_url`: `https://token.actions.githubusercontent.com` - `oidc_discovery_url`: `https://token.actions.githubusercontent.com`
- `bound_issuer`: `https://token.actions.githubusercontent.com` - `bound_issuer`: `https://token.actions.githubusercontent.com`
Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) for the auth method. Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) for the auth method.
- `role_type`: `jwt` - `role_type`: `jwt`
@@ -95,12 +140,12 @@ Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) f
- For wildcard (non-exact) matches, use `bound_claims`. - For wildcard (non-exact) matches, use `bound_claims`.
- `bound_claims_type`: `glob` - `bound_claims_type`: `glob`
- `bound_claims`: JSON object. Maps one or more claim names to corresponding wildcard values. - `bound_claims`: JSON object. Maps one or more claim names to corresponding wildcard values.
```json ```json
{"sub": "repo:<orgName>/*"} { "sub": "repo:<orgName>/*" }
``` ```
- For exact matches, use `bound_subject`. - For exact matches, use `bound_subject`.
@@ -113,17 +158,17 @@ Configure a [Vault role](https://www.vaultproject.io/api/auth/jwt#create-role) f
</details> </details>
__GitHub Actions Workflow__ **GitHub Actions Workflow**
In the GitHub Actions workflow, the workflow needs permissions to read contents In the GitHub Actions workflow, the workflow needs permissions to read contents
and write the ID token. and write the ID token.
```yaml ```yaml
jobs: jobs:
retrieve-secret: retrieve-secret:
permissions: permissions:
contents: read contents: read
id-token: write id-token: write
``` ```
In the action, provide the name of the Vault role you created to the `role` parameter. In the action, provide the name of the Vault role you created to the `role` parameter.
@@ -221,16 +266,65 @@ 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
attempt to `POST` to `auth/${method}/login` with the provided payload and parse out the client token. attempt to `POST` to `auth/${method}/login` with the provided payload and parse out the client token.
### Custom Path Name
Auth methods at custom path names can be configured using the [`path`](#path) parameter
```yaml
with:
url: https://vault.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }}
path: my-custom-path
method: userpass
username: ${{ secrets.VAULT_USERNAME }}
password: ${{ secrets.VAULT_PASSWORD }}
```
## Key Syntax ## Key Syntax
The `secrets` parameter is a set of multiple secret requests separated by the `;` character. The `secrets` parameter is a set of multiple secret requests separated by the `;` character.
Each secret request consists of the `path` and the `key` of the desired secret, and optionally the desired Env Var output name. Each secret request consists of the `path` and the `key` of the desired secret, and optionally the desired Env Var output name.
Note that the selector is using [JSONata](https://docs.jsonata.org/overview.html) and certain characters in keys may need to be escaped.
```raw ```raw
{{ Secret Path }} {{ Secret Key or Selector }} | {{ Env/Output Variable Name }} {{ Secret Path }} {{ Secret Key or Selector }} | {{ Env/Output Variable Name }}
@@ -242,7 +336,7 @@ To retrieve a key `npmToken` from path `secret/data/ci` that has value `somelong
```yaml ```yaml
with: with:
secrets: secret/data/ci npmToken secrets: secret/data/ci npmToken
``` ```
`vault-action` will automatically normalize the given secret selector key, and set the follow as environment variables for the following steps in the current job: `vault-action` will automatically normalize the given secret selector key, and set the follow as environment variables for the following steps in the current job:
@@ -255,12 +349,12 @@ You can also access the secret via outputs:
```yaml ```yaml
steps: steps:
# ... # ...
- name: Import Secrets - name: Import Secrets
id: secrets id: secrets
# Import config... # Import config...
- name: Sensitive Operation - name: Sensitive Operation
run: "my-cli --token '${{ steps.secrets.outputs.npmToken }}'" run: "my-cli --token '${{ steps.secrets.outputs.npmToken }}'"
``` ```
_**Note:** If you'd like to only use outputs and disable automatic environment variables, you can set the `exportEnv` option to `false`._ _**Note:** If you'd like to only use outputs and disable automatic environment variables, you can set the `exportEnv` option to `false`._
@@ -271,7 +365,7 @@ However, if you want to set it to a specific name, say `NPM_TOKEN`, you could do
```yaml ```yaml
with: with:
secrets: secret/data/ci npmToken | NPM_TOKEN secrets: secret/data/ci npmToken | NPM_TOKEN
``` ```
With that, `vault-action` will now use your requested name and output: With that, `vault-action` will now use your requested name and output:
@@ -288,7 +382,6 @@ steps:
# Import config... # Import config...
- name: Sensitive Operation - name: Sensitive Operation
run: "my-cli --token '${{ steps.secrets.outputs.NPM_TOKEN }}'" run: "my-cli --token '${{ steps.secrets.outputs.NPM_TOKEN }}'"
``` ```
### Multiple Secrets ### Multiple Secrets
@@ -297,25 +390,81 @@ This action can take multi-line input, so say you had your AWS keys stored in a
```yaml ```yaml
with: with:
secrets: | secrets: |
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ; secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
``` ```
You can specify a wildcard \* for the key name to get all keys in the path. If you provide an output name with the wildcard, the name will be prepended to the key name:
```yaml
with:
secrets: |
secret/data/ci/aws * | MYAPP_ ;
```
When using the `exportEnv` option all exported keys will be normalized to uppercase. For example, the key `SecretKey` would be exported as `MYAPP_SECRETKEY`.
You can disable uppercase normalization by specifying double asterisks `**` in the selector path:
```yaml
with:
secrets: |
secret/data/ci/aws ** | MYAPP_ ;
```
### KV secrets engine version 2
When accessing secrets from the KV secrets engine version 2, Vault Action
requires the full path to the secret. This is the same path that would be used
in a Vault policy for the secret. You can find the full path to your secret by
performing a `kv get` command like the following:
```bash
$ vault kv get secret/test
== Secret Path ==
secret/data/test
...
```
Note that the full path is not `secret/test`, but `secret/data/test`.
## PKI Certificate Requests
You can use the `pki` option to generate a certificate and private key for a given role.
````yaml
with:
pki: |
pki/issue/rolename {"common_name": "role.mydomain.com", "ttl": "1h"} ;
pki/issue/otherrole {"common_name": "otherrole.mydomain.com", "ttl": "1h"} ;
```
Resulting in:
```bash
ROLENAME_CA=-----BEGIN CERTIFICATE-----...
ROLENAME_CERT=-----BEGIN CERTIFICATE-----...
ROLENAME_KEY=-----BEGIN RSA PRIVATE KEY-----...
ROLENAME_CA_CHAIN=-----BEGIN CERTIFICATE-----...
OTHERROLE_CA=-----BEGIN CERTIFICATE-----...
OTHERROLE_CERT=-----BEGIN CERTIFICATE-----...
OTHERROLE_KEY=-----BEGIN RSA PRIVATE KEY-----...
OTHERROLE_CA_CHAIN=-----BEGIN CERTIFICATE-----...
````
## Other Secret Engines ## Other Secret Engines
Vault Action currently supports retrieving secrets from any engine where secrets Vault Action currently supports retrieving secrets from any engine where secrets
are retrieved via `GET` requests. This means secret engines such as PKI are currently are retrieved via `GET` requests, except for the PKI engine as noted above.
not supported due to their requirement of sending parameters along with the request
(such as `common_name`).
For example, to request a secret from the `cubbyhole` secret engine: For example, to request a secret from the `cubbyhole` secret engine:
```yaml ```yaml
with: with:
secrets: | secrets: |
/cubbyhole/foo foo ; /cubbyhole/foo foo ;
/cubbyhole/foo zip | MY_KEY ; /cubbyhole/foo zip | MY_KEY ;
``` ```
Resulting in: Resulting in:
@@ -343,12 +492,12 @@ If you ever need to add extra headers to the vault request, say if you need to a
```yaml ```yaml
with: with:
secrets: | secrets: |
secret/ci/aws accessKey | AWS_ACCESS_KEY_ID ; secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/ci/aws secretKey | AWS_SECRET_ACCESS_KEY secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
extraHeaders: | extraHeaders: |
X-Secure-Id: ${{ secrets.SECURE_ID }} X-Secure-Id: ${{ secrets.SECURE_ID }}
X-Secure-Secret: ${{ secrets.SECURE_SECRET }} X-Secure-Secret: ${{ secrets.SECURE_SECRET }}
``` ```
This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to Vault. This will automatically add the `x-secure-id` and `x-secure-secret` headers to every request to Vault.
@@ -366,50 +515,215 @@ parameter specifying the namespace. In HCP Vault, the namespace defaults to `adm
```yaml ```yaml
steps: steps:
# ... # ...
- name: Import Secrets - name: Import Secrets
uses: hashicorp/vault-action uses: hashicorp/vault-action
with: with:
url: https://vault-enterprise.mycompany.com:8200 url: https://vault-enterprise.mycompany.com:8200
caCertificate: ${{ secrets.VAULT_CA_CERT }} method: token
method: token token: ${{ secrets.VAULT_TOKEN }}
token: ${{ secrets.VAULT_TOKEN }} namespace: admin
namespace: admin secrets: |
secrets: | secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/ci/aws accessKey | AWS_ACCESS_KEY_ID ; secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
secret/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ; secret/data/ci npm_token
secret/ci npm_token ```
Alternatively, you may need to authenticate to the root namespace and retrieve
a secret from a different namespace. To do this, do not set the `namespace`
parameter. Instead set the namespace in the secret path. For example, `<NAMESPACE>/secret/data/app`:
```yaml
steps:
# ...
- name: Import Secrets
uses: hashicorp/vault-action
with:
url: https://vault-enterprise.mycompany.com:8200
method: token
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
namespace-1/secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
namespace-1/secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY ;
namespace-1/secret/data/ci npm_token
``` ```
## Reference ## Reference
Here are all the inputs available through `with`: Here are all the inputs available through `with`:
| Input | Description | Default | Required | ### `url`
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `url` | The URL for the vault endpoint | | ✔ | **Type: `string`**\
| `secrets` | A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details | | | **Required**
| `namespace` | The Vault namespace from which to query secrets. Vault Enterprise only, unset by default | | |
| `method` | The method to use to authenticate with Vault. | `token` | | The URL for the Vault endpoint.
| `role` | Vault role for specified auth method | | |
| `path` | Custom vault path, if the auth method was enabled at a different path | | | ### `secrets`
| `token` | The Vault Token to be used to authenticate with Vault | | |
| `roleId` | The Role Id for App Role authentication | | | **Type: `string`**
| `secretId` | The Secret Id for App Role authentication | | |
| `githubToken` | The Github Token to be used to authenticate with Vault | | | A semicolon-separated list of secrets to retrieve. These will automatically be
| `jwtPrivateKey` | Base64 encoded Private key to sign JWT | | | converted to environmental variable keys. See [Key Syntax](#key-syntax) for
| `jwtKeyPassword` | Password for key stored in jwtPrivateKey (if needed) | | | more details.
| `jwtGithubAudience` | Identifies the recipient ("aud" claim) that the JWT is intended for |`sigstore`| |
| `jwtTtl` | Time in seconds, after which token expires | | 3600 | ### `namespace`
| `kubernetesTokenPath` | The path to the service-account secret with the jwt token for kubernetes based authentication |`/var/run/secrets/kubernetes.io/serviceaccount/token` | |
| `authPayload` | The JSON payload to be sent to Vault when using a custom authentication method. | | | **Type: `string`**
| `extraHeaders` | A string of newline separated extra headers to include on every request. | | |
| `exportEnv` | Whether or not export secrets as environment variables. | `true` | | The Vault namespace from which to query secrets. Vault Enterprise only, unset by default.
| `exportToken` | Whether or not export Vault token as environment variables (i.e VAULT_TOKEN). | `false` | |
| `caCertificate` | Base64 encoded CA certificate the server certificate was signed with. | | | ### `method`
| `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. | | | **Type: `string`**\
| `tlsSkipVerify` | When set to true, disables verification of server certificates when testing the action. | `false` | | **Default: `token`**
The method to use to authenticate with Vault.
### `role`
**Type: `string`**
Vault role for the specified auth method.
### `path`
**Type: `string`**
The Vault path for the auth method.
### `token`
**Type: `string`**
The Vault token to be used to authenticate with Vault.
### `roleId`
**Type: `string`**
The role ID for App Role authentication.
### `secretId`
**Type: `string`**
The secret ID for App Role authentication.
### `githubToken`
**Type: `string`**
The Github Token to be used to authenticate with Vault.
### `jwtPrivateKey`
**Type: `string`**
Base64 encoded private key to sign the JWT.
### `jwtKeyPassword`
**Type: `string`**
Password for key stored in `jwtPrivateKey` (if needed).
### `jwtGithubAudience`
**Type: `string`**
Identifies the recipient ("aud" claim) that the JWT is intended for.
### `jwtTtl`
**Type: `string`**\
**Default: `3600`**
Time in seconds, after which token expires.
### `kubernetesTokenPath`
**Type: `string`**\
**Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`**
The path to the service-account secret with the jwt token for kubernetes based authentication.
### `username`
**Type: `string`**
The username of the user to log in to Vault as. Available to both Userpass and LDAP auth methods.
### `password`
**Type: `string`**
The password of the user to log in to Vault as. Available to both Userpass and LDAP auth methods.
### `authPayload`
**Type: `string`**
The JSON payload to be sent to Vault when using a custom authentication method.
### `extraHeaders`
**Type: `string`**
A string of newline separated extra headers to include on every request.
### `exportEnv`
**Type: `string`**\
**Default: `true`**
Whether or not to export secrets as environment variables.
### `exportToken`
**Type: `string`**\
**Default: `false`**
Whether or not export Vault token as environment variables (i.e VAULT_TOKEN).
### `outputToken`
**Type: `string`**\
**Default: `false`**
Whether or not to set the `vault_token` output to contain the Vault token after authentication.
### `caCertificate`
**Type: `string`**
Base64 encoded CA certificate the server certificate was signed with. Defaults to CAs provided by Mozilla.
### `clientCertificate`
**Type: `string`**
Base64 encoded client certificate the action uses to authenticate with Vault when mTLS is enabled.
### `clientKey`
**Type: `string`**
Base64 encoded client key the action uses to authenticate with Vault when mTLS is enabled.
### `tlsSkipVerify`
**Type: `string`**\
**Default: `false`**
When set to true, disables verification of server certificates when testing the action.
### `ignoreNotFound`
**Type: `string`**\
**Default: `false`**
When set to true, prevents the action from failing when a secret does not exist.
## Masking - Hiding Secrets from Logs ## Masking - Hiding Secrets from Logs
@@ -419,3 +733,76 @@ 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
```
+27 -7
View File
@@ -1,4 +1,7 @@
name: 'Vault Secrets' # Copyright IBM Corp. 2019, 2026
# SPDX-License-Identifier: MIT
name: 'HashiCorp Vault'
description: 'A Github Action that allows you to consume HashiCorp Vault™ secrets as secure environment variables' description: 'A Github Action that allows you to consume HashiCorp Vault™ secrets as secure environment variables'
inputs: inputs:
url: url:
@@ -7,6 +10,9 @@ inputs:
secrets: secrets:
description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details' description: 'A semicolon-separated list of secrets to retrieve. These will automatically be converted to environmental variable keys. See README for more details'
required: false required: false
pki:
description: 'A semicolon-separated list of certificates to generate. These will automatically be converted to environment variable keys. Cannot be used with "secrets". See README for more details'
required: false
namespace: namespace:
description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default' description: 'The Vault namespace from which to query secrets. Vault Enterprise only, unset by default'
required: false required: false
@@ -18,16 +24,16 @@ inputs:
description: 'Vault role for specified auth method' description: 'Vault role for specified auth method'
required: false required: false
path: path:
description: 'Custom Vault path, if the auth method was mounted at a different path' description: 'The Vault path for the auth method.'
required: false required: false
token: token:
description: 'The Vault Token to be used to authenticate with Vault' description: 'The Vault token to be used to authenticate with Vault'
required: false required: false
roleId: roleId:
description: 'The Role Id for App Role authentication' description: 'The role ID for App Role authentication'
required: false required: false
secretId: secretId:
description: 'The Secret Id for App Role authentication' description: 'The secret ID for App Role authentication'
required: false required: false
githubToken: githubToken:
description: 'The Github Token to be used to authenticate with Vault' description: 'The Github Token to be used to authenticate with Vault'
@@ -36,6 +42,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,8 +62,12 @@ inputs:
description: 'Whether or not export Vault token as environment variables.' description: 'Whether or not export Vault token as environment variables.'
default: 'false' default: 'false'
required: false required: false
outputToken:
description: 'Whether or not to set the `vault_token` output to contain the Vault token after authentication.'
default: 'false'
required: false
caCertificate: caCertificate:
description: 'Base64 encoded CA certificate to verify the Vault server certificate.' description: 'Base64 encoded CA certificate the server certificate was signed with. Defaults to CAs provided by Mozilla.'
required: false required: false
clientCertificate: clientCertificate:
description: 'Base64 encoded client certificate for mTLS communication with the Vault server.' description: 'Base64 encoded client certificate for mTLS communication with the Vault server.'
@@ -79,8 +95,12 @@ inputs:
secretEncodingType: secretEncodingType:
description: 'The encoding type of the secret to decode. If not specified, the secret will not be decoded. Supported values: base64, hex, utf8' description: 'The encoding type of the secret to decode. If not specified, the secret will not be decoded. Supported values: base64, hex, utf8'
required: false required: false
ignoreNotFound:
description: 'Whether or not the action should exit successfully if some requested secrets were not found.'
required: false
default: 'false'
runs: runs:
using: 'node16' using: 'node24'
main: 'dist/index.js' main: 'dist/index.js'
branding: branding:
icon: 'unlock' icon: 'unlock'
+653 -281
View File
File diff suppressed because one or more lines are too long
+38 -8
View File
@@ -1,33 +1,63 @@
# Copyright IBM Corp. 2019, 2026
# SPDX-License-Identifier: MIT
# Start vault server locally for the purposes of integration tests. # Start vault server locally for the purposes of integration tests.
version: "3.0" version: "3.0"
services: services:
vault: vault:
image: vault:latest image: hashicorp/vault:2.0.0
environment: environment:
VAULT_DEV_ROOT_TOKEN_ID: testtoken VAULT_DEV_ROOT_TOKEN_ID: testtoken
SKIP_SETCAP: "true"
VAULT_LOCAL_CONFIG: '{"disable_mlock": true}'
ports: ports:
- 8200:8200 - 8200:8200
privileged: true privileged: true
healthcheck:
test: ["CMD-SHELL", "VAULT_ADDR=http://127.0.0.1:8200 vault status"]
interval: 1s
timeout: 5s
retries: 5
vault-enterprise: vault-enterprise:
image: hashicorp/vault-enterprise:latest image: hashicorp/vault-enterprise:2.0-ent
environment: environment:
VAULT_DEV_ROOT_TOKEN_ID: testtoken VAULT_DEV_ROOT_TOKEN_ID: testtoken
VAULT_LICENSE: ${VAULT_LICENSE_CI} VAULT_LICENSE: ${VAULT_LICENSE_CI}
SKIP_SETCAP: "true"
VAULT_LOCAL_CONFIG: '{"disable_mlock": true}'
ports: ports:
- 8200:8200 - 8200:8200
privileged: true privileged: true
healthcheck:
test: ["CMD-SHELL", "VAULT_ADDR=http://127.0.0.1:8200 vault status"]
interval: 1s
timeout: 5s
retries: 30
vault-tls: vault-tls:
image: vault:latest image: hashicorp/vault:2.0.0
hostname: vault-tls hostname: vault-tls
environment: environment:
# Used by the vault CLI in the healthcheck to trust the CA
VAULT_CAPATH: /etc/vault/ca.crt VAULT_CAPATH: /etc/vault/ca.crt
SKIP_SETCAP: "true"
VAULT_LOCAL_CONFIG: '{"disable_mlock": true}'
ports: ports:
- 8200:8200 - 8200:8200
privileged: true privileged: true
healthcheck:
# Exit 2 means sealed-but-running, which is acceptable during startup
test:
- CMD-SHELL
- |
export VAULT_ADDR=https://127.0.0.1:8200 VAULT_CACERT=/etc/vault/ca.crt VAULT_CLIENT_CERT=/etc/vault/client.crt VAULT_CLIENT_KEY=/etc/vault/client.key
vault status; s=$$?; [ $$s -eq 0 ] || [ $$s -eq 2 ]
interval: 1s
timeout: 5s
retries: 30
volumes: volumes:
- ${PWD}/integrationTests/e2e-tls/configs:/etc/vault # Certs generated by scripts/gen-tls-certs.sh into .build/certs/
- vault-data:/var/lib/vault:rw - ${PWD}/.build/certs:/etc/vault
# tmpfs gives the non-root vault user write access without chown tricks;
# ephemeral storage is fine since tests always reinitialize vault from scratch
tmpfs: /var/lib/vault
entrypoint: vault server -config=/etc/vault/config.hcl entrypoint: vault server -config=/etc/vault/config.hcl
volumes:
vault-data:
+139
View File
@@ -0,0 +1,139 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
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');
})
});
+242 -11
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('@actions/core'); jest.mock('@actions/core');
jest.mock('@actions/core/lib/command'); jest.mock('@actions/core/lib/command');
const core = require('@actions/core'); const core = require('@actions/core');
@@ -8,20 +13,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: {
@@ -30,10 +36,26 @@ describe('integration', () => {
}, },
}); });
await got(`${vaultUrl}/v1/secret/data/test-with-dot-char`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
body: `{"data":{"secret.foo":"SUPERSECRET"}}`
});
await got(`${vaultUrl}/v1/secret/data/test-with-multi-dot-chars`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
body: `{"data":{"secret.foo.bar":"SUPERSECRET"}}`
});
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 +67,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 +81,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 +99,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 +109,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,12 +119,75 @@ 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',
}, },
}); });
// Enable pki engine
try {
await got(`${vaultUrl}/v1/sys/mounts/pki`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
type: 'pki'
}
});
} catch (error) {
const {response} = error;
if (response.statusCode === 400 && response.body.includes("path is already in use")) {
// Engine might already be enabled from previous test runs
} else {
throw error;
}
}
// Configure Root CA
try {
await got(`${vaultUrl}/v1/pki/root/generate/internal`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
common_name: 'test',
ttl: '24h',
},
});
} catch (error) {
const {response} = error;
if (response.statusCode === 400 && response.body.includes("already exists")) {
// Root CA might already be configured from previous test runs
} else {
throw error;
}
}
// Configure PKI Role
try {
await got(`${vaultUrl}/v1/pki/roles/Test`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
allowed_domains: ['test'],
allow_bare_domains: true,
max_ttl: '1h',
},
});
} catch (error) {
const {response} = error;
if (response.statusCode === 400 && response.body.includes("already exists")) {
// Role might already be configured from previous test runs
} else {
throw error;
}
}
}); });
beforeEach(() => { beforeEach(() => {
@@ -114,7 +199,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) {
@@ -123,14 +208,55 @@ describe('integration', () => {
.mockReturnValueOnce(secrets); .mockReturnValueOnce(secrets);
} }
function mockPkiInput(pki) {
when(core.getInput)
.calledWith('pki', expect.anything())
.mockReturnValueOnce(pki);
}
function mockIgnoreNotFound(shouldIgnore) {
when(core.getInput)
.calledWith('ignoreNotFound', expect.anything())
.mockReturnValueOnce(shouldIgnore);
}
it('prints a nice error message when secret not found', async () => { it('prints a nice error message when secret not found', async () => {
mockInput(`secret/data/test secret ; mockInput(`secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ; secret/data/test secret | NAMED_SECRET ;
secret/data/notFound kehe | NO_SIR ;`); secret/data/notFound kehe | NO_SIR ;`);
expect(exportSecrets()).rejects.toEqual(Error(`Unable to retrieve result for "secret/data/notFound" because it was not found: {"errors":[]}`)); await expect(exportSecrets()).rejects.toEqual(Error(`Unable to retrieve result for "secret/data/notFound" because it was not found: {"errors":[]}`));
}) })
it('does not error when secret not found and ignoreNotFound is true', async () => {
mockInput(`secret/data/test secret ;
secret/data/test secret | NAMED_SECRET ;
secret/data/notFound kehe | NO_SIR ;`);
mockIgnoreNotFound("true");
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(2);
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
expect(core.exportVariable).toBeCalledWith('NAMED_SECRET', 'SUPERSECRET');
})
it('gets a pki certificate', async () => {
mockPkiInput('pki/issue/Test {"common_name":"test","ttl":"1h"}');
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(4);
expect(core.exportVariable).toBeCalledWith('TEST_KEY', expect.anything());
expect(core.exportVariable).toBeCalledWith('TEST_CERT', expect.anything());
expect(core.exportVariable).toBeCalledWith('TEST_CA', expect.anything());
expect(core.exportVariable).toBeCalledWith('TEST_CA_CHAIN', expect.anything());
});
it('get simple secret', async () => { it('get simple secret', async () => {
mockInput('secret/data/test secret'); mockInput('secret/data/test secret');
@@ -170,6 +296,46 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET'); expect(core.exportVariable).toBeCalledWith('OTHERSECRETDASH', 'OTHERSUPERSECRET');
}); });
it('get wildcard secrets with dot char', async () => {
mockInput(`secret/data/test-with-dot-char * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET__FOO', 'SUPERSECRET');
});
it('get secrets with multiple dot chars', async () => {
mockInput(`secret/data/test-with-multi-dot-chars * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET__FOO__BAR', 'SUPERSECRET');
});
it('get wildcard secrets', async () => {
mockInput(`secret/data/test * ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('SECRET', 'SUPERSECRET');
});
it('get wildcard secrets with name prefix', async () => {
mockInput(`secret/data/test * | GROUP_ ;`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(1);
expect(core.exportVariable).toBeCalledWith('GROUP_SECRET', 'SUPERSECRET');
});
it('leading slash kvv2', async () => { it('leading slash kvv2', async () => {
mockInput('/secret/data/foobar fookv2'); mockInput('/secret/data/foobar fookv2');
@@ -194,6 +360,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 +401,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 +418,43 @@ describe('integration', () => {
expect(core.exportVariable).toBeCalledWith('FOO', 'bar'); expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
}); });
it('wildcard supports cubbyhole with uppercase transform', async () => {
mockInput('/cubbyhole/test *');
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(2);
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
});
it('wildcard supports cubbyhole with no change in case', async () => {
mockInput('/cubbyhole/test **');
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(2);
expect(core.exportVariable).toBeCalledWith('foo', 'bar');
expect(core.exportVariable).toBeCalledWith('zip', 'zap');
});
it('wildcard supports cubbyhole with mixed case change', async () => {
mockInput(`
/cubbyhole/test * ;
/cubbyhole/test **`);
await exportSecrets();
expect(core.exportVariable).toBeCalledTimes(4);
expect(core.exportVariable).toBeCalledWith('FOO', 'bar');
expect(core.exportVariable).toBeCalledWith('ZIP', 'zap');
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 ;
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
verbose: true verbose: true
}; };
+50 -24
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('@actions/core'); jest.mock('@actions/core');
jest.mock('@actions/core/lib/command'); jest.mock('@actions/core/lib/command');
const core = require('@actions/core'); const core = require('@actions/core');
@@ -14,6 +19,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 +65,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 +73,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 +91,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: `
@@ -96,10 +102,12 @@ describe('jwt auth', () => {
} }
}); });
// write the jwt config, the jwt role will be written on a per-test
// basis since the audience may vary
await got(`${vaultUrl}/v1/auth/jwt/config`, { await got(`${vaultUrl}/v1/auth/jwt/config`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
jwt_validation_pubkeys: publicRsaKey, jwt_validation_pubkeys: publicRsaKey,
@@ -107,26 +115,10 @@ describe('jwt auth', () => {
} }
}); });
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
method: 'POST',
headers: {
'X-Vault-Token': 'testtoken',
},
json: {
role_type: 'jwt',
bound_audiences: null,
bound_claims: {
iss: 'vault-action'
},
user_claim: 'iss',
policies: ['reader']
}
});
await got(`${vaultUrl}/v1/secret/data/test`, { await got(`${vaultUrl}/v1/secret/data/test`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-Vault-Token': 'testtoken', 'X-Vault-Token': vaultToken,
}, },
json: { json: {
data: { data: {
@@ -137,6 +129,24 @@ describe('jwt auth', () => {
}); });
describe('authenticate with private key', () => { describe('authenticate with private key', () => {
beforeAll(async () => {
await got(`${vaultUrl}/v1/auth/jwt/role/default`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
role_type: 'jwt',
bound_audiences: null,
bound_claims: {
iss: 'vault-action'
},
user_claim: 'iss',
policies: ['reader']
}
});
});
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
@@ -169,14 +179,30 @@ describe('jwt auth', () => {
describe('authenticate with Github OIDC', () => { describe('authenticate with Github OIDC', () => {
beforeAll(async () => { beforeAll(async () => {
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, { 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',
bound_audiences: null, bound_audiences: 'https://github.com/hashicorp/vault-action',
bound_claims: {
iss: 'vault-action'
},
user_claim: 'iss',
policies: ['reader']
}
});
await got(`${vaultUrl}/v1/auth/jwt/role/default-sigstore`, {
method: 'POST',
headers: {
'X-Vault-Token': vaultToken,
},
json: {
role_type: 'jwt',
bound_audiences: 'sigstore',
bound_claims: { bound_claims: {
iss: 'vault-action', iss: 'vault-action',
aud: 'sigstore', aud: 'sigstore',
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const privateRsaKey = ` const privateRsaKey = `
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArcch89X6VuWj/CQtVfaCXUl0Pcv8IJRgICN8X+3zFNrbiTdh MIIEowIBAAKCAQEArcch89X6VuWj/CQtVfaCXUl0Pcv8IJRgICN8X+3zFNrbiTdh
@@ -0,0 +1,121 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
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');
})
});
-24
View File
@@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEFjCCAv6gAwIBAgIUe0i7/HGZKvbDb30L9mC99KXFwj8wDQYJKoZIhvcNAQEL
BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
Ew1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxIzAhBgNVBAsTGlRl
c3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MS0wKwYDVQQDEyRQcm90b3R5cGUgVGVz
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjAwODA1MTg1MjAwWhcNMjUwODA0
MTg1MjAwWjCBojELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAU
BgNVBAcTDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoTCUhhc2hpQ29ycDEjMCEGA1UE
CxMaVGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxLTArBgNVBAMTJFByb3RvdHlw
ZSBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMrRXuu2+zhBs0pLYEdXIaPc4KoWO3xm2RJdbzy3hfjFybQ8
H/Y6Hi7txjGGSb45xSfXT/RF2srNfs235I+sfB8rrEizNpzkXqOgGa8LKvh2tgBT
BK/jDWsEdDhxmkpFhE69wEW+D5ub7QGnx9jrqLKfwCmUA0utlzcFBk2nRNhRtsrp
CI5YL1VN4coLpgXdvbodzbynPzGHe9R/o9K0Uiz2hgHooyKwhkVYwo0BIAQamLFz
TS7lyeLf0thDOxV31NX8SpSucqRf50WHNk8T/YtKZ9EhlBDT4ybZwwvcC/ocxxcg
1LvB0YweZNjSeO78S4CMh1TFGXnF/xOtGABlIbcCAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFABD/NxvYLpo5zVNeD01
r8IIFYlBMA0GCSqGSIb3DQEBCwUAA4IBAQB7TfpIx53gf/oI3mgR6Ciz287WBzFR
OzhJXwHk5J3mx8VC1W8tDRXih2lCLd/f9qDy6LyL/hZcoonev6w9oReuOMBiH6l4
Pf3yq2aDXX0AoGgm75c1m34kY669JLMsHq5+xuUDeeFUMd60w9zVtZfBSumy/sgN
PdjtvThh8sSByocYULs3tuxZDGyQ6GyQcn/xlMrGtmcD5IuX5IXqcKRVlZttykNx
S2ltcR00fekw8WZyPSzMJaP+/Kcq3T2viN02MS6qEycQZoYfEAMdj+A0kjbsZG9D
6J92z78b2DuLAUvZVpynNk/UbpDeqIDy40V3JDmtvrfGUMkMhMqgK/+J
-----END CERTIFICATE-----
@@ -1,25 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIENTCCAx2gAwIBAgIUMu5h1ysA5DlM6lzZFliT2C2n4lEwDQYJKoZIhvcNAQEL
BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
Ew1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxIzAhBgNVBAsTGlRl
c3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MS0wKwYDVQQDEyRQcm90b3R5cGUgVGVz
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwIBcNMjAwODA1MTg1MjAwWhgPMjEyMDA3
MTIxODUyMDBaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzESMBAGA1UEChMJSGFzaGlDb3JwMSMwIQYD
VQQLExpUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOaGFzaGlj
b3JwLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/zYDKdDGo
+Qy7eJUNjOe6jpeGvK1lMu5T1Xka+h2ay6WH5gLyrPw5pi582iYpJdHVbplKMywx
LxZv7mAbKNxqdp8UZKy0A3bCuHQqRF8ssXXHufQ8EGxNkLMLJP0e2q39OnrxXekS
8Ct3aJm3V8qkcV3CpVdPNgJh4TSuneCXIxVWjFYSiyHi0/5TRd2D+aQPz12szg5F
mBW4dLzYKHEMlWcjWG8mxtbLyt+jSR1+tSehQx7KndufdfniOWEDBdbeR3yDnZdn
p8DnRWK4oaEI3Sl8tKlDd1Yp+R96aqOEn1tPW6Jy6Vdvk3fCefclbWZ6B9kiJ/1r
gxq7AN7iKmHNAgMBAAGjdTBzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRn2mwyk+MPChnLg0iWy1r9
b3JvwTAfBgNVHSMEGDAWgBQAQ/zcb2C6aOc1TXg9Na/CCBWJQTANBgkqhkiG9w0B
AQsFAAOCAQEAOpCy0vHp1Kxgv0VBRrbrwSQLBGP8a1ubVWoeoZQ+EvX9ozqDrHxm
gM4XPYUJlUOOEu0ZRgCW60YK33E1zNKnA1F0/3/rmqMkKnm0BBs/5WzMWtsIBPcU
e0CeJmaRIXnERQMH/svD+RrFo1dcF8rUDIlWez7+xGqoIGBg7v4jEmkZ3HdckcE+
/xvC61YSG8NsJwR/CEcQ8YCyVfgvuS0ukWs4dN15aVDL3Oe61h3bRcGAywOJBrdq
9xaq7ezZp/+lUSkYnatWJBuC/aviH9g9s+gMT0I3fWHh8BB0Ne2txwJ15K/qz5he
TjxFsumrh50aFqjSiEHndtY5UWuGAFLiSw==
-----END CERTIFICATE-----
@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAv82AynQxqPkMu3iVDYznuo6XhrytZTLuU9V5Gvodmsulh+YC
8qz8OaYufNomKSXR1W6ZSjMsMS8Wb+5gGyjcanafFGSstAN2wrh0KkRfLLF1x7n0
PBBsTZCzCyT9Htqt/Tp68V3pEvArd2iZt1fKpHFdwqVXTzYCYeE0rp3glyMVVoxW
Eosh4tP+U0Xdg/mkD89drM4ORZgVuHS82ChxDJVnI1hvJsbWy8rfo0kdfrUnoUMe
yp3bn3X54jlhAwXW3kd8g52XZ6fA50ViuKGhCN0pfLSpQ3dWKfkfemqjhJ9bT1ui
culXb5N3wnn3JW1megfZIif9a4MauwDe4iphzQIDAQABAoIBAQCYHJuidAoaTwGZ
ACV9rJzuqD1lvubpFj5KwEcebPPjmtQ5deIqoaQa+D9wBvYyteq3ENKDNRg8HXL2
7B7OC1bbHB5HZxxMW17pSK3gA39Ti52z+zbGF+Q8k6BbG0efG6DW7nUoTOkWeuCN
/6fA7uAoEDxirQwUJuo2xAsq3MyMLwcs95rke9Bly8ABFNaV1oMZq8YT/w8oSc2b
/7WtxXmChHlVYXTcMqzVPqNFqPRixZRWQ+BSHoXmEDviuGd51L4s9D7iXp32TvUx
DMHeS1DFA2en7ZF1uc9VXZeplkkDtVhUe4d6qOqCcUwDFEvMonnyVSa6/FkR5jYZ
2yujTdfhAoGBAM0hGOnmnDnCjADUt4mZlr+Mf0XmdKzEV+hid4CQUvBoTXgjYMvv
c397eNePce7SwSUE1/APERInGUPhRLVFW5q6/34WRtGBbQkT8ByeJANXes4UFZe3
wdNLczWUlSl0G3jTf+Kh3+K5/PtmyxSrAS/9GIk+ibs1mlJOPyVnWqUJAoGBAO9e
WlP9/ruXluvkQyM5ZlnAnZYMsFGzzPx4tkazUjurtqxQoyZ0z+pPItGQ7lOl+pDA
EWiTun66g+Da9uBiBCJUeXiC1ge2p6bT6N194BrYyrWML9hcIL4mqVojUEUmhnSh
6b9h1pC7vFmw5ZFMIIkS60cfBMgQMZxMJN8NuaulAoGBAJM5hwURg90c2ZkbEyPK
PVz7fLlxnxoEzcc3LOf0LeLoKXnpgma8VJwRxXiJNs+fKgrkwAtG9QyfTU3f1412
2zlhr1ASsv9ZMiXKzpHrmpNfbP+NgLXkqFN7mpPBMZGQCMuemPHTFrpGnODfNTB/
T5newIZ4gSgBX+Jk0IOK+47pAoGAeKo6pK6ck9pV5TIbOg18b/AuQG7DD1yxD/CW
CkvpP1VPb8vygrdN/FLKPZRu39IC3qdD31DhKXNCeb5Hx1MBvICS/1INLLRCDVIz
yDvlFgOFJEG3+LxwcQqyQlMc6s8B5pecarKaZDmPODN5dmZG3HKiEicr1OJ878pe
p+aWW1UCgYBmGFbCc1qqlqp+srYGsv3rIgNs5HSfrAjbgY8xh9foMgrYCRAm57gv
01yVxMXWmKA6ReVEu8OTVy9fkuOL/vw2o+C6W4IPZYdvSQoPwd5Lf+AqxEQvFF1m
tT3SZAM3EhQ7tIXdIQHY27SJ1KlUJMrvUq1CiRWiG/MOKf/87JXPog==
-----END RSA PRIVATE KEY-----
@@ -1,3 +1,6 @@
# Copyright IBM Corp. 2019, 2026
# SPDX-License-Identifier: MIT
ui = false ui = false
disable_mlock = true disable_mlock = true
@@ -1,26 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIUAswquazrfsyDRvXZwn5718DUhU4wDQYJKoZIhvcNAQEL
BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
Ew1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKEwlIYXNoaUNvcnAxIzAhBgNVBAsTGlRl
c3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MS0wKwYDVQQDEyRQcm90b3R5cGUgVGVz
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwIBcNMjAwODA1MTg1MjAwWhgPMjEyMDA3
MTIxODUyMDBaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEW
MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzESMBAGA1UEChMJSGFzaGlDb3JwMSMwIQYD
VQQLExpUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOaGFzaGlj
b3JwLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0azdZsrPC
5Rv8nRxVJnLi+oZgCJYgzhCDiEbYqt1QK1gqNXp0ml5ck6ycj0drwzHzrrX+xcPV
5FcNKH3RFyon9XkzjwaXkMv6IkgvH6/jQ1dDW9kWBf3Io3Y59wnD/YaIzNK0CYJS
fRNdsZb4InH8gh+RL33+FeysgJwXG1TVA4tTUj7DQxDE0cDd9UD+C9Yx7OWiUjC1
IjqdFPusX1nziKYjeI5/UiCmOUGqJJRoMPonuzuGIj9GdmBKmga64OfeZFqn4f6a
ay61VnGCwZ24VniUwYElsFbcF2Nv9WqnrOeQlHOsYN68VMqHzaYPqE6SPa6mO5mI
/tmpXrDG3Y+RAgMBAAGjgZ0wgZowDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFE03/UTs2ZmJpWHSmstt
hDngW6F3MB8GA1UdIwQYMBaAFABD/NxvYLpo5zVNeD01r8IIFYlBMCUGA1UdEQQe
MByCCWxvY2FsaG9zdIIJdmF1bHQtdGxzhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB
AQCzarBGJium5oZDSSP5GqxpS13QP2onEen6I1k2eRdcOqtbfNdQ20RJrb4dfNkE
Dc09KWVlZAn+hYge2KKTXJ+4ltIC9V1LvquyWipNczOT1ve0H9gt3Wm88LdESqI5
HOx43pIaa3cWXBlbzrFmT1SASYm1V5Oo1mXzpUukGokHLLmAz36VVuJGbD0BxYke
5MefG4tNT1SsMsIqVvGxI9NiVs7YTdJu81MctSYK5snsEKnYdi9N7CHOk3bdDpeC
v2Vo7XBk3s4sBMGmnJO+1JOcRFJioooEFkqNyQmg3atfInysVbreKS5KtWNTaCPm
yI55plW8ga5ucja2VX3WbwAO
-----END CERTIFICATE-----
@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtGs3WbKzwuUb/J0cVSZy4vqGYAiWIM4Qg4hG2KrdUCtYKjV6
dJpeXJOsnI9Ha8Mx8661/sXD1eRXDSh90RcqJ/V5M48Gl5DL+iJILx+v40NXQ1vZ
FgX9yKN2OfcJw/2GiMzStAmCUn0TXbGW+CJx/IIfkS99/hXsrICcFxtU1QOLU1I+
w0MQxNHA3fVA/gvWMezlolIwtSI6nRT7rF9Z84imI3iOf1IgpjlBqiSUaDD6J7s7
hiI/RnZgSpoGuuDn3mRap+H+mmsutVZxgsGduFZ4lMGBJbBW3Bdjb/Vqp6znkJRz
rGDevFTKh82mD6hOkj2upjuZiP7ZqV6wxt2PkQIDAQABAoIBAQCvK0HsVvLtkSCh
HbF6gwAcnHyHFQ8d/rRN4KxYhVynD85j/NRODer8G20F/J6tZDFFlSWinUTMkQxr
/BpcPg9yCIWKp50Q30cMLujCyBMvphw9jBmzplGG0h5hnRbgMXDDtYoFvw3HJST+
XQRlGpxtO7GGdwPvBD5sJdpnHOQ6g7qIYKmlHM99kHU8vr0VghqZAYxEh8RpnYez
NLra+7ep+Zp1pFIniU6B8ohyL3OArbQ65qYrZYriAEI5HeEk0RhjewcPsV56LwbS
CncTVS/dNYgk1zRIvytmbDVD3v/4lLvnpIWeKVdk7p1aGJeCdpLeWNvDLX0Ws67r
QeZQizwhAoGBAMwAIA6+HPsx+8dhNbN3ydX8YU6uUfSeshhshIFZPIYL2vrKyAHU
/GAYVzYJH/cU0IvlLJlLdQuiZkOXEX87tgdfmM/o4Qdl12RR0BvU0Cae0txtzNrP
yTdfZqDhTz/V8jOAXUNA5oQA45Y3rI7JES8hBd1F9WFOH2WINp344GzlAoGBAOJo
SgmRE24VcnfUMqrBpwZBdBrTxDQyTagvd+MuGomIQfcE2Y4rr1eIuJJ0HF5/eYxc
DZRO/LVP9tQ8ozXi2tdmgUdKC79O2edmdOCWW5of464R/TLcM5B4SmS36RtdE3qJ
ig4fcUmsJ15MAGpkXLMh5YSD/N3TmcnURtx06Fk9AoGATi+mGcBnnybzFuF9EYHR
y7/lE6DgLF8+ZvoAdwralY2pqgFaUslsyO/LTRyGMc66d0OoqkAvZfwiMbmOrTMX
ew/6o4Tf6lPwD7UDjAcul/67VlyG7T5CIoTf8r0oAJFhOLf0BrizINiuYX6JFlid
y3BerQYJG/gzNFjWhglDCrkCgYA+3wUISRAjNrN10ShMwL/3/b8XIA1RDVMBTEU3
gfr+jCb9SIx9bWYgoafXi4TBPRbswjdHIvQMCWuankgYU6m/vQhTWp2Of4AFQS9d
moNPdmGMWhR8xidPjAfklimWXq9lDMKYj2SvN64rAmHvKXWQjO4mcVyL4RHIuTkA
STqoZQKBgQDKHd8F6tjZHEFolmjS5l682g7zVTpBhozezJ/RqYvhJh5ew1pXoD/O
Zu9iMfHoDjR4ZUXq6aeLUj/oIt1AsjwaGChOLLAvFbvePgS9XkYkwIlaxS4efAya
+CQE/JmY/a1/c2MDLNMCEXvUqX68pv6iDF8pfn+i4tn0omYqgfUlCA==
-----END RSA PRIVATE KEY-----
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
describe('e2e-tls', () => { describe('e2e-tls', () => {
it('verify', () => { it('verify', () => {
expect(process.env.SECRET).toBe("SUPERSECRET"); expect(process.env.SECRET).toBe("SUPERSECRET");
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
verbose: true verbose: true
}; };
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const got = require('got'); const got = require('got');
const core = require('@actions/core'); const core = require('@actions/core');
+9
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
describe('e2e', () => { describe('e2e', () => {
it('verify', () => { it('verify', () => {
expect(process.env.SECRET).toBe("SUPERSECRET"); expect(process.env.SECRET).toBe("SUPERSECRET");
@@ -9,5 +14,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"}');
}); });
}); });
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
verbose: true verbose: true
}; };
+65 -7
View File
@@ -1,20 +1,28 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
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 +34,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 +43,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 +94,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 +104,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 +114,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);
+49 -11
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('@actions/core'); jest.mock('@actions/core');
jest.mock('@actions/core/lib/command'); jest.mock('@actions/core/lib/command');
const core = require('@actions/core'); const core = require('@actions/core');
@@ -8,6 +13,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 +21,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 +54,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 +77,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 +124,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 +157,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 +175,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 +195,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 +208,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 +219,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 +230,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 +276,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 +294,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 +315,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
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
verbose: true verbose: true
}; };
+5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
module.exports = { module.exports = {
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/integrationTests/'], testPathIgnorePatterns: ['/node_modules/', '<rootDir>/integrationTests/'],
}; };
+2320 -4477
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -8,8 +8,8 @@
"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/**/*",
@@ -34,18 +34,18 @@
}, },
"homepage": "https://github.com/hashicorp/vault-action#readme", "homepage": "https://github.com/hashicorp/vault-action#readme",
"dependencies": { "dependencies": {
"got": "^11.8.5", "got": "^11.8.6",
"jsonata": "^1.8.6", "jsonata": "^2.0.3",
"jsrsasign": "^10.6.1" "jsrsasign": "^11.1.0"
}, },
"peerDependencies": { "peerDependencies": {
"@actions/core": ">=1 <2" "@actions/core": ">=1 <2"
}, },
"devDependencies": { "devDependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.1",
"@vercel/ncc": "^0.36.0", "@vercel/ncc": "^0.38.1",
"jest": "^29.3.1", "jest": "^29.7.0",
"jest-when": "^3.5.2", "jest-when": "^3.6.0",
"mock-http-server": "^1.4.5" "mock-http-server": "^1.4.5"
} }
} }
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Copyright IBM Corp. 2019, 2025
# SPDX-License-Identifier: MIT
# Adapted from: https://github.com/hashicorp/vault-secrets-operator/blob/main/hack/.functions
# getGH downloads files from GitHub with optional authentication
# Usage: getGH <url> [dest_file] [num_retries]
function getGH() {
local url="$1"
local dest="$2"
local num_retries="${3:-${GH_GET_RETRIES}}"
headers=(
'--header' "Accept: application/vnd.github+json"
'--header' "X-GitHub-Api-Version: 2022-11-28"
)
if [ -n "${GITHUB_TOKEN}" ]; then
headers+=(
'--header' "Authorization: Bearer ${GITHUB_TOKEN}"
)
fi
cmd=curl
opts=('-sfSL')
echo "Fetching ${url}"
if [ -z "${dest}" ]; then
opts+=('-O')
else
opts+=('-o' "${dest}")
fi
if [ -n "${num_retries}" ]; then
opts+=('--retry' "${num_retries}")
fi
${cmd} "${opts[@]}" "${headers[@]}" "${url}"
}
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Copyright IBM Corp. 2019, 2025
# SPDX-License-Identifier: MIT
#
# Generates a PKI chain (CA, server cert, client cert) using cfssl.
# Outputs certs to .build/certs/ and writes .build/e2e-tls.env for local
# act usage (act --env-file .build/e2e-tls.env).
#
# Usage: ./scripts/gen-tls-certs.sh
# Requires: cfssl, cfssljson (brew install cfssl)
set -euo pipefail
pushd "$(git rev-parse --show-toplevel || echo .)" > /dev/null
OUTDIR=".build/certs"
ENVFILE=".build/e2e-tls.env"
if ! command -v cfssl &>/dev/null || ! command -v cfssljson &>/dev/null; then
echo "error: cfssl and cfssljson are required." >&2
popd > /dev/null
exit 1
fi
mkdir -p "$OUTDIR"
pushd "$OUTDIR" > /dev/null
# ── cfssl signing config ──────────────────────────────────────────────────────
cat > cfssl-config.json <<'EOF'
{
"signing": {
"default": { "expiry": "8760h" },
"profiles": {
"server": {
"usages": ["signing", "key encipherment", "server auth"],
"expiry": "8760h"
},
"client": {
"usages": ["signing", "key encipherment", "client auth"],
"expiry": "8760h"
}
}
}
}
EOF
# ── CA ────────────────────────────────────────────────────────────────────────
echo "Generating CA..."
cfssl gencert -initca - <<'EOF' | cfssljson -bare ca
{
"CN": "Vault Test CA",
"key": { "algo": "rsa", "size": 2048 },
"ca": { "expiry": "87600h" }
}
EOF
# ── Server cert ───────────────────────────────────────────────────────────────
echo "Generating server certificate..."
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=cfssl-config.json \
-profile=server - <<'EOF' | cfssljson -bare server
{
"CN": "vault-tls",
"hosts": ["localhost", "127.0.0.1", "vault-tls"],
"key": { "algo": "rsa", "size": 2048 }
}
EOF
# ── Client cert ───────────────────────────────────────────────────────────────
echo "Generating client certificate..."
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=cfssl-config.json \
-profile=client - <<'EOF' | cfssljson -bare client
{
"CN": "vault-client",
"key": { "algo": "rsa", "size": 2048 }
}
EOF
# ── Rename to names expected by vault config ──────────────────────────────────
mv ca.pem ca.crt
mv server.pem server.crt
mv server-key.pem server.key
mv client.pem client.crt
mv client-key.pem client.key
# ── Remove intermediates not needed at runtime ────────────────────────────────
rm -f ca.csr server.csr client.csr ca-key.pem cfssl-config.json
# Ensure files are readable by the vault container user
chmod 644 ./*.crt ./*.key
popd > /dev/null
# ── Copy vault server config ──────────────────────────────────────────────────
cp "integrationTests/e2e-tls/configs/config.hcl" "$OUTDIR/config.hcl"
# ── Write env file for local act usage ───────────────────────────────────────
{
printf 'VAULTCA=%s\n' "$(base64 < "$OUTDIR/ca.crt" | tr -d '\n')"
printf 'VAULT_CLIENT_CERT=%s\n' "$(base64 < "$OUTDIR/client.crt" | tr -d '\n')"
printf 'VAULT_CLIENT_KEY=%s\n' "$(base64 < "$OUTDIR/client.key" | tr -d '\n')"
} > "$ENVFILE"
echo "Certs generated in $OUTDIR"
echo "Env file written to $ENVFILE"
popd > /dev/null
+72 -27
View File
@@ -1,11 +1,19 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
// @ts-check // @ts-check
const core = require('@actions/core'); const core = require('@actions/core');
const command = require('@actions/core/lib/command'); const command = require('@actions/core/lib/command');
const got = require('got').default; const got = require('got').default;
const jsonata = require('jsonata'); const jsonata = require('jsonata');
const { auth: { retrieveToken }, secrets: { getSecrets } } = require('./index'); const { normalizeOutputKey } = require('./utils');
const { WILDCARD, WILDCARD_UPPERCASE } = require('./constants');
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes']; const { auth: { retrieveToken }, secrets: { getSecrets }, pki: { getCertificates } } = require('./index');
const AUTH_METHODS = ['approle', 'token', 'github', 'jwt', 'kubernetes', 'ldap', 'userpass'];
const ENCODING_TYPES = ['base64', 'hex', 'utf8']; const ENCODING_TYPES = ['base64', 'hex', 'utf8'];
async function exportSecrets() { async function exportSecrets() {
@@ -13,11 +21,22 @@ async function exportSecrets() {
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 pkiInput = core.getInput('pki', { required: false });
let pkiRequests = [];
if (pkiInput) {
if (secretsInput) {
throw Error('You cannot provide both "secrets" and "pki" inputs.');
}
pkiRequests = parsePkiInput(pkiInput);
}
const secretEncodingType = core.getInput('secretEncodingType', { required: false }); const secretEncodingType = core.getInput('secretEncodingType', { required: false });
const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase(); const vaultMethod = (core.getInput('method', { required: false }) || 'token').toLowerCase();
@@ -69,20 +88,23 @@ 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}`);
} }
const requests = secretRequests.map(request => { let results = [];
const { path, selector } = request; if (pkiRequests.length > 0) {
return request; results = await getCertificates(pkiRequests, client);
}); } else {
results = await getSecrets(secretRequests, client);
const results = await getSecrets(requests, client); }
for (const result of results) { for (const result of results) {
@@ -103,7 +125,7 @@ async function exportSecrets() {
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) {
@@ -121,6 +143,43 @@ async function exportSecrets() {
* @property {string} selector * @property {string} selector
*/ */
/**
* Parses a pki input string into key paths and the request parameters.
* @param {string} pkiInput
*/
function parsePkiInput(pkiInput) {
if (!pkiInput) {
return []
}
const secrets = pkiInput
.split(';')
.filter(key => !!key)
.map(key => key.trim())
.filter(key => key.length !== 0);
return secrets.map(secret => {
const path = secret.substring(0, secret.indexOf(' '));
const parameters = secret.substring(secret.indexOf(' ') + 1);
core.debug(` Parsing PKI: ${path} with parameters: ${parameters}`);
if (!path || !parameters) {
throw Error(`You must provide a valid path and parameters. Input: "${secret}"`);
}
let outputVarName = path.split('/').pop();
let envVarName = normalizeOutputKey(outputVarName);
return {
path,
envVarName,
outputVarName,
parameters: JSON.parse(parameters),
};
});
}
/** /**
* Parses a secrets input string into key paths and their resulting environment variable name. * Parses a secrets input string into key paths and their resulting environment variable name.
* @param {string} secretsInput * @param {string} secretsInput
@@ -167,7 +226,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 && selector !== WILDCARD_UPPERCASE && (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}"`);
} }
@@ -187,20 +246,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
@@ -229,6 +274,6 @@ function parseHeadersInput(inputKey, inputOptions) {
module.exports = { module.exports = {
exportSecrets, exportSecrets,
parseSecretsInput, parseSecretsInput,
normalizeOutputKey, parseHeadersInput,
parseHeadersInput
}; };
+97 -6
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('got'); jest.mock('got');
jest.mock('@actions/core'); jest.mock('@actions/core');
jest.mock('@actions/core/lib/command'); jest.mock('@actions/core/lib/command');
@@ -184,6 +189,11 @@ describe('exportSecrets', () => {
.mockReturnValueOnce(doExport); .mockReturnValueOnce(doExport);
} }
function mockOutputToken(doOutput) {
when(core.getInput)
.calledWith('outputToken', expect.anything())
.mockReturnValueOnce(doOutput);
}
function mockEncodeType(doEncode) { function mockEncodeType(doEncode) {
when(core.getInput) when(core.getInput)
.calledWith('secretEncodingType', expect.anything()) .calledWith('secretEncodingType', expect.anything())
@@ -215,6 +225,55 @@ describe('exportSecrets', () => {
expect(core.setOutput).toBeCalledWith('key', '1'); expect(core.setOutput).toBeCalledWith('key', '1');
}); });
it('JSON data secret retrieval', async () => {
const jsonData = {"x":1,"y":2};
let result = JSON.stringify(jsonData);
mockInput('test key');
mockVaultData({
key: jsonData,
});
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', result);
expect(core.setOutput).toBeCalledWith('key', result);
});
it('JSON string secret retrieval', async () => {
const jsonString = '{"x":1,"y":2}';
mockInput('test key');
mockVaultData({
key: jsonString,
});
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
expect(core.setOutput).toBeCalledWith('key', jsonString);
});
it('multi-line JSON string secret retrieval', async () => {
const jsonString = `
{
"x":1,
"y":"bar"
}
`;
mockInput('test key');
mockVaultData({
key: jsonString,
});
await exportSecrets();
expect(core.exportVariable).toBeCalledWith('KEY', jsonString);
expect(core.setOutput).toBeCalledWith('key', jsonString);
});
it('intl secret retrieval', async () => { it('intl secret retrieval', async () => {
mockInput('测试 测试'); mockInput('测试 测试');
mockVaultData({ mockVaultData({
@@ -323,13 +382,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
@@ -343,10 +425,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);
}) })
@@ -358,4 +440,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');
})
}); });
+47 -5
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
// @ts-check // @ts-check
const core = require('@actions/core'); const core = require('@actions/core');
const rsasign = require('jsrsasign'); const rsasign = require('jsrsasign');
@@ -5,18 +10,21 @@ const fs = require('fs');
const { default: got } = require('got'); const { default: got } = require('got');
const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token' const defaultKubernetesTokenPath = '/var/run/secrets/kubernetes.io/serviceaccount/token'
const retries = 5
const retries_delay = 3000
/*** /***
* Authenticate with Vault and retrieve a Vault token that can be used for requests. * Authenticate with Vault and retrieve a Vault token that can be used for requests.
* @param {string} method * @param {string} method
* @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': {
const vaultRoleId = core.getInput('roleId', { required: true }); const vaultRoleId = core.getInput('roleId', { required: true });
const vaultSecretId = core.getInput('secretId', { required: true }); const vaultSecretId = core.getInput('secretId', { required: false });
return await getClientToken(client, method, path, { role_id: vaultRoleId, secret_id: vaultSecretId }); return await getClientToken(client, method, path, { role_id: vaultRoleId, secret_id: vaultSecretId });
} }
case 'github': { case 'github': {
@@ -34,7 +42,10 @@ async function retrieveToken(method, client) {
const githubAudience = core.getInput('jwtGithubAudience', { required: false }); const githubAudience = core.getInput('jwtGithubAudience', { required: false });
if (!privateKey) { if (!privateKey) {
jwt = await core.getIDToken(githubAudience) jwt = await retryAsyncFunction(retries, retries_delay, core.getIDToken, githubAudience)
.then((result) => {
return result;
});
} else { } else {
jwt = generateJwt(privateKey, keyPassword, Number(tokenTtl)); jwt = generateJwt(privateKey, keyPassword, Number(tokenTtl));
} }
@@ -50,6 +61,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') {
@@ -107,12 +125,12 @@ 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>} */
let response; let response;
try { try {
response = await client.post(`v1/auth/${path}/login`, options); response = await client.post(`${path}`, options);
} catch (err) { } catch (err) {
if (err instanceof got.HTTPError) { if (err instanceof got.HTTPError) {
throw Error(`failed to retrieve vault token. code: ${err.code}, message: ${err.message}, vaultResponse: ${JSON.stringify(err.response.body)}`) throw Error(`failed to retrieve vault token. code: ${err.code}, message: ${err.message}, vaultResponse: ${JSON.stringify(err.response.body)}`)
@@ -134,6 +152,30 @@ async function getClientToken(client, method, path, payload) {
} }
} }
/***
* Generic function for retrying an async function
* @param {number} retries
* @param {number} delay
* @param {Function} func
* @param {any[]} args
*/
async function retryAsyncFunction(retries, delay, func, ...args) {
let attempt = 0;
while (attempt < retries) {
try {
const result = await func(...args);
return result;
} catch (error) {
attempt++;
if (attempt < retries) {
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}
/*** /***
* @typedef {Object} VaultLoginResponse * @typedef {Object} VaultLoginResponse
* @property {{ * @property {{
+24
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('got'); jest.mock('got');
jest.mock('@actions/core'); jest.mock('@actions/core');
jest.mock('@actions/core/lib/command'); jest.mock('@actions/core/lib/command');
@@ -85,4 +90,23 @@ describe("test retrival for token", () => {
const url = got.post.mock.calls[0][0] const url = got.post.mock.calls[0][0]
expect(url).toContain('differentK8sPath') expect(url).toContain('differentK8sPath')
}) })
it("test retrieval with jwt", async () => {
const method = "jwt"
const jwtToken = "someTestToken"
const testRole = "testRole"
const privateKeyRaw = ""
mockApiResponse()
mockInput("role", testRole)
mockInput("jwtPrivateKey", privateKeyRaw)
core.getIDToken = jest.fn()
core.getIDToken.mockReturnValueOnce(jwtToken)
const token = await retrieveToken(method, got)
expect(token).toEqual(testToken)
const payload = got.post.mock.calls[0][1].json
expect(payload).toEqual({ jwt: jwtToken, role: testRole })
const url = got.post.mock.calls[0][0]
expect(url).toContain('jwt')
})
}) })
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const WILDCARD_UPPERCASE = '*';
const WILDCARD = '**';
module.exports = {
WILDCARD,
WILDCARD_UPPERCASE,
};
+6
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const core = require('@actions/core'); const core = require('@actions/core');
const { exportSecrets } = require('./action'); const { exportSecrets } = require('./action');
@@ -5,6 +10,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);
} }
})(); })();
+8 -1
View File
@@ -1,7 +1,14 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const auth = require('./auth'); const auth = require('./auth');
const secrets = require('./secrets'); const secrets = require('./secrets');
const pki = require('./pki');
module.exports = { module.exports = {
auth, auth,
secrets secrets,
pki
}; };
+81
View File
@@ -0,0 +1,81 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const { normalizeOutputKey } = require('./utils');
const core = require('@actions/core');
/** A map of postfix values mapped to the key in the certificate response and a transformer function */
const outputMap = {
cert: { key: 'certificate', tx: (v) => v },
key: { key: 'private_key', tx: (v) => v },
ca: { key: 'issuing_ca', tx: (v) => v },
ca_chain: { key: 'ca_chain', tx: (v) => v.join('\n') },
};
/**
* @typedef PkiRequest
* @type {object}
* @property {string} path - The path to the PKI endpoint
* @property {Record<string, any>} parameters - The parameters to send to the PKI endpoint
* @property {string} envVarName - The name of the environment variable to set
* @property {string} outputVarName - The name of the output variable to set
*/
/**
* @typedef {Object} PkiResponse
* @property {PkiRequest} request
* @property {string} value
* @property {boolean} cachedResponse
*/
/**
* Generate and return the certificates from the PKI engine
* @param {Array<PkiRequest>} pkiRequests
* @param {import('got').Got} client
* @return {Promise<Array<PkiResponse>>}
*/
async function getCertificates(pkiRequests, client) {
/** @type Array<PkiResponse> */
let results = [];
for (const pkiRequest of pkiRequests) {
const { path, parameters } = pkiRequest;
const requestPath = `v1/${path}`;
let body;
try {
const result = await client.post(requestPath, {
body: JSON.stringify(parameters),
});
body = result.body;
} catch (error) {
core.error(`${error.response?.body ?? error.message}`);
throw error;
}
body = JSON.parse(body);
core.info(`✔ Successfully generated certificate (serial number ${body.data.serial_number})`);
Object.entries(outputMap).forEach(([key, value]) => {
const val = value.tx(body.data[value.key]);
results.push({
request: {
...pkiRequest,
envVarName: normalizeOutputKey(`${pkiRequest.envVarName}_${key}`, true),
outputVarName: normalizeOutputKey(`${pkiRequest.outputVarName}_${key}`),
},
value: val,
cachedResponse: false,
});
});
}
return results;
}
module.exports = {
getCertificates,
};
+6 -1
View File
@@ -1,3 +1,8 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
jest.mock('@actions/core'); jest.mock('@actions/core');
const core = require('@actions/core'); const core = require('@actions/core');
@@ -66,4 +71,4 @@ describe('exportSecrets retries', () => {
done(); done();
}); });
}); });
}); });
+120 -26
View File
@@ -1,5 +1,12 @@
const jsonata = require("jsonata"); /**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
const jsonata = require("jsonata");
const { WILDCARD, WILDCARD_UPPERCASE} = require("./constants");
const { normalizeOutputKey } = require("./utils");
const core = require('@actions/core');
/** /**
* @typedef {Object} SecretRequest * @typedef {Object} SecretRequest
@@ -21,13 +28,17 @@ const jsonata = require("jsonata");
* @param {import('got').Got} client * @param {import('got').Got} client
* @return {Promise<SecretResponse<TRequest>[]>} * @return {Promise<SecretResponse<TRequest>[]>}
*/ */
async function getSecrets(secretRequests, client) { async function getSecrets(secretRequests, client, ignoreNotFound) {
const responseCache = new Map(); const responseCache = new Map();
const results = []; let results = [];
let upperCaseEnv = false;
for (const secretRequest of secretRequests) { for (const secretRequest of secretRequests) {
let { path, selector } = secretRequest; let { path, selector } = secretRequest;
const requestPath = `v1/${path}`; // Strip leading slashes to avoid double-slash in the request path
// (e.g. /cubbyhole/test → v1/cubbyhole/test)
const requestPath = `v1/${path.replace(/^\/+/, '')}`;
let body; let body;
let cachedResponse = false; let cachedResponse = false;
if (responseCache.has(requestPath)) { if (responseCache.has(requestPath)) {
@@ -40,42 +51,88 @@ 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()}`) notFoundMsg = `Unable to retrieve result for "${path}" because it was not found: ${response.body.trim()}`;
const ignoreNotFound = (core.getInput('ignoreNotFound', { required: false }) || 'false').toLowerCase() != 'false';
if (ignoreNotFound) {
core.error(`${notFoundMsg}`);
continue;
} else {
throw Error(notFoundMsg)
}
} }
throw error throw error
} }
} }
if (!selector.match(/.*[\.].*/)) {
selector = '"' + selector + '"'
}
selector = "data." + selector
body = JSON.parse(body)
if (body.data["data"] != undefined) {
selector = "data." + selector
}
const value = selectData(body, selector); body = JSON.parse(body);
results.push({
request: secretRequest, if (selector === WILDCARD || selector === WILDCARD_UPPERCASE) {
value, upperCaseEnv = selector === WILDCARD_UPPERCASE;
cachedResponse let keys = body.data;
}); if (body.data["data"] != undefined) {
keys = keys.data;
}
for (let key in keys) {
let newRequest = Object.assign({},secretRequest);
newRequest.selector = key;
if (secretRequest.selector === secretRequest.outputVarName) {
newRequest.outputVarName = key;
newRequest.envVarName = key;
} else {
newRequest.outputVarName = secretRequest.outputVarName+key;
newRequest.envVarName = secretRequest.envVarName+key;
}
newRequest.outputVarName = normalizeOutputKey(newRequest.outputVarName);
newRequest.envVarName = normalizeOutputKey(newRequest.envVarName, upperCaseEnv);
// JSONata field references containing reserved tokens should
// be enclosed in backticks
// https://docs.jsonata.org/simple#examples
if (key.includes(".")) {
const backtick = '`';
key = backtick.concat(key, backtick);
}
selector = key;
results = await selectAndAppendResults(
selector,
body,
cachedResponse,
newRequest,
results
);
}
}
else {
results = await selectAndAppendResults(
selector,
body,
cachedResponse,
secretRequest,
results
);
}
} }
return results; return results;
} }
/** /**
* Uses a Jsonata selector retrieve a bit of data from the result * Uses a Jsonata selector retrieve a bit of data from the result
* @param {object} data * @param {object} data
* @param {string} selector * @param {string} selector
*/ */
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,7 +143,44 @@ 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.includes(".")) {
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
} }
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright IBM Corp. 2019, 2026
* SPDX-License-Identifier: MIT
*/
/**
* Replaces any dot chars to __ and removes non-ascii charts
* @param {string} dataKey
* @param {boolean=} isEnvVar
*/
function normalizeOutputKey(dataKey, upperCase = false) {
let outputKey = dataKey
.replaceAll(".", "__")
.replace(new RegExp("-", "g"), "")
.replace(/[^\p{L}\p{N}_-]/gu, "");
if (upperCase) {
outputKey = outputKey.toUpperCase();
}
return outputKey;
}
module.exports = {
normalizeOutputKey
};