Sairamiv opened a new issue, #14008:
URL: https://github.com/apache/apisix/issues/14008

   ### Description
   
   ### Description
   
   When APISIX terminates TLS for several hosts that share one (wildcard) 
certificate and resolve to the same address, an HTTP/2 browser may **coalesce** 
a request for host B onto a connection it already opened for host A (RFC 9110 
§9.5 / RFC 7540 §9.1.1 connection reuse). If host B requires a client 
certificate but host A does not, the coalesced request arrives on a connection 
where **no client certificate was ever requested**.
   
   APISIX rejects this in `http_access_phase` via `verify_https_client` 
(`apisix/init.lua`, defined ~L388, called ~L777) with **`400 Bad Request`**. 
Browsers do **not** retry a `400`, so the coalesced connection is kept and the 
request fails permanently — the client-certificate prompt never appears and the 
user is stuck.
   
   ### Expected behavior
   
   Per **RFC 9110 §15.5.20**, the correct status when a request targets an 
authority the connection was not established to serve is **`421 Misdirected 
Request`**. All major browsers respond to `421` by retrying on a **fresh** 
connection, which is then negotiated with the correct SNI and triggers the 
client-certificate handshake.
   
   APISIX should return **`421` instead of `400`** when it rejects a request 
specifically because the connection's negotiated SNI (`$ssl_server_name`) 
differs from the request Host / `:authority` **and** the Host requires a client 
certificate the connection did not request. A config flag for 
backward-compatibility would be reasonable.
   
   ### Why this can only be fixed in core
   
   `verify_https_client()` runs inside `http_access_phase()`. Every plugin 
phase (route/global-rule `rewrite` and `access`) also runs inside 
`http_access_phase()` — i.e. *after* the `400` has already been returned — so 
this cannot be addressed by a plugin. The only Nginx phase earlier than 
`http_access_phase` is a server-level `rewrite_by_lua_block`, which is where a 
deployment can work around it today; but the authoritative fix belongs in 
core's client-cert rejection path.
   
   ### Steps to Reproduce
   
   1. One HTTPS listener; a wildcard cert covering `a.example.com` and 
`b.example.com`; both resolve to the same IP; HTTP/2 enabled.
   2. Configure `b.example.com` to require a client certificate; 
`a.example.com` to not.
   3. From an HTTP/2 client, open a connection to `a.example.com` (no client 
cert requested).
   4. On that same connection, send a request with `:authority: b.example.com` 
(what a browser does when it coalesces).
   5. Observe `400`. Expected: `421`, so the client re-handshakes on a new 
connection for `b.example.com`.
   
   ### Environment
   
   - APISIX version: 3.x
   - Deployment: multiple SNI hosts under one wildcard cert on a single HTTPS 
listener, per-host client-certificate requirement, HTTP/2 enabled.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to