feat: Sprint 1 — production hardening, TLS lifecycle, CI coverage, lint cleanup

- Fix 3 client panics: replace .unwrap()/.expect() with proper error
  handling in rpc.rs (AUTH_CONTEXT lock), repl.rs (pending_member),
  and retry.rs (last_err)
- Add --danger-accept-invalid-certs flag with InsecureServerCertVerifier
  for development TLS bypass, plus mdBook TLS documentation
- Add CI coverage job (cargo-tarpaulin) and Docker build validation
  to GitHub Actions workflow, plus README CI badge
- Add [workspace.lints] config, fix 46 clippy warnings across 8 crates,
  zero warnings on all buildable crates
- Update Dockerfile for all 11 workspace members
This commit is contained in:
2026-03-03 23:19:11 +01:00
parent dc4e4e49a0
commit 612b06aa8e
33 changed files with 388 additions and 67 deletions

View File

@@ -0,0 +1,100 @@
# TLS in quicproquo
quicproquo uses QUIC (RFC 9000) for all client-server communication. QUIC mandates TLS 1.3, so every connection is encrypted and authenticated at the transport layer — there is no plaintext mode.
## How it works
The server holds a TLS certificate and private key (DER format). On startup it either loads existing files or, in development mode, generates a self-signed certificate automatically. The client authenticates the server by verifying its certificate against a trusted root provided via `--ca-cert` (or `QPQ_CA_CERT`).
The TLS handshake negotiates the ALPN protocol `capnp`, after which the QUIC bi-directional stream carries Cap'n Proto RPC traffic.
## Certificate pinning with `--ca-cert`
By default the client trusts exactly the certificate (or CA) in the file given by `--ca-cert`:
```bash
qpq --ca-cert data/server-cert.der --server-name localhost health --server 127.0.0.1:7000
```
This is a form of **certificate pinning**: the client will only connect to a server whose certificate chains to the provided root. For single-server deployments, pass the server's own self-signed certificate. For CA-issued certificates, pass the CA's root certificate instead.
| Flag / Env var | Purpose |
|---|---|
| `--ca-cert` / `QPQ_CA_CERT` | Path to trusted root certificate (DER) |
| `--server-name` / `QPQ_SERVER_NAME` | Expected TLS server name (must match certificate SAN) |
## The `--danger-accept-invalid-certs` flag
For local development and testing you can skip certificate verification entirely:
```bash
qpq --danger-accept-invalid-certs health --server 127.0.0.1:7000
```
Or via the environment:
```bash
export QPQ_DANGER_ACCEPT_INVALID_CERTS=true
qpq health --server 127.0.0.1:7000
```
When active, the client prints a warning to stderr:
```
WARNING: TLS verification disabled — insecure mode
```
**Never use this flag in production.** It disables all certificate checks, making the connection vulnerable to man-in-the-middle attacks. It exists solely so that developers can iterate without managing certificates during local testing.
## Generating self-signed certificates for development
### Using rcgen (Rust)
The server generates a self-signed certificate automatically when the cert/key files are missing (unless `QPQ_PRODUCTION=1` is set). The generated files are written to:
- `data/server-cert.der` — DER-encoded certificate
- `data/server-key.der` — DER-encoded PKCS#8 private key
### Using openssl
To generate a self-signed certificate manually:
```bash
# Generate a private key and self-signed certificate (PEM)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
# Convert to DER format (required by quicproquo)
openssl x509 -in cert.pem -outform DER -out data/server-cert.der
openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out data/server-key.der -nocrypt
```
Point the server at the DER files:
```bash
export QPQ_TLS_CERT=data/server-cert.der
export QPQ_TLS_KEY=data/server-key.der
cargo run -p quicproquo-server
```
And the client at the certificate:
```bash
qpq --ca-cert data/server-cert.der --server-name localhost repl
```
## CA-issued certificates
For production deployments with a public CA (e.g. Let's Encrypt):
1. Obtain the certificate and key (e.g. via certbot).
2. Convert to DER format as shown above.
3. Configure the client to trust the CA root rather than the server certificate directly:
```bash
qpq --ca-cert /etc/ssl/certs/isrg-root-x1.der --server-name chat.example.com repl
```
See [Certificate Lifecycle and CA-Signed TLS](certificate-lifecycle.md) for rotation, OCSP, and operational details.