smithingdev/laravel-vault
Composer 安装命令:
composer require smithingdev/laravel-vault
包简介
A small, focused Laravel client for HashiCorp Vault: AppRole/token auth and KV2 secret CRUD.
README 文档
README
A small, focused Laravel client for HashiCorp Vault: AppRole/token authentication and KV2 secret CRUD. No domain assumptions — just the transport.
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13 (
illuminate/http+illuminate/support)
Install
composer require smithingdev/laravel-vault php artisan vendor:publish --tag=vault-config
The service provider and Vault facade are auto-discovered.
Configure
Set these in .env:
VAULT_ADDR=https://vault.example.com VAULT_AUTH_METHOD=approle # approle | token # AppRole VAULT_ROLE_ID=... VAULT_SECRET_ID=... # or a pre-issued token VAULT_TOKEN=hvs.... # Namespaces (see "Namespace modes" below) VAULT_NAMESPACE_MODE=header # header (default) | path VAULT_NAMESPACE=acme/dev # Enterprise namespace (optional) VAULT_KV_MOUNT=secret # KV2 mount point (header mode) VAULT_VERIFY=true # TLS verification (see below) VAULT_TIMEOUT=10 # per-request timeout in seconds (default 10) VAULT_LOG_CHANNEL= # optional log channel (see below)
TLS verification
VAULT_VERIFY controls how the Vault server's certificate is checked:
true(default) — verify against the system CA bundle. Keep this in production.false— disable verification. Only for local/self-signed setups you trust.- a filesystem path — verify against a custom CA bundle (e.g. a private CA).
Logging
VAULT_LOG_CHANNEL is optional and disabled by default. When set to a configured
log channel, the package logs a line on successful authentication and logs the raw
Vault response body (at error level) whenever a request fails — the body is logged
rather than thrown, so it never leaks into exception messages (see Errors).
Namespace modes
Vault supports two equivalent ways to address an Enterprise namespace, and this
package lets you pick via VAULT_NAMESPACE_MODE:
-
header(default, conventional) — the model the Vault CLI and official clients use.VAULT_NAMESPACEis sent as theX-Vault-Namespaceheader on every request, andVAULT_KV_MOUNTis the KV2 mount point. Requests go to/v1/<mount>/data/<path>. If you don't use Enterprise namespaces, leaveVAULT_NAMESPACEempty and setVAULT_KV_MOUNTto your mount (defaultsecret). -
path(legacy) —VAULT_KV_NAMESPACEis the full path where the KV2 engine is mounted and is embedded directly in every KV2 URI (/v1/<kv_namespace>/data/<path>); the namespace header is sent on login only.VAULT_NAMESPACE_MODE=path VAULT_NAMESPACE=acme/dev # X-Vault-Namespace on login VAULT_KV_NAMESPACE=acme/dev/kv-secrets # baked into every KV2 URI
Upgrading from a version without
VAULT_NAMESPACE_MODE: the default is nowheader. If you previously relied onVAULT_KV_NAMESPACE, setVAULT_NAMESPACE_MODE=path(and keepVAULT_KV_NAMESPACE) to retain the old behavior.
Usage
Use the facade, or inject VaultService anywhere the container resolves dependencies:
use Smithingdev\Vault\Facades\Vault; Vault::read('myapp/database');
use Smithingdev\Vault\VaultService; class RotateSecrets { public function __construct(private VaultService $vault) {} public function handle(): void { $this->vault->read('myapp/database'); } }
KV2 operations
Vault::write('myapp/database', ['username' => 'app', 'password' => '...']); $secrets = Vault::read('myapp/database'); // array|null $old = Vault::read('myapp/database', version: 2); $keys = Vault::list('myapp'); // array<string> Vault::delete('myapp/database'); // soft-delete latest version (recoverable) Vault::destroy('myapp/database'); // permanent: all versions + metadata
Return values and not-found behavior:
| Method | Returns | When the path is missing (404) |
|---|---|---|
read($path, $version = null) |
array<string, mixed>|null |
null (also null if a 200 response carries no data.data) |
write($path, array $data) |
void |
— (creates the path) |
list($path) |
array<int, string> (keys directly under the path) |
[] |
delete($path) |
void |
no-op |
destroy($path) |
void |
no-op |
delete() soft-deletes the latest version — KV2 keeps it recoverable via Vault's
undelete endpoint until it's destroyed. destroy() permanently removes all
versions and metadata and cannot be undone. Any other failure throws (see Errors).
Authentication
authenticate() runs automatically on the first call and the token is cached for
the process lifetime. With AppRole the client re-authenticates once on a 401/403
and retries the failed request, so an expired token is transparently refreshed.
The token auth method uses a pre-issued token and cannot self-refresh.
Errors
Every failure surfaces as a single exception type, Smithingdev\Vault\Exceptions\VaultException
(a RuntimeException), so you only have to catch one thing:
use Smithingdev\Vault\Exceptions\VaultException; try { $secrets = Vault::read('myapp/database'); } catch (VaultException $e) { // log, alert, fall back, ... }
A VaultException is thrown when:
- Vault returns a failing HTTP status other than the not-found cases handled above (read/list/delete/destroy treat 404 as "missing", not an error).
- A transport-level problem occurs (timeout, DNS, connection refused) — these are
wrapped so you never have to catch Guzzle's
ConnectionExceptionseparately. - Authentication is misconfigured (no token for
tokenauth, missing AppRole credentials) or login fails. namespace_modeis set to anything other thanheaderorpath.
Exception messages are intentionally generic (e.g. Vault read 'foo' failed (HTTP 500).).
The raw Vault response body is not included in the message — it is sent to the
configured log channel instead (see Logging), so secrets and internal
details don't leak into stack traces or error reporting.
Multiple namespaces
To promote secrets between environments with a single login, derive a clone bound to a sibling namespace. The clone shares the already-acquired token, so no second login happens.
In header mode, swap the Enterprise namespace with withNamespace():
$prod = app(VaultService::class)->withNamespace('acme/prod'); $prod->write('myapp/database', $secrets);
In path mode, swap the full KV namespace path with withKvNamespace():
$prod = app(VaultService::class)->withKvNamespace('acme/prod/kv-secrets'); $prod->write('myapp/database', $secrets);
Each setter has a matching getter for reading what the instance is currently bound to — useful for deriving a sibling target from the current one:
Vault::namespace(); // current Enterprise namespace (header mode), e.g. "acme/dev" Vault::kvNamespace(); // current KV namespace path (path mode), e.g. "acme/dev/kv-secrets"
What this package is not
It deliberately ships only auth + KV2 CRUD. Application-specific concepts belong in your app, not here.
Testing
composer install ./vendor/bin/pest
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 3
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-16