Andrey Yarovoy created HDDS-16682:
-------------------------------------
Summary: Make the HTTPS SNI host check configurable instead of
unconditionally disabled
Key: HDDS-16682
URL: https://issues.apache.org/jira/browse/HDDS-16682
Project: Apache Ozone
Issue Type: Improvement
Components: Security
Reporter: Andrey Yarovoy
HDDS-8280 upgraded Jetty from 9.4 to 12.0.38. Jetty 12's SecureRequestCustomizer
defaults sniHostCheck to true, which rejects any HTTPS request whose Host header
(or TLS SNI) is not covered by the certificate the connector served, with a
400 "Invalid SNI". Jetty 9.4 only applied that check when the client's SNI
actually matched a certificate in the keystore, so requests carrying no SNI or
an unmatched name were served with the default certificate.
That is a behavior change for every Ozone HTTPS endpoint, and it breaks setups
that worked before the upgrade:
* requests to an IP literal, which carry no SNI at all;
* requests to localhost, a VIP, a load-balancer name, or any DNS alias that is
absent from the keystore certificate's CN/SubjectAlternativeName.
To keep the upgrade behavior-preserving, HttpServer2 currently disables the
check unconditionally:
hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/HttpServer2.java
httpConfig.addCustomizer(new SecureRequestCustomizer(false));
That was the right call for the upgrade itself -- it keeps HDDS-8280 free of a
behavior change on the TLS path -- but it leaves no way to get the hardening
back. Operators whose certificates do cover every name clients use should be
able to enable the check, and today they cannot without patching Ozone.
Note that the Ozone HTTPS keystore is operator-supplied: HttpServer2 reads it
from ssl-server.xml via hadoop.ssl.server.keystore.location, not from the SCM
internal CA. So whether the check can be satisfied depends entirely on the
operator's certificate, which is exactly why this belongs in configuration and
cannot be decided for them.
Proposed change
---------------
Add a boolean configuration key, default false, that selects the sniHostCheck
argument passed to SecureRequestCustomizer:
ozone.http.sni.host.check.enabled (default: false)
false -- current behavior: the Host/SNI name is not required to be covered by
the served certificate (Jetty 9.4 semantics).
true -- Jetty 12 default: reject requests whose Host or SNI is not covered by
the served certificate.
The key sits in the ozone.http.* namespace alongside ozone.http.policy and
ozone.http.basedir, which already govern this HTTP server family including its
HTTPS mode.
Scope:
* Read the key in HttpServer2.Builder (loadSSLConfiguration / the HTTPS
connector path) and pass it to SecureRequestCustomizer, replacing the
hard-coded false. Follow the shape of the existing allowAmbiguousUri flag --
a Builder setter plus a BaseHttpServer hook -- so a service can override the
cluster-wide value if one ever needs to.
* Document the key in ozone-default.xml, stating plainly that the default
preserves pre-Jetty-12 behavior and that enabling it requires the server
certificate to carry every hostname and IP clients connect with, as a
SubjectAlternativeName (an IP literal needs an iPAddress SAN, since such a
request sends no SNI).
* Mention it in the HTTPS section of the security docs next to the existing
TLS configuration.
Default kept at false deliberately: flipping it would be the behavior change
HDDS-8280 avoided, and it would break clusters reached by VIP or IP literal on
upgrade. Changing the default is a separate decision for a later release, and
should be made only with a release note.
Out of scope: Jetty's separate and stricter sniRequired option (reject requests
that send no SNI at all). It is a different policy with a different blast radius
and no pre-upgrade equivalent, so it should be its own Jira if anyone wants it.
Testing
-------
TestHttpServer2SSL already has testIpLiteralServedWithNonMatchingCertificate,
which asserts that the IP literal 127.0.0.1 is served 200 against the
CN=localhost test keystore -- that test pins the default-false behavior and
must keep passing unchanged. Add the mirror case: with the key set to true, the
same IP-literal request gets 400 while the covered "localhost" name still gets
200. That is a direct test of the knob in both positions against one keystore.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]