Skip to main content
Certplane splits certificate management into two distinct phases. In the first phase a host proves its identity to an internal CA and receives a long-lived identity certificate. In the second phase that identity certificate acts as a credential when the host connects to the broker to request publicly trusted service certificates. Service private keys never leave the host.

The two binaries

BinaryRole
certplane-brokerCentral HTTPS API. Authenticates agents over mTLS, enforces policy, talks to the configured ACME issuer, caches issued certificates in a SQLite or file store, and records audit events.
certplane-agentOne-shot binary on each managed host. Enrolls with step-ca once, then on every invocation renews the identity certificate (if needed), generates or reuses service keys locally, submits CSRs to the broker, installs returned bundles, and runs reload hooks.
Both binaries are configured by a single YAML file passed with -c / --config.

Phase 1: Enrollment

Enrollment happens once per host. It binds the host to its machine identity.
1

Place a bootstrap token

An operator (or an Ansible play) writes a short-lived step-ca bootstrap token to the path set in identity.bootstrap_token.
2

Run `certplane-agent ... enroll`

The agent acquires a file lock at state_dir/agent.lock, then:
  1. Generates a local ECDSA private key at identity.key (if missing).
  2. Builds a CSR with CN = identity.name.
  3. Calls the step-ca provisioner endpoint at identity.step_ca.url, presenting the CSR and the bootstrap token. The server’s TLS certificate is verified using either identity.step_ca.fingerprint or identity.step_ca.root_ca_bundle.
  4. Writes the returned certificate to identity.cert and deletes the bootstrap token file so it cannot be reused.
3

Identity is established

The agent now holds a (identity.key, identity.cert) pair signed by step-ca. The certificate’s CN is the agent’s stable identity used in all subsequent broker calls.

Phase 2: Renewal loop

Every certplane-agent ... run invocation does the following:
1

Identity renewal

If the identity certificate expires within identity.renew_before (default 8h), the agent rekeys (or reuses, depending on configuration) with step-ca and writes a fresh identity.crt. If less than identity.warn_before remains but no renewal is yet due, the agent logs a warning.
2

For each service certificate

For every entry under certificates[]:
  1. Ensure a local service key at key. The agent generates an ECDSA P-256 key if the file is missing and reuses it otherwise. Keys never leave the host.
  2. Check the existing certificate at cert. If it parses, matches the key, and is not yet within renew_before of expiry (default 720h), the agent logs certificate skipped, not in renewal window and moves on.
  3. Build a CSR containing exactly the configured dns_names.
  4. Call the broker. The agent opens an mTLS connection to broker.url, presenting identity.cert as the client certificate and trusting broker.server_ca_bundle for the broker’s TLS. It POSTs {profile, csr_pem} to /v1/certificates.
  5. Validate the bundle. When the broker returns cert_pem, chain_pem, and fullchain_pem, the agent verifies the leaf matches the local key and contains exactly the requested DNS names — never trusting the broker blindly.
  6. Install atomically. The agent writes cert, chain, and fullchain to disk.
  7. Reload. If reload_command is set, the agent runs it with timeout reload_timeout (default 30s) and logs the output.

What the broker does on /v1/certificates

When the broker receives an issuance request:
  1. Authenticate. TLS terminates with tls.RequireAndVerifyClientCert against server.mtls.agent_ca_bundle. The identity is the client certificate’s CN.
  2. Authorize. The broker looks up the identity in the current policy (policy.path), confirms the requested profile is listed under hosts.<key>.profiles, and validates the CSR’s DNS names against the profile’s dns_names.
  3. Rate-limit. Per-identity and per-(identity, profile) hourly limits from rate_limits are enforced.
  4. Serve from cache or issue. If a cached certificate for (profile, CSR fingerprint) exists and is not within the profile’s renew_before window, it is returned. Otherwise the broker calls the configured ACME directory (Let’s Encrypt by default) with the profile’s challenge type, fetches the certificate, stores the bundle, and returns it.
  5. Audit. Every decision — allow or deny, plus broker lifecycle events — is recorded via the audit recorder. Inspect with certplane-broker ... audit tail.

What never crosses the wire

  • Service private keys. Always generated and rotated on the host using them.
  • DNS provider credentials. Stay at the broker. Agents have no knowledge of how the broker satisfies an ACME challenge.
  • ACME account keys. Live on the broker only.

Where to go next