Production Checklist
Work through this list before an instance serves real wallets and users, and again after major upgrades. Each item links to the guide that explains it.
Secrets
-
MASTER_SECRETis a random value of at least 32 characters (openssl rand -base64 32), kept in a secret manager and never changed while the data exists (Encryption keys). - The encryption key comes from
vault,awsorazureinstead of being derived fromMASTER_SECRET(Encryption keys). -
AUTH_CLIENT_SECRETis random. Changing it in the environment later has no effect; rotate it withPOST /api/client/<id>/rotate-secret(Tenants and access). - No default or example credentials remain: database password, RustFS/S3
keys, Vault token
root, Grafanaadmin/admin, the demo clientroot/root. - No demo tenant or bundled demo keys (
eudiplo demo,--demo-tenant,--template demo) are deployed. - Secrets in tenant config files are
${VAR}placeholders, not literal values (Configuration as code).
Network and TLS
-
PUBLIC_URLis the publichttps://URL, and its certificate is issued by a public CA. The backend is served at the root of its host name (TLS and reverse proxy). - The backend port is reachable only through the reverse proxy
(
EUDIPLO_BIND_ADDRESS=127.0.0.1in Compose). -
CORS_ORIGINSlists exactly the origins of the web client and of your own frontends that call/api/*from the browser, for exampleCORS_ORIGINS=https://console.example.com. Protocol endpoints stay open. - The reverse proxy, API gateway or WAF rate-limits the token, PAR and
credential endpoints and the management API; EUDIPLO has no built-in rate
limiting. Where possible, restrict
/api/*to known networks. -
OUTBOUND_URL_ALLOW_HTTPandOUTBOUND_URL_ALLOW_PRIVATE_NETWORKstayfalse(the default since 9.0). Webhooks, attribute providers, metadata, trust lists, status lists, federation entities, external authorization servers and the upstream provider of a chained authorization server then need public HTTPS targets; CRLs may use HTTP.OUTBOUND_URL_ALLOWED_HOSTSadditionally limits them to the listed hosts; it does not exempt a host from either check, so a webhook on a private address still needsOUTBOUND_URL_ALLOW_PRIVATE_NETWORK=true. - Egress is restricted at the network level if EUDIPLO must not reach internal services: KMS providers, including those in a tenant's KMS configuration, are called without the outbound URL policy (security model).
- Services with private CAs (Vault, KMS, webhooks, PostgreSQL) are trusted
through
NODE_EXTRA_CA_CERTSorDB_SSL_CA_PATH, never by disabling certificate checks.
Access
- Each application has its own API client with only the roles it needs,
usually
issuance:offerandpresentation:request, limited withallowedIssuanceConfigsandallowedPresentationConfigs(Tenants and access). -
tenants:manageandtenant:adminare held only by operators and the configuration pipeline (Roles). - People sign in through your identity provider rather than with shared client secrets (Keycloak SSO).
Keys, certificates and checks
- Signing keys live in the KMS your security model requires
(KMS); the
dbprovider stores them encrypted in the database. - Access and registration certificates come from your registrar instead of self-signed certificates (Registrar, Registration certificates).
- No
SKIP_*flag is set; the startup log lists active ones (Skip flags). - Issuance configurations require DPoP and wallet attestation where your use case demands it (Authorization servers).
Data and logs
- PostgreSQL is used for anything beyond a single small instance, with
DB_SSL=truewhen it is not on the same host (Database). - Session retention (
SESSION_TTL,SESSION_CLEANUP_MODE) and audit log retention (AUDIT_LOG_RETENTION_DAYS,AUDIT_LOG_MAX_ENTRIES_PER_TENANT) match your data protection rules (Database, Logging). - Logs do not capture personal data:
LOG_REDACT_SENSITIVE_DATA=true,LOG_HTTP_RESPONSE_BODY=false,LOG_OID4VP_DECRYPTED_RESPONSE=false,LOG_SESSION_STOREnotverbose(Logging).
Backups
Back up all of these together; a restore needs every part:
| What | How |
|---|---|
| Database | pg_dump (bundled stack: docker compose exec -T postgres pg_dump -U eudiplo -Fc eudiplo > eudiplo.dump), or the SQLite file <FOLDER>/service.db while the backend is stopped |
| Object storage | Bucket versioning or replication, or a copy of LOCAL_STORAGE_DIR |
| Encryption key | MASTER_SECRET (with ENCRYPTION_KEY_SOURCE=env) or the external key, in your secret manager |
| Signing keys in a KMS | The KMS's own backup (Vault snapshots, AWS KMS key policies, HSM backup) |
| Configuration | The config folder in version control, or eudiplo config export per tenant |
| Environment | The env file or Kubernetes secret, without which the backend does not start |
- Backups run on a schedule and a restore has been tested on a separate instance.
- A backup is taken before every upgrade; migrations cannot be undone (Database).
Operations
- Image versions are pinned to a release (
X.Y.Z), and backend and web client run the same version (Kubernetes, CLI). - Metrics, traces and logs reach your monitoring, and someone receives the alerts (Monitoring).
-
eudiplo doctor --strictpasses, also as a scheduled check withEUDIPLO_CLIENT_IDandEUDIPLO_CLIENT_SECRETset (CLI):
eudiplo doctor --instance production --strict