> ## Documentation Index
> Fetch the complete documentation index at: https://certplane.kippel.org/llms.txt
> Use this file to discover all available pages before exploring further.

# How Certplane works

> A two-phase flow — enrollment then renewal — where each host proves identity to the broker, which enforces policy and calls your public CA via ACME.

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

| Binary             | Role                                                                                                                                                                                                                                                             |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `certplane-broker` | Central 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-agent`  | One-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.

<Steps>
  <Step title="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`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## Phase 2: Renewal loop

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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## 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

* [Broker setup](/setup/broker) and [Broker configuration](/configuration/broker)
* [Agent enrollment](/setup/agent-enroll) and [Running the agent](/setup/agent-run)
* [Policy overview](/policy/overview)
* [Ansible roles guide](/guides/ansible)
