Configuration¶
Cryptographic parameters live in config.yaml (privacy budgets, data bounds,
the library query plan). Deployment concerns come from environment variables,
so the same file can be mounted anywhere unchanged.
| Variable | Default | Purpose |
|---|---|---|
CRM_AUTH_MODE |
dev |
dev or oidc |
CRM_OIDC_ISSUER |
– | Identity provider base URL (alias: KEYCLOAK_URL) |
CRM_OIDC_REALM |
– | Realm, if the provider uses one (alias: KEYCLOAK_REALM) |
CRM_OIDC_CLIENT_ID |
– | Client ID (alias: KEYCLOAK_CLIENT_ID) |
CRM_OIDC_CLIENT_SECRET |
– | Client secret (alias: KEYCLOAK_CLIENT_SECRET) |
CRM_OIDC_AUDIENCE |
client ID | Audience a token must carry (alias: EXPECTED_AUDIENCE) |
CRM_OIDC_JWKS_URI |
discovered | Signing key endpoint, to skip discovery (alias: JWKS_URI) |
CRM_OIDC_VERIFY_AUDIENCE |
auto | Verified whenever an audience or client ID is set; false disables it |
CRM_OIDC_ENFORCE_PERMISSIONS |
true |
Require a per-action permission in the token |
CRM_OIDC_PERMISSION_PREFIX |
crypto |
Namespace of those permissions |
CRM_LEDGER_ENDPOINT |
– | Distributed ledger receiving audit records (alias: LEDGER_ENDPOINT) |
CRM_CM_ENDPOINT |
– | Compliance manager; records are uploaded to <endpoint>/input (alias: CM_ENDPOINT) |
CRM_CM_USE_CASE_ID |
– | Use case the compliance manager files records under (alias: CM_USE_CASE_ID) |
CRM_ILM_ENDPOINT |
– | Identity manager receiving audit records |
CRM_FORWARD_ATTEMPTS |
3 |
Deliveries tried per audit record and downstream service |
CRM_CONFIG_PATH |
./config.yaml |
Configuration file to load |
CRM_AUDIT_LOG_PATH |
./audit/crm-audit.jsonl |
Local audit trail |
CRM_BUDGET_STORE_PATH |
./audit/budgets.json |
Per-user budget state; unset to keep in memory |
CRM_LOG_LEVEL |
INFO |
Log verbosity (alias: LOG_LEVEL) |
CRM_MAX_REQUEST_BYTES |
10485760 |
Largest accepted request body (10 MiB) |
Authentication¶
Token verification is written against generic OpenID Connect — discovery
via .well-known/openid-configuration, then local JWKS signature and expiry
validation — rather than any one vendor's API, so it works with any conforming
provider. Only asymmetric signature algorithms are accepted, which closes the
algorithm-confusion attack where a token is signed with HMAC using the
provider's public key as the secret.
In oidc mode a token must pass four checks: signature, expiry, issuer and
audience. The audience is this service's client ID unless CRM_OIDC_AUDIENCE
says otherwise, and is what stops a token issued for another platform service
from being replayed here. With Keycloak, a typical configuration is:
CRM_AUTH_MODE=oidc
KEYCLOAK_URL=https://keycloak.example
KEYCLOAK_REALM=recitals
KEYCLOAK_CLIENT_ID=recitals-cryptography-manager
A valid token identifies the caller; it does not by itself allow anything.
Each action needs its own permission, named crypto.<action>:
| Action | Permission |
|---|---|
encrypt, decrypt |
crypto.encrypt, crypto.decrypt |
generate_key, rotate_key, key_info |
crypto.generate_key, crypto.rotate_key, crypto.key_info |
query (differential privacy) |
crypto.query |
A permission is granted by a matching entry in the token's scope, by a realm
role of that name, or by a client role on this service — where the prefix may
be dropped, so the role encrypt under
resource_access.<client ID>.roles grants crypto.encrypt. A caller without
it receives 403 and the name of the permission it lacks.
The default dev mode accepts any bearer token without verifying it and
derives a stable per-caller identity from a digest of the token, so budgets
still separate correctly. It enforces no permissions. It exists so the service
is runnable without an identity provider. Never use it in a real
deployment.
Privacy Budgets¶
Each user gets privacy_budget_limit epsilon. Every differential privacy
request is checked against the remaining budget before it runs and debited only
after it succeeds, so a failed query costs nothing. A request costing more than
what remains is refused with 400; a user with nothing left gets 429.
Budgets persist to CRM_BUDGET_STORE_PATH, because a budget that resets when
the container restarts is not a privacy guarantee. The store is single-node; a
multi-replica deployment needs a shared backend, and BudgetStore is the seam
to add one behind.
Audit Trail¶
Every request produces exactly one audit record — including requests that were refused, so a rejection is as traceable as a success. Records are appended as JSON lines locally and, when the corresponding endpoints are configured, forwarded to the ledger, compliance and identity services. Forwarding is best-effort and runs off the request path: a delivery is retried a few times, and a downstream service that stays unreachable is logged and never fails the caller's request. The local file remains the complete record.
Besides the outcome, a record names the caller, the client their token was
issued to and its issuer, and carries the request's metadata, so a caller can
pass a workflow identifier through for later correlation.
The compliance manager is sent RDF rather than JSON, as its input API requires:
each record is rendered as a Turtle document using the Data Privacy Vocabulary
and uploaded to /input, with the outcome reduced to passed or failed. It
files every submission under a use case, so set CRM_CM_USE_CASE_ID, or name
one per request in metadata.use_case_id; a record with neither is not sent.
The use case must already exist there.
When the service has client credentials of its own (oidc mode with a client
secret), it obtains a service token and presents it on these calls.
Sensitive material never reaches a record. Key material, plaintext, ciphertext, tokens, passwords and secret shares are redacted by key name, and bulk data is reduced to a size summary, so a record says how much was processed without saying what it was.