feat(CertificateManager): Implement on-demand certificate retrieval for missing SNI certificates. When no certificate is found for a TLS ClientHello, the system now automatically registers the domain with the Port80Handler to trigger ACME issuance and immediately falls back to using the default certificate to complete the handshake. Additionally, HTTP requests on port 80 for unrecognized domains now return a 503 indicating that certificate issuance is in progress.
This commit is contained in:
@ -1,18 +1,42 @@
|
||||
# Plan: Fallback to NetworkProxy on Missing SNI
|
||||
# Plan: On-Demand Certificate Retrieval in NetworkProxy
|
||||
|
||||
When a TLS ClientHello arrives without an SNI extension, we currently send a TLS alert and close the connection. Instead, if NetworkProxy is in use, we want to forward the connection to the network proxy first, and only issue the TLS error if proxying is not possible.
|
||||
When a TLS connection arrives with an SNI for a domain that has no certificate yet, we want to automatically kick off certificate issuance (ACME HTTP-01 or DNS-01) so the domain is provisioned on the fly without prior manual configuration.
|
||||
|
||||
## Goals
|
||||
- Allow TLS connections with no SNI to be forwarded to NetworkProxy when configured for any domain.
|
||||
- Only send a TLS unrecognized_name alert if proxy forwarding is unavailable or fails.
|
||||
- Automatically initiate certificate issuance upon first TLS handshake for an unprovisioned domain.
|
||||
- Use Port80Handler (HTTP-01) or custom `certProvisionFunction` (e.g., DNS-01) to retrieve the certificate.
|
||||
- Continue the TLS handshake immediately using the default certificate, then swap to the new certificate on subsequent connections.
|
||||
- For HTTP traffic on port 80, register the domain for ACME and return a 503 until the challenge is complete.
|
||||
|
||||
## Plan
|
||||
- [ ] In `ts/smartproxy/classes.pp.connectionhandler.ts`, locate the SNI-block branch in `handleStandardConnection` that checks `allowSessionTicket === false && isClientHello && !serverName`.
|
||||
- [ ] Replace the direct TLS alert/error logic with:
|
||||
- If NetworkProxy is enabled (global `useNetworkProxy` setting or an active NetworkProxy instance), call `this.handleNetworkProxyConnection(socket, record)` (passing the buffered ClientHello) before issuing a TLS alert.
|
||||
- Supply an error callback to `forwardToNetworkProxy`; if proxying fails or no NetworkProxy is available, fall back to the original TLS alert sequence.
|
||||
- [ ] Ensure existing metrics and cleanup (`record.incomingTerminationReason`, termination stats) are correctly tracked in the forwarding error path.
|
||||
- [ ] Add or update unit tests to simulate a TLS ClientHello without SNI on port 443 and verify:
|
||||
- When NetworkProxy is enabled, the connection is forwarded to the proxy.
|
||||
- If proxy forwarding fails or the domain is not configured, a TLS alert is sent and the socket is closed.
|
||||
- [ ] Run `pnpm test` to confirm no regressions and that the new behavior is correctly covered.
|
||||
1. Detect missing certificate in SNI callback:
|
||||
- In `ts/networkproxy/classes.np.networkproxy.ts` (or within `CertificateManager.handleSNI`), after looking up `certificateCache`, if no cert is found:
|
||||
- Call `port80Handler.addDomain({ domainName, sslRedirect: false, acmeMaintenance: true })` to trigger dynamic provisioning.
|
||||
- Emit a `certificateRequested` event for observability.
|
||||
- Immediately call `cb(null, defaultSecureContext)` so the handshake uses the default cert.
|
||||
|
||||
2. HTTP-01 fallback on port 80:
|
||||
- In `ts/port80handler/classes.port80handler.ts``, in `handleRequest()`, when a request arrives for a new domain not in `domainCertificates`:
|
||||
- Call `addDomain({ domainName, sslRedirect: false, acmeMaintenance: true })`.
|
||||
- Return HTTP 503 with a message like “Certificate issuance in progress.”
|
||||
|
||||
3. CertProvisioner & events:
|
||||
- Ensure `CertProvisioner` is subscribed to `Port80Handler` for newly added domains.
|
||||
- After certificate issuance completes, `Port80Handler` emits `CERTIFICATE_ISSUED`, `CertificateManager` caches and writes disk, and future SNI callbacks will serve the new cert.
|
||||
|
||||
4. Metrics and cleanup:
|
||||
- Track dynamic requests count via a `certificateRequested` event or metric.
|
||||
- Handle error paths: if ACME/DNS fails, emit `CERTIFICATE_FAILED` and continue serving default cert.
|
||||
|
||||
5. Tests:
|
||||
- Simulate a TLS ClientHello for an unconfigured domain:
|
||||
• Verify `port80Handler.addDomain` is called and `certificateRequested` event emitted.
|
||||
• Confirm handshake completes with default cert context.
|
||||
- Simulate HTTP-01 challenge flow for a new domain:
|
||||
• Verify on first HTTP request, `addDomain` is invoked and 503 returned.
|
||||
• After manually injecting a challenge in `Http01MemoryHandler`, verify 200 with key authorization.
|
||||
- Simulate successful ACME response and ensure SNI now returns the real cert.
|
||||
|
||||
6. Final validation:
|
||||
- Run `pnpm test` to ensure all existing tests pass.
|
||||
- Add new unit/integration tests for the dynamic provisioning flow.
|
Reference in New Issue
Block a user