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.API server on the KMS authority host;
  • KMS.Client.Remote in 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"
  )

See Multi-user encryption.

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/ready for 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.