PKI & Certificates

Cacophony uses a file-based PKI for mutual TLS authentication between cluster nodes. Certificate management is explicit and automatable.

PKI File Layout

$CACOPHONY_DIR/
  pki/
    ca.pem                    # Cluster certificate authority
    nodes/
      localhost.crt           # Node certificate
      localhost.key           # Node private key
      worker-1.crt
      worker-1.key

Certificate Commands

Initialize CA

# Create the cluster CA if it does not exist
caco cert init

Creates $CACOPHONY_DIR/pki/ca.pem and associated private key. Safe to run repeatedly — does not overwrite an existing CA.

Issue Node Certificate

# Issue or rotate a certificate for a specific node
caco cert issue --node localhost
caco cert issue --node worker-1

Creates <node>.crt and <node>.key under $CACOPHONY_DIR/pki/nodes/. The certificate subject/SAN matches the configured host for that node.

Both direct issuance and CSR bootstrap include the CA's authority key identifier for strict X.509 clients, including Python 3.13's default verifier. This does not retroactively modify existing certificates. Reissue and distribute an older leaf through the normal PKI workflow if a client rejects its missing authority key identifier; do not disable certificate verification.

Distribute Certificates

# Copy CA and node cert to a remote node via SSH
caco cert distribute --node worker-1

Uses the SSH configuration from the node's config entry to copy PKI material to the target machine. Requires SSH access to be configured.

Check Certificate Status

# Validate file presence and expiry
caco cert status

Reports on the presence and validity of the CA, node certificates, and their expiration dates.

Run Certificate Authority Service

caco cert serve

Run the certificate authority and bootstrap HTTPS service in the foreground. Useful for dedicated authority deploys where the full daemon is not needed. A CA-only role running caco cert serve will not answer ordinary daemon health probes; mark that as an expected health annotation when bootstrap HTTPS is the intended service surface.

Pull Certificates from Authority

caco cert pull

Pull certificates from the PKI authority node. Used on secondary nodes to obtain cert material when the authority has already issued it, without needing SSH-based distribution.

A joining node generates its own key pair and certificate signing request; the private key never leaves it. It posts its name, advertised host, ports and the CSR to the bootstrap authority's join endpoint with the bootstrap bearer token, before it has any cluster certificate. The authority checks the token in constant time, requires the name to match exactly one configured static node, client node or dynamic template, and refuses static nodes that are provisioned over SSH. It signs the CSR with a subject and SAN it chooses itself, and returns the certificate, the cluster CA and the identity kind. For a dynamic node it also records a lease in the replicated dynamic-node registry, adds the node to the replication peer set and emits a node_joined feed event. The node installs the material and from then on uses mutual TLS, renewing its lease periodically.
Joining with a CSRThe authority signs a node's own key with a subject it chooses; dynamic nodes are also leased into the replicated registry and peer set.

Multi-Node Setup

  1. Run caco cert init once on the bootstrap node to create the cluster CA
  2. Run NODE="worker-1"; caco cert issue --node "$NODE" for each node
  3. Run NODE="worker-1"; caco cert distribute --node "$NODE" to copy certs to remote nodes
  4. Verify with caco cert status on each node

Fresh secondary nodes must not auto-create a new cluster CA locally. They must join the existing cluster trust domain by receiving ca.pem plus their node-specific certificate and key from the bootstrap authority, or by equivalent pre-provisioned PKI files.

Automation All cert commands are file-based and explicit. Once SSH is configured in config.yaml, the entire PKI lifecycle can be automated in scripts or CI.

Bootstrap exposure is configurable: pki.bootstrap.exposure: tailnet_direct serves the HTTPS authority directly on its bind_port without Tailscale Funnel; use funnel only for intentional public exposure. Daemonless identities in client_nodes get cert material via caco cert issue --node <client-node> / cert pull and stay in the cluster CA trust domain with identity_kind: client_node diagnostics, without becoming daemon peers.

TLS Configuration

Node TLS paths are specified in the config under each node entry:

nodes:
  - name: localhost
    host: localhost
    tls:
      cert: $HOME/.cacophony/pki/nodes/localhost.crt
      key: $HOME/.cacophony/pki/nodes/localhost.key

caco config validate verifies that TLS file paths exist when cluster listeners are enabled.