fix(tunnel): recover an expired leaf over plain HTTPS, not mTLS
Correction to the previous commit. Its recovery vhost could not work: nginx
will not forward an expired client certificate in ANY `ssl_verify_client` mode.
`optional` answers a bare `400 The SSL certificate error` before any location
runs, and `optional_no_ca` only tolerates CHAIN failures — see nginx's
`ngx_ssl_verify_error_optional()`, which covers self-signed / unknown-issuer /
unverifiable-leaf and NOT `X509_V_ERR_CERT_HAS_EXPIRED`. Verified live against
the deployed :8472 server, which rejected the real expired leaf.
So recovery drops mTLS instead of trying to bend it:
- agent: `buildTlsOptions` and the mTLS renew transport go back to being
strictly fail-closed on expiry — the relaxation is gone from the TLS layer
entirely. The rotator now decides per attempt: valid → mTLS `/renew`,
expired-inside-grace → plain `/recover`, expired-beyond-grace → terminal
`onExhausted` with no request issued at all.
- new `recoverCert` POSTs `{cert, csr}` with no client certificate. Possession
of the private key is still proven: the CSR is self-signed by it and the host
signer already enforces CSR PoP plus `CSR key == registered key`, so a
replayed (public) cert without the key yields at most a certificate the
attacker cannot authenticate with.
- control-plane: `/renew` is strict again (grace 0, matching the terminator).
The grace lives on the new `POST /recover`, which reads the cert from the BODY
and ignores the `x-client-cert` header, then runs the identical trust
pipeline: X.509 path validation to the frp-client-CA anchors, SPIFFE parse,
`notBefore` (never graced), registry `active` + account match. Revocation
still bites.
- deploy: no new vhost, no new SNI, no DNS. One `location = /recover` merged
into the existing enroll vhost, documented in
`deploy/nginx/enroll-recover-location.md` with the nginx source citation.
The load-bearing new test is "a self-signed cert with a FORGED SPIFFE SAN is
refused → 401": nginx no longer validates the chain on this path, so that
assertion is what keeps `/recover` from being a cert vending machine.
Verified: agent 289/289, control-plane 290/290, tsc clean on both.
This commit is contained in:
@@ -17,18 +17,6 @@ frp.terminal.yaojia.wang 127.0.0.1:7000; # frps control channel (TLS passthro
|
||||
*.terminal.yaojia.wang 127.0.0.1:8470; # nginx :8470 TLS-term + device-CA mTLS (frp-mtls.conf)
|
||||
```
|
||||
|
||||
### Later addition — expired-leaf recovery (recover-mtls.conf)
|
||||
|
||||
```nginx
|
||||
recover.terminal.yaojia.wang 127.0.0.1:8472; # expired-leaf re-issue (recover-mtls.conf)
|
||||
```
|
||||
|
||||
**Order IS load-bearing for this one**: it must sit ABOVE the `*.terminal.yaojia.wang` wildcard, the
|
||||
same way `enroll.terminal.yaojia.wang` does. `hostnames` gives exact matches priority over wildcards
|
||||
in nginx's `map`, so a correctly-placed line wins regardless — but keep the exact-match lines grouped
|
||||
above the wildcard so a future editor cannot mis-read the intent. No DNS work is needed: the zone is
|
||||
already served by the wildcard record `*.terminal.yaojia.wang → <vps>`.
|
||||
|
||||
## Coexistence warning (do NOT retype the existing lines)
|
||||
|
||||
The existing `map` body already contains — and MUST be preserved **verbatim**:
|
||||
|
||||
Reference in New Issue
Block a user