docs(generic): document TLS cert paths, rotation cadence, and reload pattern
Expand §11 TLS/PKI with the concrete host cert paths, file modes, and the ACL-for-service-accounts pattern. Document the 24h cert expiry and the continuous step.service renewal so implementations don't assume certs are stable. Add the standard systemd .path/.service reload pair for services that need to re-read certs without restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
generic.md
41
generic.md
@@ -462,11 +462,46 @@ This is the environment these apps deploy into. Claude Code should assume it.
|
||||
- Internal DNS split-horizon via `.internal` domains (`hanzalova.internal`, `kosherinata.internal`, etc.).
|
||||
|
||||
### TLS / PKI
|
||||
- Internal PKI via Smallstep `step-ca` at `ca.internal`.
|
||||
- Host certs renewed via systemd timers.
|
||||
- mTLS everywhere internal services talk to each other.
|
||||
- Internal PKI via Smallstep `step-ca` at `https://ca.internal`.
|
||||
- Every host runs `step.service` (the Smallstep renewer) which keeps the host's cert fresh. **Certs are issued with a 24-hour expiry** and renewed continuously — services must tolerate cert rotation, not assume certs are stable for the life of the process.
|
||||
- **mTLS everywhere** internal services talk to each other.
|
||||
- **Quantum-safe** SSH (sntrup761x25519 KEX) and TLS (X25519MLKEM768 where peers support it) are the default. External peers that don't support PQ fall back to classical curves — document the fallback explicitly in nginx config.
|
||||
|
||||
**Standard cert paths on every host:**
|
||||
|
||||
| Path | Contents | Mode |
|
||||
| --- | --- | --- |
|
||||
| `/etc/pki/ca-trust/source/anchors/root-internal.pem` | Internal root CA bundle | world-readable |
|
||||
| `/etc/pki/tls/misc/$(hostname -f).pem` | Host cert (public) | world-readable |
|
||||
| `/etc/pki/tls/private/$(hostname -f).pem` | Host private key | ACL grants read to service-account users |
|
||||
|
||||
Application code and systemd units should reference these paths directly — they're the same on every host, so config templates don't need to bake in a hostname. The key file is not world-readable; each app's service account is granted read access via `setfacl` (e.g., `setfacl -m u:<app>:r /etc/pki/tls/private/$(hostname -f).pem`) as part of deploy. This happens in `deploy.sh` alongside the `systemd-sysusers` step (§8).
|
||||
|
||||
**Reacting to cert rotation:**
|
||||
|
||||
Services that hold cert state in memory (most Rust daemons using `rustls` or `openssl`) must reload when the host cert changes. Ship a pair of systemd units alongside the service unit:
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/<app>-api-cert.path
|
||||
[Path]
|
||||
PathChanged=/etc/pki/tls/misc/<hostname>.pem
|
||||
Unit=<app>-api-cert-reload.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/<app>-api-cert-reload.service
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/systemctl reload <app>-api.service
|
||||
```
|
||||
|
||||
The service unit itself needs an `ExecReload=` that causes the daemon to re-read its certs without dropping in-flight requests (typically `SIGHUP` handling in the Rust binary). If the daemon can't reload gracefully, `ExecStart=/bin/systemctl restart <app>-api.service` is the fallback — but prefer graceful reload.
|
||||
|
||||
Ship these `.path` and cert-reload `.service` units from `asset/systemd/` the same way as the main unit.
|
||||
|
||||
### Ingress
|
||||
- Per-site nginx reverse proxy terminates all WAN inbound 443.
|
||||
- Public DNS via Cloudflare, **unproxied by default** (CF's mTLS origin-pull has been unreliable). Revisit if/when that changes.
|
||||
|
||||
Reference in New Issue
Block a user