docs: Phase 4 follow-on batch 3 — 5 file-based target per-pages

Extracts the file-based deploy target connectors:

- haproxy.md (107 lines) — combined-PEM (cert+chain+key) deploy with
  haproxy -c validate; multi-frontend + crt-list directory guidance
- traefik.md (105 lines) — file-provider zero-reload deploy; file
  watcher latency notes; mixing with built-in ACME guidance
- caddy.md (100 lines) — admin API mode (recommended) vs file mode;
  admin-API exposure threat model
- envoy.md (112 lines) — file SDS mode (recommended) vs static
  bootstrap; service-mesh interactions
- postfix.md (175 lines) — dual-mode (Postfix MTA / Dovecot IMAPS)
  connector with daemon-specific quirks (STARTTLS chain expectations,
  no shared session cache); Bundle 11 test pins

Index forward-list expanded to enumerate all 10 target connectors
(5 from Phase 4 structural + 5 from this batch) in alphabetical
order.

This is part 3 of 4 for the Phase 4 follow-on (per-connector page
extraction) tracked in cowork/docs-overhaul-phase-2-restructure-2026-05-04/log.md.

Net add: 5 files, 599 lines. No content removed from index.md.
This commit is contained in:
shankar0123
2026-05-05 04:02:25 +00:00
parent a310aab7c7
commit d327ace885
6 changed files with 609 additions and 5 deletions
+100
View File
@@ -0,0 +1,100 @@
# Caddy Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Caddy target connector. For
> the connector-development context (interface contract, registry,
> atomic deploy primitive shared across all targets), see the
> [connector index](index.md).
## Overview
The Caddy connector supports two deployment modes:
- **API mode (recommended).** Posts the certificate directly to
Caddy's admin API for zero-downtime hot reload.
- **File mode (fallback).** Writes cert and key files to disk,
relying on Caddy's built-in file watcher or a manual reload.
Implementation lives at `internal/connector/target/caddy/`.
## When to use this connector
Use the Caddy connector when:
- Caddy fronts your services and you want certctl-managed certs
rather than letting Caddy run its own ACME client.
- You want zero-downtime hot reload via Caddy's admin API.
Look elsewhere when:
- You'd rather Caddy keep running its own ACME client — point it
at certctl's ACME server (see
[migration/acme-from-caddy.md](../../migration/acme-from-caddy.md))
for the cleanest pattern.
## Configuration
```json
{
"mode": "api",
"admin_api": "http://localhost:2019",
"cert_dir": "/etc/caddy/certs",
"cert_file": "site.crt",
"key_file": "site.key"
}
```
When `mode` is `"api"`, the connector posts the certificate to
the admin API endpoint. When `mode` is `"file"`, it writes files
to `cert_dir` (same pattern as Traefik). The `admin_api` field is
ignored in file mode.
## Mode trade-offs
### API mode
- Zero-downtime hot reload via `POST /load` or
certificate-specific endpoints.
- Requires Caddy's admin API to be enabled and reachable from the
deployment agent.
- Best fit for production deployments where Caddy is configured
with an admin endpoint.
### File mode
- Writes cert and key files to `cert_dir`; Caddy picks them up
via its file watcher or on next config reload.
- Use when the admin API isn't available or when Caddy is
configured to read certificates from disk.
- Behaviorally equivalent to the [Traefik](traefik.md) connector.
## Deploy contract
API mode bypasses the Bundle I file-write deploy primitive and
talks directly to the Caddy admin API. File mode follows the
standard atomic-write + verify path (idempotency check → backup
→ atomic write → optional reload → post-deploy TLS verify).
## Operator playbook
### Admin API exposure
Caddy's admin API is an unauthenticated control surface by
default. In API mode, ensure the admin API is bound to a
loopback or trusted network — exposing it to the public would
let anyone reload Caddy's config. Run the agent on the same host
as Caddy and use `http://localhost:2019` for the safest posture.
### Falling back to file mode
If the admin API is intermittently unreachable, switch the
target's `mode` to `file` via `PUT /api/v1/targets/{id}`. The
deploy still lands; reload behaviour is whatever the operator's
Caddy config does with file changes.
## Related docs
- [Connector index](index.md) — interface contract, registry, deploy primitive
- [Traefik](traefik.md) — comparable file-provider target
- [Migration: point Caddy at certctl's ACME](../../migration/acme-from-caddy.md) — alternative pattern when Caddy should keep its ACME client
+112
View File
@@ -0,0 +1,112 @@
# Envoy Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Envoy target connector. For
> the connector-development context (interface contract, registry,
> atomic deploy primitive shared across all targets), see the
> [connector index](index.md).
## Overview
The Envoy connector uses **file-based certificate delivery** — it
writes certificate and key files to a directory that Envoy watches
via its SDS (Secret Discovery Service) file-based configuration or
static `filename` references in the bootstrap config. When files
change, Envoy automatically picks up the new certificates without
requiring a reload command.
Implementation lives at `internal/connector/target/envoy/`.
## When to use this connector
Use the Envoy connector when:
- Envoy fronts your services (standalone, as part of a service
mesh, or as an API gateway like Emissary or Gloo).
- You want certctl to drive cert rotation and let Envoy's file
SDS handle the rolling reload across worker threads.
Look elsewhere when:
- You're running an Envoy-based service mesh (Istio, Consul
Connect) — those meshes have their own cert distribution
pipelines, and integrating certctl at the mesh layer is a
different design than this connector covers.
- You're using Envoy's xDS/gRPC SDS path (not file-based SDS) —
the gRPC SDS-server connector is on the V3-Pro roadmap.
## Configuration
```json
{
"cert_dir": "/etc/envoy/certs",
"cert_filename": "cert.pem",
"key_filename": "key.pem",
"chain_filename": "chain.pem",
"sds_config": true
}
```
| Field | Type | Default | Description |
|---|---|---|---|
| `cert_dir` | string | (required) | Directory where Envoy watches for certificate files |
| `cert_filename` | string | `cert.pem` | Filename for the certificate (leaf + chain unless `chain_filename` is set) |
| `key_filename` | string | `key.pem` | Filename for the private key |
| `chain_filename` | string | (empty) | If set, chain is written to a separate file instead of appended to the cert |
| `sds_config` | bool | `false` | If true, writes an `sds.json` file for Envoy's file-based SDS provider |
## SDS mode (recommended for production)
When `sds_config` is `true`, the connector writes an SDS JSON
file (`{cert_dir}/sds.json`) containing a `tls_certificate`
resource that points to the cert and key file paths. Envoy's
file-based SDS (`path_config_source`) watches this file for
changes, providing automatic hot-reload of certificates without
restarting worker threads.
This is the recommended approach for production Envoy deployments
using dynamic TLS configuration.
## Static-bootstrap mode
When `sds_config` is `false` (the default), the connector simply
writes cert and key files. Use this mode when Envoy's bootstrap
config references the cert / key files directly via static
`filename` fields in the TLS context.
In this mode Envoy still picks up file changes via its filesystem
watcher, but the operator should verify the bootstrap config sets
`watched_directory` (or equivalent) on each `tls_certificate`
entry — without it, the cert is loaded once at startup and
subsequent file changes are ignored.
## Deploy contract
Standard atomic-write + post-deploy verify (file-based deploy
primitive shared across all file-deploy connectors). When SDS
mode is on, the SDS JSON file is updated last so Envoy sees the
cert / key on disk before the SDS resource pointer changes.
## Operator playbook
### Hot-reload across worker threads
Envoy's file SDS path triggers a per-worker-thread reload as each
worker re-reads the SDS file. In-flight TLS connections on each
worker continue with the OLD cert until they close; new
connections after the reload pick up the NEW cert.
### Service mesh interactions
If you're running Istio or Consul Connect, the mesh's own cert
distribution pipeline (citadel / SDS server) is the system of
record for sidecar certs. Don't point this connector at sidecar
cert paths — point it at standalone Envoy gateways or API edges
that aren't sidecar-managed.
## Related docs
- [Connector index](index.md) — interface contract, registry, deploy primitive
- [NGINX](nginx.md) — explicit-reload-command counterpart
- [Traefik](traefik.md) — file-watcher counterpart with simpler semantics
+107
View File
@@ -0,0 +1,107 @@
# HAProxy Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the HAProxy target connector.
> For the connector-development context (interface contract,
> registry, atomic deploy primitive shared across all targets), see
> the [connector index](index.md).
## Overview
HAProxy differs from NGINX and Apache in one important way: it
expects all TLS material in a **single combined PEM file**
certificate, intermediate chain, and private key concatenated. The
connector builds this combined file, writes it with 0600
permissions (since it contains the private key), optionally
validates the HAProxy configuration, and reloads.
Implementation lives at `internal/connector/target/haproxy/`.
## When to use this connector
Use the HAProxy connector when:
- HAProxy fronts your applications and you want certctl to
rotate the cert + chain + key in place atomically without
hand-rolling the combined-PEM build.
- You want validate-before-reload behaviour to keep a bad config
from taking down the load balancer mid-rotation.
Look elsewhere when:
- You're running HAProxy Enterprise's hot-cert-update API path —
the connector currently uses the file-write-and-reload model;
the API path is on the V3-Pro roadmap.
- You're not running HAProxy directly but a managed load balancer
(AWS ALB, Azure Application Gateway). Use the cloud-native
target connector for that platform instead.
## Configuration
```json
{
"pem_path": "/etc/haproxy/certs/site.pem",
"reload_command": "systemctl reload haproxy",
"validate_command": "haproxy -c -f /etc/haproxy/haproxy.cfg"
}
```
The combined PEM is built in this order: server certificate,
intermediate / chain certificates, private key.
The `validate_command` is optional — if omitted, the connector
skips config validation and goes straight to reload. Keeping it
on is the production-recommended posture.
## Deploy contract
Every cert deploy follows the Bundle I `deploy.Apply(ctx, plan)`
flow:
1. **Idempotency check** — SHA-256 over the combined PEM bytes;
skip if the destination already matches.
2. **Pre-deploy backup** — copy existing PEM to
`<pem_path>.certctl-bak.<unix-nanos>`.
3. **Atomic write** — temp-file + chown + atomic rename.
4. **PreCommit (validate)** — runs `haproxy -c -f
/etc/haproxy/haproxy.cfg`. Failure aborts; no live cert
touched.
5. **Atomic rename** — temp → final.
6. **PostCommit (reload)** — runs `systemctl reload haproxy` (or
the operator's override).
7. **Post-deploy TLS verify** — dials the configured endpoint
when configured; pulls leaf cert SHA-256; compares against
deployed bytes. Mismatch triggers automatic rollback.
## Operator playbook
### Old cert served via session resumption
HAProxy keeps TLS sessions alive for the configured
`tune.ssl.lifetime` (default 1h). Resumed clients see the OLD
cert until their session expires. Post-deploy verify in certctl
returns the NEW cert from a fresh handshake; warm clients see the
OLD cert until session expiration.
### Multi-frontend deployments
When HAProxy serves multiple frontends with different certs,
configure **one target per frontend's cert** in the certctl
control plane. Each gets its own `pem_path`. The reload command
is shared (HAProxy reloads all frontends together), so the
deploys can land in any order; the final reload picks them all up.
### `crt-list` directories
If your HAProxy config uses a `crt-list` directory rather than a
single PEM, set `pem_path` to a file inside the directory and let
HAProxy enumerate it on reload. The connector treats `pem_path`
as a single file regardless of HAProxy's directory semantics.
## Related docs
- [Connector index](index.md) — interface contract, registry, deploy primitive
- [NGINX](nginx.md) — separate-file deploy contract counterpart
- [Apache](apache.md) — separate-file deploy contract with `apachectl configtest`
- [Migration: ACME from HAProxy](../../migration/acme-from-caddy.md) — pattern for pointing edge proxies at certctl's ACME server (Caddy walkthrough; HAProxy ACME plumbing is similar)
+10 -5
View File
@@ -32,11 +32,16 @@ Issuer connectors:
Target connectors:
- [Apache](apache.md) — Apache httpd connector deep dive
- [F5 BIG-IP](f5.md) — F5 connector deep dive (proxy agent + iControl REST)
- [IIS](iis.md) — Microsoft IIS connector deep dive (local PowerShell + WinRM modes)
- [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets connector deep dive
- [NGINX](nginx.md) — NGINX connector deep dive (deploy contract + quirks)
- [Apache](apache.md) — Apache httpd, separate-file deploy + `apachectl configtest`
- [Caddy](caddy.md) — admin-API hot reload or file-watcher fallback
- [Envoy](envoy.md) — file SDS hot reload, optional `sds.json`
- [F5 BIG-IP](f5.md) — proxy-agent pattern + transactional iControl REST
- [HAProxy](haproxy.md) — combined-PEM deploy + `haproxy -c` validate
- [IIS](iis.md) — Microsoft IIS, local PowerShell + WinRM modes
- [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets atomic update
- [NGINX](nginx.md) — separate-file deploy + `nginx -t` validate
- [Postfix / Dovecot](postfix.md) — dual-mode mail-server TLS connector
- [Traefik](traefik.md) — file-provider zero-reload deploy
## Contents
+175
View File
@@ -0,0 +1,175 @@
# Postfix / Dovecot Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Postfix / Dovecot mail server
> TLS connector. For the connector-development context (interface
> contract, registry, atomic deploy primitive shared across all
> targets), see the [connector index](index.md).
## Overview
A dual-mode mail-server TLS connector. Writes certificate, key, and
chain files to configured paths and reloads the mail service. The
`mode` field selects between Postfix MTA and Dovecot IMAP/POP3,
which determines default file paths and reload commands.
This connector pairs with certctl's S/MIME certificate support
(email protection EKU, email SAN routing) for a complete email
infrastructure story — TLS for transport encryption, S/MIME for
end-to-end message signing and encryption.
Implementation lives at `internal/connector/target/postfix/`.
## When to use this connector
Use the Postfix / Dovecot connector when:
- You operate a self-hosted mail server (Postfix as MTA, Dovecot
as IMAPS/POP3S) and want certctl to rotate the TLS material in
place.
- You want validate-before-reload behaviour to keep a bad cert
config from taking down mail.
Look elsewhere when:
- You're running a mail provider (Google Workspace, Microsoft 365)
— the provider rotates certs internally.
- Your MTA is something else (Exim, Sendmail) — these don't have
built-in connectors yet; use a [generic file-based
target](index.md#target-connector) by hand or commission a
custom adapter.
## Configuration
### Postfix mode
```json
{
"mode": "postfix",
"cert_path": "/etc/postfix/certs/cert.pem",
"key_path": "/etc/postfix/certs/key.pem",
"chain_path": "/etc/postfix/certs/chain.pem",
"reload_command": "postfix reload",
"validate_command": "postfix check"
}
```
### Dovecot mode
```json
{
"mode": "dovecot",
"cert_path": "/etc/dovecot/certs/cert.pem",
"key_path": "/etc/dovecot/certs/key.pem",
"chain_path": "/etc/dovecot/certs/chain.pem",
"reload_command": "doveadm reload",
"validate_command": "doveconf -n"
}
```
### Field reference
| Field | Default (Postfix) | Default (Dovecot) | Description |
|---|---|---|---|
| `mode` | `postfix` | `dovecot` | Service mode — determines defaults |
| `cert_path` | `/etc/postfix/certs/cert.pem` | `/etc/dovecot/certs/cert.pem` | Path for certificate file |
| `key_path` | `/etc/postfix/certs/key.pem` | `/etc/dovecot/certs/key.pem` | Path for private key (0600 permissions) |
| `chain_path` | (empty) | (empty) | If set, chain written separately; otherwise appended to cert |
| `reload_command` | `postfix reload` | `doveadm reload` | Command to reload the mail service |
| `validate_command` | `postfix check` | `doveconf -n` | Optional config validation before reload |
All commands are validated against shell injection via
`validation.ValidateShellCommand()`. File permissions: cert /
chain 0644, key 0600.
## Choosing Mode=postfix vs Mode=dovecot
Both modes share the same Go connector code (atomic-write,
PreCommit/PostCommit hooks, post-deploy verify, rollback), so the
rollback contract is identical across modes. The mode flag just
swaps the daemon-specific defaults.
`mode: postfix` is also the **default when `mode` is unset**.
### Hosts running BOTH Postfix and Dovecot
The common mail-server pattern. Configure **two separate targets**
in the certctl control plane, one per daemon. Each gets its own
cert path, its own validate / reload command, and its own
optional verify endpoint. The cert + key bytes can be identical
across the two targets if your mail server uses the same TLS
material for both daemons (which many do); certctl does not
deduplicate the deploys, but the byte-equal cert hits the
SHA-256 idempotency short-circuit on subsequent renewals when
the target paths haven't changed.
### Sharing a single cert file across daemons via symlink
Works fine with the connector — the atomic-write path's
`os.Rename` follows symlinks. Configure both targets to point at
the same canonical path, or have one target's `cert_path`
symlink into the other's. Operators who want byte-deduplication
should rely on this approach rather than asking certctl to
coordinate it.
## Daemon-specific quirks
### Postfix STARTTLS (port 25)
Typically requires the cert to chain to a public root for
receiving mail from arbitrary external MTAs that validate
SMTP-side server certs. If you're deploying a self-signed cert
from `iss-local`, configure the receiving Postfix accordingly
(e.g. `smtpd_use_tls=yes` + `smtpd_tls_security_level=may` for
opportunistic TLS so external senders that don't validate
continue to deliver).
### Dovecot IMAPS (port 993)
Typically client-facing — the chain you ship matters more here
because IMAPS clients (Thunderbird, Outlook) actively validate.
Set `chain_path` if your certificate chain is supplied
separately; when `chain_path` is unset, the connector appends the
chain bytes to `cert_path`.
### No shared TLS session cache
Postfix and Dovecot do not share a TLS session cache by default.
Both reload independently, so a cert renewal that updates both
targets via certctl requires both reloads to succeed before
clients re-handshake. The two targets are fully independent in
the certctl scheduler — one reload failing rolls back that
target only.
## Post-deploy verify
Operator-supplied via `post_deploy_verify` (`enabled` +
`endpoint` + `timeout`) — the connector does NOT bake in a
per-mode default port. Operators that opt in should set
`endpoint` to their daemon's listener (e.g. `mail.example.com:25`
for Postfix STARTTLS, `mail.example.com:993` for Dovecot IMAPS).
## Test pins
Bundle 11 (commit `88e8881`) added end-to-end tests for
`Mode=dovecot`:
- `TestPostfix_Atomic_DovecotMode_HappyPath` — confirms
`applyDefaults` populates the dovecot validate + reload
commands AND the deploy threads them through to `runValidate`
+ `runReload`.
- `TestPostfix_Atomic_DovecotMode_VerifyFails_Rollback`
confirms the rollback path under `Mode=dovecot` restores
pre-deploy cert + key bytes byte-exact.
The `Mode=postfix` branch has equivalent test coverage in the
same file (see `TestPostfix_HappyPath`,
`TestPostfix_VerifyMismatch_Rollback`,
`TestPostfix_ReloadFails_Rollback`).
## Related docs
- [Connector index](index.md) — interface contract, registry, deploy primitive
- [NGINX](nginx.md) — comparable file-based deploy with explicit reload
- [Apache](apache.md) — comparable file-based deploy with `apachectl configtest`
+105
View File
@@ -0,0 +1,105 @@
# Traefik Connector — Operator Deep-Dive
> Last reviewed: 2026-05-05
>
> Operator-grade documentation for the Traefik target connector.
> For the connector-development context (interface contract,
> registry, atomic deploy primitive shared across all targets), see
> the [connector index](index.md).
## Overview
The Traefik connector uses Traefik's **file provider** — it writes
certificate and key files to a watched directory, and Traefik
automatically picks up the changes without any explicit reload
command. This is the simplest deployment model in the catalog:
write the files, Traefik does the rest.
Implementation lives at `internal/connector/target/traefik/`.
## When to use this connector
Use the Traefik connector when:
- Traefik fronts your services with the file provider configured
(`providers.file.directory` in Traefik's static config).
- You want a no-reload deployment path — Traefik picks up file
changes automatically.
Look elsewhere when:
- You're running Traefik with its built-in ACME client. Either
point Traefik at certctl's ACME server (see
[migration/acme-from-traefik.md](../../migration/acme-from-traefik.md))
or let certctl-issued certs flow through this file-provider
connector — but don't run both.
- Traefik is not exposed (e.g. behind another reverse proxy that
terminates TLS); the front-most TLS terminator is what wants
the cert.
## Configuration
```json
{
"cert_dir": "/etc/traefik/certs",
"cert_file": "site.crt",
"key_file": "site.key"
}
```
The `cert_dir` is the directory Traefik is configured to watch
via its file provider. The connector writes `cert_file` and
`key_file` into this directory with appropriate permissions
(0644 for the cert, 0600 for the key). Traefik's file watcher
detects the change and reloads the TLS configuration
automatically.
## Deploy contract
Every cert deploy follows the Bundle I `deploy.Apply(ctx, plan)`
flow:
1. Idempotency check on cert + key bytes.
2. Pre-deploy backup of existing files.
3. Atomic write of cert + key to temp paths.
4. Atomic rename of temp paths to final cert / key paths.
5. **No reload command** — Traefik's file watcher handles it.
6. Post-deploy TLS verify when configured (dials the endpoint;
pulls leaf cert SHA-256; compares).
The validate / reload / rollback semantics that NGINX and HAProxy
depend on don't apply here — Traefik's file watcher is the
"reload"; if Traefik fails to load the new file, that's a Traefik
problem visible in Traefik's logs, and the previous cert remains
served until Traefik retries.
## Operator playbook
### File watcher latency
Traefik's file watcher polls the directory; the cert may take a
few seconds to be picked up after the atomic rename. Post-deploy
verify with `PostDeployVerifyAttempts: 5` and a small backoff
covers this comfortably.
### Multi-router deployments
Traefik routes traffic by hostname, and the file provider can
expose multiple certs in the same directory. Configure one
certctl target per cert (one `cert_file` + `key_file` pair per
hostname); they all land in the same watched directory and
Traefik picks them up.
### Mixing file provider with ACME
If Traefik is also running its own ACME client, both can write to
the same `certificatesResolvers` config but with different
storage backends. Best practice: don't mix. Pick one source of
truth — either Traefik's ACME or certctl-supplied files — and
delete the other config block from `traefik.yml`.
## Related docs
- [Connector index](index.md) — interface contract, registry, deploy primitive
- [NGINX](nginx.md) — explicit-reload deploy contract counterpart
- [Migration: point Traefik at certctl's ACME](../../migration/acme-from-traefik.md) — alternative pattern when Traefik should pull rather than have certctl push