Skip to content
Registry StackDocsLatest

Harden a production deployment

View as Markdown

Registry Relay and Registry Notary enforce authentication, authorization, key publication, and audit as software controls (see Security overview). RS-SEC-G Section 9 draws a line at the deployment: secret and key provisioning, key custody and rotation, audit retention and storage, tenant isolation, transport termination and certificate management, edge rate limiting, deployment configuration, and incident response stay with the operator. This page turns that boundary into a checklist you work through before you call a deployment production-ready.

Use this checklist before a production or evidence_grade deployment goes live, and again after any change to signing keys, audit sinks, network placement, or the declared deployment profile. It assumes you have already configured your service per the Registry Relay configuration reference or the Registry Notary operator configuration reference.

  • Decide the deployment profile (local, hosted_lab, production, or evidence_grade) you are claiming; see the deployment profile section of the Registry Relay configuration reference. The profile is never inferred from hostname or network position; you declare it.
  • Have registry-notary doctor (Notary) or your Relay startup logs available to check gate findings as you work through each item.
  • Configure signing keys through the provider abstraction (local_jwk_env, file_watch, or pkcs11) under evidence.signing_keys, not as inline YAML values. Startup fails closed if an active signing provider cannot be constructed. See the signing key provider reference.
  • Provision secrets (API-key fingerprints, source tokens, private JWKs, the audit hash secret) only through the environment variables named in config. For local development the binary accepts --env-file; for shared environments use the platform secret store and do not check dotenv files into the repository.
  • Rotate a signing key by adding a new kid, moving credential profiles to it, then moving the old key to publish_only until its configured publication window ends or verifiers no longer need it. Do not delete a key verifiers may still need.
  • Keep RS256 scoped to the eSignet relying-party (RP) client assertion key (eSignet, the MOSIP-project identity provider, is the one integration that needs RS256). Credential profile, access token, and federation response signing use Ed25519 EdDSA or ES256/P-256; RS256 is rejected there.
  • If a signing key uses provider: pkcs11, confirm the deployed binary was built with the pkcs11 capability (registry-notary build-info) and validate the module, token, and key lookup with registry-notary doctor --config <path>.
  • Relay: confirm every env-backed fingerprint.name referenced in config exists in the runtime environment, and that no raw key, fingerprint, private JWK, or full environment dump reaches a log line; see the production checklist in the Registry Relay configuration reference.
  • Configure a durable audit sink (file or syslog, not bare stdout) before you declare production or evidence_grade. A missing durable sink trips relay.audit.sink_missing / notary.audit.sink_missing, which fails startup at evidence_grade (startup_fail for both services, and already startup_fail at production for Notary).
  • Set audit.hash_secret_env to an environment variable holding at least 32 bytes of deployment-specific random secret material. Relay startup fails closed if it is missing, empty, unset, or weak.
  • Keep audit.write_policy: fail_closed (the default) unless an explicit availability exception accepts best-effort audit durability under availability_first. fail_closed means a request whose audit record cannot be written returns 503 audit.write_failed instead of a success (REQ-SEC-G-009).
  • If you rotate the audit hash secret, retain the old secret under your audit retention controls for any period during which older records must remain comparable to new ones, or accept that new records will not match old audit handles.
  • Hash-chained envelopes (prev_hash, record_hash) detect ordering gaps and accidental corruption inside the retained set, but they do not prove that every historical audit record remains present. Use off-host audit shipping when completeness matters.
  • For evidence_grade, configure deployment.evidence.audit_ack_cursor_path for every shipping target, including stdout and syslog. The off-host shipper writes registry.audit.ack_cursor.v1 after a successful hand-off. Registry Relay and Registry Notary report ready only while acked_at is fresh and last_acked_hash equals the live keyed audit chain tail. A matching cursor means the trusted shipper claims zero local backlog. The local cursor does not prove remote receipt or retention.
  • Run the shipper independently of application readiness. A signed configuration bundle produces a bundle-acceptance audit record before the service starts serving requests. The service reports 503 until the shipper acknowledges that record, then reports ready while the cursor stays at the current tail.
  • Replace the cursor atomically, keep it at or below 16 KiB, and mount it read-only for the Registry process. Keep the cursor path on local storage. Runtime reads use one 500 ms bounded worker and fail readiness when the file is missing, stale, malformed, unsafe, mismatched, or too slow.
  • Treat registry-relay doctor and registry-notary doctor as offline contract checks. They can validate the cursor timestamp and shape but cannot bind it to a live audit tail, so a fresh cursor remains unverified. An evidence_grade offline doctor run therefore reports the hard shipping gate until the live service performs tail binding.
  • For the 1.0 single-node VM topology, follow Run single-node Compose behind your reverse proxy before exposing Relay or Notary beyond the host.
  • Terminate TLS and manage certificates at your reverse proxy or load balancer; RS-SEC-G Section 9 leaves transport termination and certificate management to the operator.
  • Keep the admin listener private. A publicly exposed admin surface trips relay.admin.public_exposure or notary.admin.shared_exposure, both startup_fail at evidence_grade.
  • Set explicit cors.allowed_origins. The default CORS policy is deny by omission; add only the origins your integration actually needs.
  • Enable server.trust_proxy with an explicit trusted_proxies list only when a reverse proxy sits in front of Relay. Both enabled and trusted_proxies default to off/empty.
  • Enforce ingress rate limiting at your gateway or edge, then either declare deployment.evidence.ingress_rate_limit: true or accept the relay.ingress.rate_limit_missing finding it otherwise raises. The flag defaults to false.
  • Leave allow_insecure_localhost and allow_insecure_private_network false on Notary source connections in production unless a deployment review explicitly accepts the private-network source; a plain http:// source with neither allowance trips notary.source.insecure_url, and enabling the escape hatch trips notary.source.private_network_escape.
  • For Postgres sources, require sslmode=require on the connection string and use read-only database credentials; for live materialization, scope that role to SELECT only the configured table or view.
  • Apply the Registry Platform HTTP-security response headers and outbound-HTTP-policy primitive on source-connector egress; both are RS-SEC-G recommendations (REQ-SEC-G-012), not an enforced default, so confirm your build turns them on.
  • Declare deployment.profile explicitly. An omitted profile binds no gates and only raises a deployment.profile_undeclared warning, so a silent gap stays silent until you declare a profile.
  • Treat evidence_grade as requiring a signed local config bundle: running it from a plain local YAML file trips relay.config.unsigned / notary.config.unsigned and the process refuses to start.
  • Review GET /admin/v1/posture regularly. It reports the declared profile, active findings, and active waivers, so the deployment’s actual state is inspectable rather than asserted.
  • Give every waiver a non-empty, non-secret reason and a mandatory expiry date. startup_fail gates are never waivable; a waiver naming one is rejected. Waiver reasons appear only in the restricted posture tier.
  • Use deployment.evidence.* flags (for example ingress_rate_limit, api_key_rotation) only to assert controls that live outside the process and cannot be observed by it. Each flag defaults to false.
  • If a Notary instance runs active-active with peers, declare multi_instance and use Redis-backed replay storage. In-memory replay under a multi-instance or federated declaration trips notary.replay.in_memory_high_risk.
  • Know your private disclosure channel before you need it: see Report a vulnerability.
  • If credential-status is enabled, use the admin status endpoint (requires the registry_notary:admin scope) to mark a compromised credential revoked. Registry Notary defines no other revocation flow; a credential issued with credential-status disabled cannot be revoked through the API. See Known limitations and non-guarantees.
  • Rotate a compromised signing key using the documented rotation procedure (new kid, old key to publish_only) rather than deleting it outright, so previously issued, still-valid credentials remain verifiable through the rotation.
  • Preserve the audit trail for post-incident review: hash-chained, fail-closed audit records are the record a deployment reconstructs a request timeline from (REQ-SEC-G-008, REQ-SEC-G-009).
  • Start the service against your production config and confirm it starts (or fails closed as expected for a deliberately unmet startup_fail gate).
  • Query GET /admin/v1/posture and read the deployment object: confirm the profile matches what you declared, and that no unwaived finding at or above your profile’s threshold is outstanding.
  • Send a request that should be audited and confirm a corresponding record lands in your configured sink, not only stdout.
  • Attempt an admin-listener request from outside the private network it is meant to be bound to, and confirm it is refused.
SymptomCauseFix
Process refuses to start after declaring production or evidence_gradeA startup_fail gate tripped (for example missing audit sink or unsigned config)Check the posture or startup log for the finding id, then fix the underlying condition; startup_fail gates cannot be waived
Posture reports deployment.profile_undeclaredNo deployment.profile setDeclare a profile explicitly; an omitted profile is not the same as local
Posture reports deployment.waiver_expiredA waiver’s expires date passedRe-review the finding and either fix it or issue a new waiver with a new expiry