Packages
Key management service with embedded OTP runtime, optional HTTP API, SQLite/PostgreSQL storage, and external RMK providers
Current section
Files
Jump to
Current section
Files
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.