docs: implementation plan for gateway TLS auto-cert and lenient client trust

This commit is contained in:
Joseph Doherty
2026-06-01 07:01:58 -04:00
parent 6bfa4fe884
commit c4e7ddea70
3 changed files with 1231 additions and 5 deletions
@@ -50,9 +50,12 @@ New type `SelfSignedCertificateProvider` in
day` (clock-skew slack), `notAfter = now + ValidityYears`. SANs: `DNS=localhost`,
`DNS=<MachineName>`, `DNS=<MachineName.FQDN>` when resolvable, plus
`IP=127.0.0.1` and `IP=::1`. Server-auth EKU.
4. **Persist securely.** Write the PFX with a random in-memory-only export password;
restrictive ACL (SYSTEM + Administrators + service account) on the `certs`
directory and file; atomic write (temp + rename).
4. **Persist securely.** Write the PFX with an **empty** export password (a random
in-memory password cannot be reused across restarts, which the persist-and-reuse
decision requires); protect the private key with a restrictive ACL (SYSTEM +
Administrators + service account) on the `certs` directory and file on Windows,
and `0600` on non-Windows; atomic write (temp + rename). After generating, the
cert is reloaded from the persisted PFX so Kestrel always serves the on-disk key.
5. **Wire into Kestrel.** In `GatewayApplication.CreateBuilder`, add
`builder.WebHost.ConfigureKestrel(o => o.ConfigureHttpsDefaults(h =>
h.ServerCertificate = cert))`. `ConfigureHttpsDefaults` supplies the cert only
@@ -71,8 +74,11 @@ All optional; the zero-config path needs none of them.
| `Tls:AdditionalDnsNames` | `[]` | Extra SANs (e.g. a load-balancer name) |
| `Tls:RegenerateIfExpired` | `true` | Auto-replace an expired persisted cert |
Validated by `GatewayOptionsValidator`: path non-empty when TLS is active,
`ValidityYears` in 1100.
Validated by `GatewayOptionsValidator`: `ValidityYears` in 1100,
`SelfSignedCertPath` is a valid path shape when non-blank, and
`AdditionalDnsNames` entries are non-blank. (The "https endpoint exists but cert
path is blank" fail-fast lives in the bootstrap/provider, not the validator,
because the validator only sees the `MxGateway` section, not `Kestrel:Endpoints`.)
**Logging:** on generate/load, log thumbprint + SAN list + `notAfter` at
Information. Never log the PFX password or private key.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
{
"planPath": "docs/plans/2026-06-01-gateway-cert-autogen-implementation.md",
"tasks": [
{"id": 1, "subject": "Task 1: Add TlsOptions config + bind into GatewayOptions", "status": "pending"},
{"id": 2, "subject": "Task 2: Validate MxGateway:Tls in GatewayOptionsValidator", "status": "pending", "blockedBy": [1]},
{"id": 3, "subject": "Task 3: SelfSignedCertificateProvider.GenerateCertificate", "status": "pending", "blockedBy": [1]},
{"id": 4, "subject": "Task 4: SelfSignedCertificateProvider.LoadOrCreate (persist/reuse/regenerate/ACL)", "status": "pending", "blockedBy": [3]},
{"id": 5, "subject": "Task 5: KestrelTlsInspector (detect HTTPS-without-cert)", "status": "pending"},
{"id": 6, "subject": "Task 6: Wire auto-cert into GatewayApplication.CreateBuilder", "status": "pending", "blockedBy": [1, 4, 5]},
{"id": 7, "subject": "Task 7: .NET client lenient TLS by default", "status": "pending"},
{"id": 8, "subject": "Task 8: Go client lenient TLS by default", "status": "pending"},
{"id": 9, "subject": "Task 9: Java client lenient TLS by default", "status": "pending"},
{"id": 10, "subject": "Task 10: Python client lenient TLS via TOFU pre-fetch", "status": "pending"},
{"id": 11, "subject": "Task 11: Rust client lenient TLS via rustls verifier (spike + fallback)", "status": "pending"},
{"id": 12, "subject": "Task 12: Documentation", "status": "pending", "blockedBy": [6, 7, 8, 9, 10, 11]}
],
"lastUpdated": "2026-06-01"
}