diff --git a/docs/modules/vault.md b/docs/modules/vault.md new file mode 100644 index 000000000..f2b5e412e --- /dev/null +++ b/docs/modules/vault.md @@ -0,0 +1,29 @@ +# Vault Module + +[Vault](https://www.vaultproject.io/) by HashiCorp is a tool for securely accessing secrets such as API keys, passwords, or certificates. This module allows you to run and initialize a Vault container for integration tests. + +## Install + +```bash +npm install @testcontainers/vault --save-dev +``` + +## Examples + + +[Start and perform read/write with node-vault:](../../packages/modules/vault/src/vault-container.test.ts) inside_block:readWrite + + + +[Run Vault CLI init commands at startup:](../../packages/modules/vault/src/vault-container.test.ts) inside_block:initCommands + + +## Why use Vault in integration tests? + +With the growing adoption of Vault in modern infrastructure, testing components that depend on Vault for secret resolution or encryption can be complex. This module allows: + +- Starting a local Vault instance during test runs +- Seeding secrets or enabling engines with Vault CLI +- Validating app behavior with secured data access + +Use this module to test Vault-backed workflows without the need for pre-provisioned Vault infrastructure. diff --git a/mkdocs.yml b/mkdocs.yml index fcbfbfa88..1f71adc38 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,5 +84,6 @@ nav: - Selenium: modules/selenium.md - ToxiProxy: modules/toxiproxy.md - Valkey: modules/valkey.md + - Vault: modules/vault.md - Weaviate: modules/weaviate.md - Configuration: configuration.md diff --git a/package-lock.json b/package-lock.json index 668f09786..f9655bd5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5734,6 +5734,60 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tough-cookie": { + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@postman/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz", + "integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -7208,6 +7262,10 @@ "resolved": "packages/modules/valkey", "link": true }, + "node_modules/@testcontainers/vault": { + "resolved": "packages/modules/vault", + "link": true + }, "node_modules/@testcontainers/weaviate": { "resolved": "packages/modules/weaviate", "link": true @@ -8854,6 +8912,16 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -8951,6 +9019,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -9175,6 +9253,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -9474,6 +9559,13 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/cassandra-driver": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-4.8.0.tgz", @@ -10393,6 +10485,19 @@ "node": ">=0.12" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -10777,6 +10882,24 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -11484,6 +11607,16 @@ "url": "https://github.com/sponsors/jaydenseric" } }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, "node_modules/farmhash-modern": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", @@ -11877,6 +12010,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/form-data": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", @@ -12312,6 +12455,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -12755,6 +12908,21 @@ "node": ">= 6" } }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -13529,6 +13697,13 @@ "ws": "*" } }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -13736,6 +13911,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -13758,6 +13940,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/json11": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/json11/-/json11-2.0.2.tgz", @@ -13877,6 +14066,22 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -15557,6 +15762,16 @@ "dev": true, "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -15985,6 +16200,22 @@ "node": ">=8" } }, + "node_modules/node-vault": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/node-vault/-/node-vault-0.10.5.tgz", + "integrity": "sha512-sIyB/5296U2tMT7hH1nrkoYUXkRxuLsG40fgUHaBhzM+G/uyBKBo+QNsvKqE5FNq24QJM+tr97N+knLQiEEcSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "mustache": "^4.2.0", + "postman-request": "^2.88.1-postman.42", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">= 18.0.0" + } + }, "node_modules/node.extend": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.3.tgz", @@ -16242,6 +16473,16 @@ "js-sdsl": "4.3.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/oauth4webapi": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.3.1.tgz", @@ -17049,6 +17290,47 @@ "node": ">=0.10.0" } }, + "node_modules/postman-request": { + "version": "2.88.1-postman.42", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.42.tgz", + "integrity": "sha512-lepCE8QU0izagxxA31O/MHj8IUguwLlpqeVK7A8vHK401FPvN/PTIzWHm29c/L3j3kTUE7dhZbq8vvbyQ7S2Bw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.4", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/postman-request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -18810,6 +19092,46 @@ "nan": "^2.18.0" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sshpk/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/ssri": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", @@ -18892,6 +19214,16 @@ "stream-chain": "^2.2.5" } }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "bluebird": "^2.6.2" + } + }, "node_modules/stream-shift": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.2.tgz", @@ -19908,6 +20240,25 @@ "node": "*" } }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "dev": true, + "license": [ + { + "type": "Public Domain", + "url": "http://geraintluff.github.io/tv4/LICENSE.txt" + }, + { + "type": "MIT", + "url": "http://jsonary.com/LICENSE.txt" + } + ], + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -20259,6 +20610,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz", @@ -21792,6 +22165,17 @@ "redis": "^5.5.6" } }, + "packages/modules/vault": { + "name": "@testcontainers/vault", + "version": "11.2.1", + "license": "MIT", + "dependencies": { + "testcontainers": "^11.2.1" + }, + "devDependencies": { + "node-vault": "^0.10.5" + } + }, "packages/modules/weaviate": { "name": "@testcontainers/weaviate", "version": "11.2.1", diff --git a/packages/modules/vault/Dockerfile b/packages/modules/vault/Dockerfile new file mode 100644 index 000000000..7f6755c2c --- /dev/null +++ b/packages/modules/vault/Dockerfile @@ -0,0 +1 @@ +FROM hashicorp/vault:1.13.0 \ No newline at end of file diff --git a/packages/modules/vault/package.json b/packages/modules/vault/package.json new file mode 100644 index 000000000..d6b9e4e47 --- /dev/null +++ b/packages/modules/vault/package.json @@ -0,0 +1,38 @@ +{ + "name": "@testcontainers/vault", + "version": "11.2.1", + "license": "MIT", + "keywords": [ + "vault", + "hashicorp", + "testing", + "docker", + "testcontainers" + ], + "description": "HashiCorp Vault module for Testcontainers", + "homepage": "https://github.com/testcontainers/testcontainers-node#readme", + "repository": { + "type": "git", + "url": "https://github.com/testcontainers/testcontainers-node" + }, + "bugs": { + "url": "https://github.com/testcontainers/testcontainers-node/issues" + }, + "main": "build/index.js", + "files": [ + "build" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepack": "shx cp ../../../README.md . && shx cp ../../../LICENSE .", + "build": "tsc --project tsconfig.build.json" + }, + "devDependencies": { + "node-vault": "^0.10.5" + }, + "dependencies": { + "testcontainers": "^11.2.1" + } +} diff --git a/packages/modules/vault/src/index.ts b/packages/modules/vault/src/index.ts new file mode 100644 index 000000000..2cfba6221 --- /dev/null +++ b/packages/modules/vault/src/index.ts @@ -0,0 +1 @@ +export { StartedVaultContainer, VaultContainer } from "./vault-container"; diff --git a/packages/modules/vault/src/vault-container.test.ts b/packages/modules/vault/src/vault-container.test.ts new file mode 100644 index 000000000..8911286a6 --- /dev/null +++ b/packages/modules/vault/src/vault-container.test.ts @@ -0,0 +1,51 @@ +import vault from "node-vault"; +import { getImage } from "../../../testcontainers/src/utils/test-helper"; +import { VaultContainer } from "./vault-container"; + +const VAULT_TOKEN = "my-root-token"; +const IMAGE = getImage(__dirname); + +describe("VaultContainer", { timeout: 180_000 }, () => { + // inside_block:readWrite { + it("should start Vault and allow reading/writing secrets", async () => { + const container = await new VaultContainer(IMAGE).withVaultToken(VAULT_TOKEN).start(); + + const client = vault({ + apiVersion: "v1", + endpoint: container.getAddress(), + token: container.getRootToken(), + }); + + await client.write("secret/data/hello", { + data: { + message: "world", + other: "vault", + }, + }); + + const result = await client.read("secret/data/hello"); + const data = result?.data?.data; + + expect(data.message).toBe("world"); + expect(data.other).toBe("vault"); + + await container.stop(); + }); + // } + + // inside_block:initCommands { + it("should execute init commands using vault CLI", async () => { + const container = await new VaultContainer(IMAGE) + .withVaultToken(VAULT_TOKEN) + .withInitCommands("secrets enable transit", "write -f transit/keys/my-key") + .start(); + + const result = await container.exec(["vault", "read", "-format=json", "transit/keys/my-key"]); + + expect(result.exitCode).toBe(0); + expect(result.output).toContain("my-key"); + + await container.stop(); + }); + // } +}); diff --git a/packages/modules/vault/src/vault-container.ts b/packages/modules/vault/src/vault-container.ts new file mode 100644 index 000000000..5fbea92da --- /dev/null +++ b/packages/modules/vault/src/vault-container.ts @@ -0,0 +1,135 @@ +import { AbstractStartedContainer, GenericContainer, Wait } from "testcontainers"; + +const VAULT_PORT = 8200; + +/** + * Testcontainers module for HashiCorp Vault. + * + * This container exposes Vault on port 8200, sets up a wait strategy using the health check endpoint, and supports: + * - Supplying a root token + * - Executing post-start CLI init commands + */ +export class VaultContainer extends GenericContainer { + private readonly initCommands: string[] = []; + private token?: string; + + /** + * Constructs a VaultContainer with a default image and healthcheck strategy. + * + * - Sets VAULT_ADDR to internal container address + * - Adds IPC_LOCK capability (required by Vault) + * - Exposes Vault on port 8200 + * - Waits for HTTP 200 response from /v1/sys/health + * @param image Docker image to use (e.g. `hashicorp/vault:1.13.0`) + */ + constructor(image: string) { + super(image); + + this.withExposedPorts(VAULT_PORT) + .withEnvironment({ VAULT_ADDR: `http://0.0.0.0:${VAULT_PORT}` }) + .withAddedCapabilities("IPC_LOCK") + .withWaitStrategy(Wait.forHttp("/v1/sys/health", VAULT_PORT).forStatusCode(200)); + } + + /** + * Sets a root token to be used with Vault, passed via environment variables. + * + * @param token Vault root token + * @returns this + */ + public withVaultToken(token: string): this { + this.token = token; + this.withEnvironment({ + VAULT_DEV_ROOT_TOKEN_ID: token, + VAULT_TOKEN: token, + }); + return this; + } + + /** + * Registers one or more Vault CLI init commands to be run after container starts. + * + * Example: + * .withInitCommands("secrets enable transit", "kv put secret/foo bar=baz") + * + * @param commands Vault CLI commands (without `vault` prefix) + * @returns this + */ + public withInitCommands(...commands: string[]): this { + this.initCommands.push(...commands); + return this; + } + + /** + * Starts the Vault container and executes any registered init commands. + * + * Wraps the base container in a StartedVaultContainer with helper accessors. + */ + public override async start(): Promise { + const started = await super.start(); + const container = new StartedVaultContainer(started, this.token); + + if (this.initCommands.length > 0) { + await container.execVaultCommands(this.initCommands); + } + + return container; + } +} + +/** + * A running Vault container, with accessors for port, address, and exec helper. + */ +export class StartedVaultContainer extends AbstractStartedContainer { + constructor( + startedTestContainer: AbstractStartedContainer["startedTestContainer"], + private readonly token?: string + ) { + super(startedTestContainer); + } + + /** + * Returns the mapped host port for Vault (default: 8200). + */ + public getVaultPort(): number { + return this.getMappedPort(VAULT_PORT); + } + + /** + * Returns the full Vault HTTP address (e.g., http://localhost:32768). + */ + public getAddress(): string { + return `http://${this.getHost()}:${this.getVaultPort()}`; + } + + /** + * Returns the root token set at container creation time, if any. + */ + public getRootToken(): string | undefined { + return this.token; + } + + /** + * Executes Vault CLI commands inside the container after it has started. + * + * This is typically used to pre-configure secret engines or seed test data. + * + * @example + * await container.execVaultCommands([ + * "secrets enable transit", + * "write -f transit/keys/my-key", + * "kv put secret/my-secret value=123", + * ]); + * + * @param commands Array of Vault CLI commands (without the `vault` prefix) + * @throws If the command fails (non-zero exit code) + */ + public async execVaultCommands(commands: string[]): Promise { + const cmd = commands.map((c) => `vault ${c}`).join(" && "); + const result = await this.exec(["/bin/sh", "-c", cmd]); + + if (result.exitCode !== 0) { + throw new Error(`execVaultCommands failed with exit code ${result.exitCode}: ${result.output}`); + } + } +} diff --git a/packages/modules/vault/tsconfig.build.json b/packages/modules/vault/tsconfig.build.json new file mode 100644 index 000000000..ff7390b10 --- /dev/null +++ b/packages/modules/vault/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "build", + "src/**/*.test.ts" + ], + "references": [ + { + "path": "../../testcontainers" + } + ] +} \ No newline at end of file diff --git a/packages/modules/vault/tsconfig.json b/packages/modules/vault/tsconfig.json new file mode 100644 index 000000000..4d74c3e41 --- /dev/null +++ b/packages/modules/vault/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build", + "paths": { + "testcontainers": [ + "../../testcontainers/src" + ] + } + }, + "exclude": [ + "build" + ], + "references": [ + { + "path": "../../testcontainers" + } + ] +} \ No newline at end of file