symfony / hashicorp-vault-key-management
Symfony HashiCorp Vault Key Management Bridge
Package info
github.com/symfony/hashicorp-vault-key-management
Type:symfony-key-management-bridge
pkg:composer/symfony/hashicorp-vault-key-management
Requires
- php: >=8.4.1
- symfony/http-client: ^7.4|^8.0
- symfony/key-management: ^8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-14 08:45:52 UTC
README
Provides an implementation of Symfony\Component\KeyManagement\EncrypterInterface and
Symfony\Component\KeyManagement\DataKeyGeneratorInterface backed by the
HashiCorp Vault Transit
secret engine. Encryption, decryption and data-key generation never expose
the master key: Vault performs them server-side.
This Bridge is experimental. Experimental features are not covered by Symfony's Backward Compatibility Promise.
use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\KeyManagement\Bridge\HashiCorpVault\TransitKms; $kms = new TransitKms( HttpClient::createForBaseUri('https://vault.example.com:8200/v1/'), $_SERVER['VAULT_TOKEN'], ); $ciphertext = $kms->encrypt('app-key', 'hello world'); // → Ciphertext blob looks like "vault:v1:..." $plaintext = $kms->decrypt($ciphertext); $dataKey = $kms->generateDataKey('app-key', 32); $result = $dataKey->use(fn (string $dek): string => /* local AEAD encrypt */);
DSN scheme
hashicorp-vault-transit://<token>@<host>[:<port>][/<path>][?mount=<mount>&namespace=<ns>]
The HTTP base URI is built from <host>[:<port>]<path>; if <path> is empty
it defaults to /v1/. The Vault token is taken from the user component of
the DSN. Default mount point is transit. The namespace option maps to
Vault's X-Vault-Namespace header (Vault Enterprise multi-tenancy).
Example:
hashicorp-vault-transit://s.token@vault.example.com:8200/v1/?mount=transit&namespace=tenant-acme
AAD support
The $aad argument is forwarded as Vault's context parameter (HKDF
context, base64-encoded). This works for keys created with derived=true.
Vault does not reject a context on non-derived keys, it silently ignores it,
which would make an AAD mismatch decrypt fine. The bridge therefore reads the
key configuration once per key (GET <mount>/keys/<name>) when a non-empty
$aad is used and refuses AAD for non-derived keys with an
UnsupportedOperationException.
That preflight is a read on the key itself, so a token whose policy only
grants update on the encryption endpoints is no longer enough as soon as an
AAD is passed: the request fails with a RuntimeException reporting HTTP 403.
Grant read on <mount>/keys/* too:
path "transit/encrypt/*" { capabilities = ["update"] } path "transit/decrypt/*" { capabilities = ["update"] } path "transit/datakey/plaintext/*" { capabilities = ["update"] } # only needed when AAD is used, to tell derived keys from non-derived ones path "transit/keys/*" { capabilities = ["read"] }
Note that Vault's context is a key-derivation input, not authenticated data
in the AEAD sense. The practical effect is similar (different context →
unable to decrypt) but the security model differs slightly from backends like
AWS KMS, where EncryptionContext is integrity-protected. AAD is treated as
opaque bytes; if you have structured data, serialize it (e.g. canonical JSON
with sorted keys) yourself and pass the resulting string.