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 embedded.md
Raw

docs/use_cases/embedded.md

# Embedded KMS in an application

Use this pattern when one Elixir/Phoenix application should run KMS in-process and store KMS key material locally.

The app calls the normal `KMS` API through `KMS.Client.Local`. KMS starts its OTP supervision tree with the application.

## What runs

Embedded mode starts:

- the KMS OTP application;
- the configured KMS repo using SQLite by default;
- `KMS.Cache`;
- optional RMK-wrapped signing-key custody and caller-process signing;
- session cleanup jobs;
- optional fallback bootstrap;
- no HTTP API unless you also start the KMS web application.

The application and KMS share one BEAM VM and one trust boundary.

A narrow host application can start only selected KMS components. For example, an application that supplies its own Ecto repo and needs only process-local key caching can use:

```elixir
config :kms, :components, [:cache]
config :kms, KMS.Repo, repo: MyApp.Repo
```

With no `:sessions` component, production startup does not require KMS session configuration. The host remains responsible for starting and migrating `MyApp.Repo`. Any KMS schemas used through that repo must have matching host migrations. This includes the `signing_keys` table when the host uses `KMS.install_signing_key_once/1` and `KMS.sign_digest/2`.

## When to use

Good fit:

- one application owns the encrypted data;
- app-level authorization is enough;
- you want KEK aliases, RMK wrapping, and KMS migrations;
- a separate KMS service would be operational overhead;
- SQLite persistence is sufficient for KMS key tables.

Examples:

- Phoenix app with encrypted Ecto fields;
- internal tool with a few encrypted secrets;
- single-node deployment with local disk backups;
- small SaaS app where one KEK alias per owner or tenant is enough.

Poor fit:

- multiple app servers need one central KMS authority;
- app hosts must not read KMS DB/RMK material;
- KMS needs separate deployment, audit, or access controls;
- you need a public/user-facing encryption API.

Use [remote KMS](remote.md) for those.

## Minimal Phoenix config

Start KMS with the Phoenix app:

```elixir
def application do
  [
    mod: {MyApp.Application, []},
    extra_applications: [:logger, :runtime_tools, :kms]
  ]
end
```

Configure local client, SQLite KMS repo, and local RMK:

```elixir
config :my_app,
  ecto_repos: [MyApp.Repo, KMS.Repo.SQLite]

config :kms, ecto_repos: [KMS.Repo.SQLite]
config :kms, KMS.Repo, repo: KMS.Repo.SQLite
config :kms, :client, KMS.Client.Local
config :kms, :rmk_provider, KMS.RMK.Local

config :kms, KMS.RMK.Local,
  key_path: Path.expand("../priv/kms_rmk_#{config_env()}.key", __DIR__)
```

In dev/test, point KMS SQLite at a writable file:

```elixir
config :kms, KMS.Repo.SQLite,
  database: Path.expand("../tmp/kms/kms_dev.sqlite3", __DIR__),
  journal_mode: :wal,
  foreign_keys: :on,
  busy_timeout: 5_000,
  priv: "priv/repo",
  migration_primary_key: false,
  migration_foreign_key: [type: :text],
  migration_timestamps: [type: :utc_datetime_usec]
```

See [Database configuration](../database.md).

## Field-level encryption shape

Use an alias per owner/tenant:

```elixir
alias_name = "owner:#{owner_id}"
{:ok, _kek} = KMS.create_alias(alias_name)
```

Use row/field AAD:

```elixir
aad = "document:#{document_id}/body"
{:ok, encrypted_body} = KMS.encrypt_alias(body, alias_name, aad: aad)
{:ok, body} = KMS.decrypt_alias(encrypted_body, alias_name, aad: aad)
```

The [Quickstart](../quickstart.md) shows this inside a Phoenix changeset with a working example under `example/phoenix_example`.

## Trust boundary

Embedded mode trusts the application process.

Local calls without `session:` are trusted/admin calls. That is fine when your application owns authorization and only calls KMS after normal app-level checks.

If KMS should enforce per-user authorization, pass `session:` and use principals/bindings. See [Multi-user encryption](multi_user_encryption.md).

## Persistence and backup

With SQLite + local RMK, back up:

- KMS SQLite database and WAL/SHM sidecars;
- local RMK key file;
- application database rows containing ciphertext.

Keep RMK key backups access-controlled separately from database backups when possible.

## Operational notes

- Use one writer per SQLite database file.
- Store SQLite and RMK files on durable disk, not ephemeral container filesystem.
- Disable production crash dumps: `ERL_CRASH_DUMP_BYTES=0`.
- Signing runs in calling processes. The sensitive KMS cache retains decoded signing material, and each active signing caller briefly receives that material.
- Use external RMK providers when DB backups need a separate key-service/IAM boundary.
- Do not enable `KMS.API` accidentally; embedded mode does not need it.