Packages

Key management service with embedded OTP runtime, optional HTTP API, SQLite/PostgreSQL storage, and external RMK providers

Current section

Files

Jump to
kms docs use_cases remote.md
Raw

docs/use_cases/remote.md

# Remote KMS service

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](embedded.md) for simple single-app deployments.

## Server configuration

On the KMS authority release, run `:kms` with the optional `KMS.API` server enabled.

```elixir
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:

```sh
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:

```sh
mix ecto.migrate
```

Health checks:

```sh
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:

```elixir
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:

```elixir
{: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:`:

```elixir
{: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](multi_user_encryption.md).

## Direct HTTP example

Most apps should use `KMS.Client.Remote`. Direct HTTP is useful for smoke tests:

```sh
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](../observability.md) 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.