Skip to content

Wildcard distribution

A fleet-wide *.zone certificate the broker holds and serves to granted agents. It keeps internal hostnames out of Certificate Transparency logs and cuts rate-limit pressure — at the cost of a shared key.

Why wildcards for internal fleets

Every publicly-issued certificate is published to Certificate Transparency logs, forever. A per-host certificate for scada-hmi.plant.example.com broadcasts that hostname to the world — reconnaissance gold. A single *.plant.example.com wildcard leaks only the zone. For internal infrastructure that's usually the right trade.

The cost: one private key shared across many hosts means a bigger blast radius if a host is compromised. gapcert bounds this by renewing the wildcard at 1/3 of its lifetime (frequent key rotation) and serving the key only to agents an explicit policy grants.

Broker policy

Mark a policy strategy: wildcard. It must name an explicit agents list — an empty or match-all list is refused, because it would hand the shared private key to the whole fleet:

policies:
  - match: "*.plant.example.com"
    strategy: wildcard
    agents: ["plant-*"]          # required — who may receive the shared key
    solver: challenge-zone

The broker generates the wildcard key, obtains the certificate, and stores the key encrypted at rest (AES-256-GCM). The encryption key comes from GAPCERT_MASTER_KEY (base64 of 32 bytes) or a persisted per-install random key — never a hardcoded default; if neither is available the broker fails closed.

Agent side

The agent marks the certificate shared and names the wildcard:

# ~/.config/gapcert/agent.yaml
data_dir: /var/lib/gapcert
certificates:
  - name: plant-wildcard
    domains: ["*.plant.example.com"]
    shared: true
    hook: "systemctl reload nginx"
$ gapcert agent obtain
plant-wildcard       fetching shared *.plant.example.com from broker ...
plant-wildcard       stored /var/lib/gapcert/certs/plant-wildcard (expires 2026-10-24)

POST /v1/shared is the only endpoint that returns a private key — mTLS-gated, policy-gated, and audited on every fetch. Before installing, the agent verifies the returned certificate and key form a matching pair and cover exactly the requested wildcard.

Per-host or wildcard, per policy

The strategy is per policy, so one broker can serve both: wildcards for the zones where CT exposure matters and per-host certificates elsewhere. A per-host CSR request can never mint a wildcard, and a wildcard fetch can never yield a per-host name — the two paths are strictly separated and default-deny.

This path serves private keys, so it went through its own security cadence; see the security model.