mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:21:30 +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:
|
||||
|
||||
- [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
|
||||
|
||||
|
||||
@@ -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