Use this pattern when application servers should call a dedicated KMS authority process over HTTP instead of opening KMS storage or RMK material locally.
Remote KMS uses:
- optional
KMS.APIserver on the KMS authority host; KMS.Client.Remotein application hosts;- bearer API token for the backend HTTP API;
- optional KMS sessions for principal-scoped authorization.
When to use
Good fit:
- several applications need one KMS authority;
- app servers should not have KMS database credentials;
- app servers should not have local RMK key files or cloud RMK credentials;
- operations want separate deploy/backup/access controls for KMS;
- KMS traffic stays on a private network or TLS-protected service mesh.
Examples:
- central KMS service for internal Phoenix apps;
- KMS host with external RMK credentials while app hosts only have an API token;
- tenant service encrypting/decrypting through a private KMS endpoint;
- gradual move from embedded KMS to service-owned KMS.
Poor fit:
- browser/public clients calling KMS directly;
- untrusted network without TLS;
- workloads that cannot tolerate network dependency for decrypt;
- deployments where one embedded app is simpler and sufficient.
Use embedded KMS for simple single-app deployments.
Server configuration
On the KMS authority release, run :kms with the optional KMS.API server enabled.
config :kms, :api,
server: true,
port: 4004,
scheme: :http,
api_token: System.fetch_env!("KMS_API_TOKEN")Configure KMS persistence and RMK on the server only:
export KMS_DATABASE_BACKEND=sqlite
export KMS_DATA_DIR=/var/lib/kms
export KMS_RMK_LOCAL_KEY_PATH=/var/lib/kms/rmk.key
export KMS_SESSION_HASH_KEY=<high-entropy-secret>
export KMS_API_TOKEN=<high-entropy-admin-token>
export ERL_CRASH_DUMP_BYTES=0
Run migrations before serving traffic:
mix ecto.migrate
Health checks:
curl -fsS http://localhost:4004/health/live
curl -fsS http://localhost:4004/health/ready
Expose the service only on a trusted private network or behind TLS. Do not expose it directly to browsers or the public internet.
Client configuration
On application servers:
config :kms, :client, KMS.Client.Remote
config :kms, KMS.Client.Remote,
base_url: "https://kms.internal.example.com",
api_token: System.fetch_env!("KMS_REMOTE_API_TOKEN")Application code uses the same facade:
{:ok, _kek} = KMS.create_alias("owner:alice")
{:ok, encrypted} =
KMS.encrypt_alias("secret", "owner:alice", aad: "document:123/body")
{:ok, "secret"} =
KMS.decrypt_alias(encrypted, "owner:alice", aad: "document:123/body")The remote client sends requests to KMS.API and maps JSON responses back to KMS return values.
HTTP API token vs KMS session
There are two different bearer credentials:
- HTTP API token: authenticates app-to-KMS backend calls to
KMS.API. - KMS session secret: optional principal-scoped authorization token used by KMS operations.
The API token protects the remote admin surface. It does not replace application authorization.
When KMS should enforce per-principal permissions, create sessions and pass session::
{:ok, session} = KMS.create_session("users", "alice", %{password: password})
{:ok, encrypted} =
KMS.encrypt_alias("secret", "tenant/acme/docs",
session: session.secret,
aad: "document:123/body"
)Direct HTTP example
Most apps should use KMS.Client.Remote. Direct HTTP is useful for smoke tests:
curl -fsS \
-H "authorization: Bearer $KMS_API_TOKEN" \
-H "content-type: application/json" \
-d '{"alias":"owner:alice","cleartext":"secret","opts":{"aad":"document:123/body"}}' \
https://kms.internal.example.com/api/encrypt
Security notes
- Treat the HTTP API as an admin backend API.
- Require a high-entropy API token.
- Use TLS or a trusted private network.
- Disable request-body logging in proxies, APM, and app logs.
- Never log API tokens, session secrets, plaintext, ciphertext payloads, passwords, factor codes, or RMK material.
- Keep RMK credentials and KMS database access only on KMS authority hosts.
- Run fallback registry and recovery operations locally on the KMS host; they are absent from the HTTP API.
- Use
/health/readyfor dependency readiness and Observability for telemetry.
Availability notes
Remote KMS adds a network dependency to encrypt/decrypt paths. Remote get_* helpers
return nil only for an API not_found response; transport, HTTP, and malformed-response
failures return {:error, reason}. Prefer fetch_* when callers always want tagged results.
Design app behavior for KMS outages:
- fail closed for decrypt-sensitive operations;
- avoid decrypting during request paths when cached plaintext or async jobs are acceptable;
- set reasonable HTTP timeouts at the client/proxy layer;
- monitor KMS latency, RMK latency, cache hit rate, and readiness.