Architecture
CertAutoPilot is a single Go binary that runs in three modes — API, worker, scheduler — backed by MongoDB. This page describes process boundaries, the encryption envelope, and the audit chain.
The three process modes
The same binary runs different responsibilities depending on the --mode flag.
| Mode | What it does | State |
|---|---|---|
api | HTTP/HTTPS server, REST + GraphQL, session and JWT. | Stateless |
worker | Pulls jobs (issue / renew / distribute / scan) and runs them. | Stateless; many replicas OK. |
scheduler | Enqueues time-driven jobs: renewal windows, drift scans, KEK rotation steps. | Leader-elected. Exactly one active. |
all | All three in one process. Default for the standalone installer. | For small deployments. |
Data flow for issuance
- API receives
POST /api/v1/certificatesand writes a requested certificate document plus an issue job. - Scheduler is uninvolved here — only renewals and scans are scheduler-driven.
- Worker dequeues the job. It loads the ACME account, talks to the CA, publishes the DNS-01 challenge via the zone's credential, polls for validation, finalizes the order, and writes the chain back.
- Worker emits
cert.requested,cert.issued, andcert.distributedaudit events as it goes; each event is HMAC-chained to the previous one.
Encryption envelope
Every secret field is wrapped in a per-field DEK (data-encryption key). The DEK is itself wrapped by a versioned KEK (key-encryption key). The KEK lives outside MongoDB — locally, in a file with restricted permissions, or in an HSM (PKCS#11 — see supported HSMs for what has actually been validated).
field { // stored as one base64 string
kv: 7, // which KEK version sealed this
edek: AES-256-GCM(KEK_v7, plaintext_dek),
ct: AES-256-GCM(plaintext_dek, plaintext_field),
p: "env", // "env" or "pkcs11"
ab: true // field-bound — optional, off by default
}
Only the ab flag is stored. The binding context itself — the collection and
field the value belongs to — is never written down; it is reconstructed at read
time from where the value was found. Storing it beside the ciphertext would let
anyone moving the value carry the matching context with it, which is precisely
what binding exists to prevent.
This means a KEK rotation proceeds record by record and can be cancelled and resumed — it is never an all-or-nothing re-encryption of the whole database. Each record is decrypted and re-sealed under a fresh data key, so plan the window against how much secret material you hold, not just how many rows.
So that a stolen MongoDB dump alone is useless. Decrypting any one field requires (a) the KEK in memory and (b) the DEK from the same field. Optional field binding (off by default) additionally ties the credential and key material to the field it lives in, so once binding is enabled and your data has been rotated, such a value no longer decrypts if it is moved elsewhere.
Audit chain
Every audit entry carries prev_checksum — an anchor to the entry before it, alongside its own HMAC signature. A tampered or deleted entry breaks the chain; the verify endpoint (owner-only, rate-limited to one call a minute) reports the first broken link. There is no certautopilot audit CLI command.
The same events are forwarded to a SIEM via RFC 5424 syslog or CEF over TCP/UDP/TLS. The forwarder is durable: events are persisted to disk before TCP send, so a SIEM outage does not lose events.
Multi-tenant RBAC
RBAC is enforced at the API layer with policies declared per route handler. Project-scoped objects also enforce tenancy at the data-access layer — every read and write injects a project filter so an admin in project A literally cannot read project B objects, even with a crafted query.
Network ports
| Port | Role | Notes |
|---|---|---|
| 443 | HTTPS UI + API | nginx termination by default; pass-through TLS optional. |
| 8080 | Internal HTTP | Bound to 127.0.0.1; nginx proxies to it. |
| 9090 | Prometheus | Scrape-only; bind explicitly. |
| 27017 | MongoDB | Local-only by default. Replica set members talk on this port. |