KMS bindings let KMS enforce which principal can perform which operation on which scope.
Use bindings when KMS should decide decrypt/encrypt permission. If your app owns all authorization and calls KMS locally without session:, those calls are trusted/admin calls and bypass principal authorization.
Model
A binding has:
principal_id: principal receiving permission;permission: operation, e.g.kek.decrypt;scope: resource selector, e.g.kek.alias:tenant/acme/docs.
An operation is authorized when an authenticated session principal has a binding whose permission and scope match the requested operation.
Authorization is not key possession. Session-protected aliases additionally require a recipient envelope that the session's private key can decrypt. RMK-only aliases use bindings alone.
Important rule
Local calls without session: are trusted/admin execution for RMK-only aliases. They cannot decrypt session-protected aliases. Example for an RMK-only alias:
KMS.decrypt_alias(ciphertext, "tenant/acme/docs", aad: aad)Pass session: when KMS should enforce authorization:
KMS.decrypt_alias(ciphertext, "tenant/acme/docs",
session: session.secret,
aad: aad
)Remote HTTP API token authentication is separate. The API token authorizes backend access to the HTTP API; KMS sessions authorize principal-scoped KMS operations.
Permissions
Exact permissions:
| Resource | Actions |
|---|---|
kek | create, encrypt, decrypt, rewrap, manage_tags |
principal | create, read, update, delete |
principal_factor | create, read, update, delete |
session | create, read, delete |
binding | create, read, update, delete |
bindings | create, read, update, delete |
fallback_public_key | create, read, rotate, delete |
kek_fallback | create, read, unwrap |
principal_fallback | create, read, unwrap |
Wildcards:
kek.*
principal.*
*.*kek.create is unscoped because the KEK does not exist yet.
Scopes
Supported scopes:
*
kek.alias:<alias>
kek.tag:<tag>
principal.ref:<kind>/<ref>
principal.tag:<tag>
principal.self
principal_factor.id:<id>
principal_factor.principal_id:<principal_id>
principal_factor.principal:<kind>/<ref>
principal_factor.self
session.id:<id>
session.principal_id:<principal_id>
session.pending_factor_id:<factor_id>
session.principal:<kind>/<ref>
session.self
binding.id:<id>
binding.principal_id:<principal_id>
binding.permission:<permission>
binding.scope:<scope>
bindings.scope:<scope>
fallback_public_key.id:<id>
fallback_public_key.name:<name>
kek_fallback.id:<id>
kek_fallback.kek_id:<kek_id>
kek_fallback.public_key_id:<public_key_id>
principal_fallback.id:<id>
principal_fallback.principal_id:<principal_id>
principal_fallback.public_key_id:<public_key_id>Use narrow scopes in production. Prefer alias scopes for KEK operations:
kek.alias:tenant/acme/docsGrant encrypt/decrypt on one alias
{:ok, principal} =
KMS.create_principal(%{
kind: "users",
ref: "alice",
username: "alice@example.com",
password: password
})
for permission <- ["kek.encrypt", "kek.decrypt"] do
{:ok, _binding} =
KMS.create_binding(principal.id, %{
permission: permission,
scope: "kek.alias:tenant/acme/docs"
})
endUse 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"
)Self scopes
Self scopes let a principal operate on its own resource. Example use cases:
- read/update own principal record;
- read/delete own sessions;
- advance own pending factor session.
Use self scopes when possible instead of broad principal/session scopes.
Tag scopes
KEKs and principals can have tags. Tag scopes are useful when one binding should cover a group:
{:ok, _binding} =
KMS.create_binding(principal.id, %{
permission: "kek.decrypt",
scope: "kek.tag:tenant:acme"
})Tags are authorization grouping metadata. They are not AAD and not encryption context.
Least privilege
Recommended:
- grant
kek.decryptonly where needed; - separate
kek.encryptandkek.decryptfor write-only producers; - avoid
*.*except break-glass/admin principals; - keep binding management restricted;
- use MFA factors for high-value principals;
- rotate/delete sessions after use.
Debugging authorization
If an operation returns {:error, :not_allowed}:
- Confirm call passed
session:. - Confirm session is authenticated and not expired.
- Confirm principal id matches binding principal.
- Confirm permission matches operation.
- Confirm scope matches alias/tag/resource.
- Confirm AAD is correct separately; AAD mismatch is a crypto failure, not authorization.