mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 13:51:36 +00:00
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:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -32,11 +32,16 @@ Issuer connectors:
|
|||||||
|
|
||||||
Target connectors:
|
Target connectors:
|
||||||
|
|
||||||
- [Apache](apache.md) — Apache httpd connector deep dive
|
- [Apache](apache.md) — Apache httpd, separate-file deploy + `apachectl configtest`
|
||||||
- [F5 BIG-IP](f5.md) — F5 connector deep dive (proxy agent + iControl REST)
|
- [Caddy](caddy.md) — admin-API hot reload or file-watcher fallback
|
||||||
- [IIS](iis.md) — Microsoft IIS connector deep dive (local PowerShell + WinRM modes)
|
- [Envoy](envoy.md) — file SDS hot reload, optional `sds.json`
|
||||||
- [Kubernetes Secrets](k8s.md) — k8s.io/tls Secrets connector deep dive
|
- [F5 BIG-IP](f5.md) — proxy-agent pattern + transactional iControl REST
|
||||||
- [NGINX](nginx.md) — NGINX connector deep dive (deploy contract + quirks)
|
- [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
|
## Contents
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user