[improve][misc] PIP-478: remove the superseded PIP-337 TLS stack - #26322
Conversation
fa40bd0 to
4afd91a
Compare
4afd91a to
cd252c9
Compare
cd252c9 to
f97d49d
Compare
david-streamlio
left a comment
There was a problem hiding this comment.
A −3265 removal PR lives or dies on two questions: is the removed code actually dead, and is the compatibility story true. I checked both independently rather than reading them off the description, and the substance holds up well. The retraction in the compatibility section is the right call and clearly took work to get right.
Deadness — verified. No dangling import of any of the thirteen removed types anywhere in the tree, and no fully-qualified usage except the two deliberate string literals (PulsarConfigurationLoader:68, ConfigurationDataUtils:50) that name the old default FQCN for the tolerance check — which is exactly right, since those must survive the class they name. Every remaining textual hit is a false positive: Athenz's keyRefresher.getKeyManagerProxy() is an Athenz-library type, SchemaRegistryServiceImpl's convertFromDomainType is schema code, the rest are comments and PIP prose.
The BouncyCastle move — semantics preserved. Same resolution order (registered BC → BCFIPS → classpath), Security.addProvider side effect retained on the classpath path, and MessageCryptoBc's own BcProviderHolder is what preserves first-use timing, so the claim survives the move to a lazy holder. I also checked the failure mode a removal like this invites — nothing in the tree, production or test, resolves BouncyCastle by name (Security.getProvider("BC") / getInstance(…, "BC")), so losing SecurityUtility's eager registration strands nothing. Conscrypt genuinely is an eager static final field in both classes, so that part of the claim is exact.
Deleted tests — coverage has successors. SslContextTest is PIP-337-only despite the generic name, and each of its four methods maps onto a live one: serverPemContextAcrossEngineAndCiphers / clientPemContextAcrossEngineAndCiphers for the SslProvider×cipher matrix, serverKeystoreContextBuildsWithCiphers / clientKeystoreContextBuildsWithCiphers for the keystore paths, plus defaultProtocolsAppliedWhenPolicyLeavesThemUnset for the TLSv1.3/1.2 floor.
The compatibility claim itself — verified at the source. SecurityUtility.loadConscryptProvider() really did call Conscrypt.setDefaultHostnameVerifier(wrap(new TlsHostnameVerifier())) process-globally from a static field initializer, so scoping the break to Conscrypt-pinned clients is correct. The base branch's own JcaProviders CAVEAT predicted this PR almost verbatim. ClusterData retention is backed by real code (BrokerService.warnOnStalePip337ClusterFactory, deduped per cluster), the config tolerance does the right thing (default FQCN treated as unset, custom value rejected with a migration message), and the JdkSslContexts javadoc line about the Conscrypt propagation workaround was genuinely stale — no such code remains in the method.
Three things:
1. The sweep is incomplete, and it misses in the two suites the PR cites as its evidence.
The stated goal is that "the corrected record is not contradicted by the next file a reader opens." Three files still contradict it:
AuthenticationTlsHostnameVerificationTest.java:143— "CN-based matching is no longer supported (PIP-478)." That is verbatim the retracted claim. Also:128and:182, "SAN-based (RFC 2818) host-name verification". This file is not in the diff at all, and it is the first suite the PR names under Verifying this change.ProxyWithAuthorizationTest.java:77-79— "SAN-based (RFC 2818) hostname verification … CN-based matching is no longer supported (PIP-478, Pulsar 5.0), so this cert always fails hostname verification." This file is in the diff — the sweep corrected lines 186 and 245 and stopped short of this one.JcaProviders.java:298— "standard RFC 2818 (SAN-based) verification, matching PIP-478's removal of the deprecated CN-based matching" — three lines above the corrected text this PR adds at :300.
The ProxyWithAuthorizationTest one is worth more than staleness: it states the wrong reason the test passes. The PR's own analysis is that these suites "fail on CN mismatch, not on SAN absence" — the comment tells the next reader the opposite, next to the fixture that would mislead them.
I checked whether these are simply deferred to a later PR in the stack: they are not. All three survive unchanged at lh-pip-478-proxy-async-auth (#26328), the top of the stack, which contains this branch plus #26326 and #26327.
2. "RFC 2818" is the wrong citation, and the PR body repeats it.
RFC 2818 §3.1 mandates the CN fallback: "If a subjectAltName extension of type dNSName is present, that MUST be used as the identity. Otherwise, the (most specific) Common Name field … MUST be used." So Conscrypt refusing a SAN-less certificate is stricter than RFC 2818, not "its own RFC 2818 verification" (PR body, and pip-478.md:1247). The phrase is inherited from the SecurityUtility comment being deleted, which had it wrong too.
package-info.java gets this exactly right — "per RFC 6125 the JDK and OpenSSL engines consult the CN … Conscrypt does not fall back to the CN" — so the code doc and the PIP now characterize the same behaviour with different standards. Since this passage is the corrected record, I'd align the PIP and body on the package-info's framing: RFC 6125 for the fallback, and "does not implement the CN fallback RFC 2818 allows" for Conscrypt.
3. Nit: two build-file comments now misdescribe the timing.
pulsar-common/build.gradle.kts and pulsar-broker-common/build.gradle.kts read "tests that exercise JcaProviders (which loads org.bouncycastle.jce.provider.BouncyCastleProvider in a static initializer)". True of SecurityUtility (BC_PROVIDER was an eager static final), not of JcaProviders, which resolves BC through BouncyCastleHolder on first use — the very property the PR highlights as preserved. Mechanical rename carrying over a clause that no longer describes the class.
None of this touches the removal itself, which I think is sound. 1 and 2 are about the corrected record being self-consistent — and given that this PR exists partly to retract a false compatibility claim, a reader landing on AuthenticationTlsHostnameVerificationTest:143 would come away with the retracted version.
For disclosure: this is static analysis over the stacked branch. I did not run sanityCheck or the cited suites locally.
There was a problem hiding this comment.
Pull request overview
Removes the superseded PIP-337 TLS stack and documents provider-specific hostname verification behavior.
Changes:
- Deletes obsolete TLS factories, utilities, hostname verifier classes, and tests.
- Moves message-crypto provider lookup to
JcaProviders. - Corrects SAN-only compatibility claims.
Reviewed changes
Copilot reviewed 41 out of 41 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/certificate-authority/generate_keystore.sh |
Revises certificate guidance. |
tests/certificate-authority/ec/jks/key_store_generation.txt |
Revises SAN guidance. |
pulsar-proxy/.../SslContextFallbackSynthesisTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyWithoutServiceDiscoveryTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyWithAuthorizationTest.java |
Revises hostname-verification comments. |
pulsar-proxy/.../ProxyTlsTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyTlsFactoryTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyServiceTlsStarterTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyMutualTlsTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyKeyStoreTlsWithoutAuthTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyKeyStoreTlsWithAuthTest.java |
Corrects TLS comment. |
pulsar-proxy/.../ProxyKeyStoreTlsTransportTest.java |
Corrects TLS comment. |
pulsar-common/.../TrustManagerProxyTest.java |
Removes obsolete proxy test. |
pulsar-common/.../tls/JdkSslContextsTest.java |
Corrects provider test terminology. |
pulsar-common/.../netty/SslContextTest.java |
Removes legacy factory tests. |
pulsar-common/.../KeyManagerProxyTest.java |
Removes obsolete proxy test. |
pulsar-common/.../DefaultPulsarSslFactoryTest.java |
Removes legacy factory tests. |
pulsar-common/.../TrustManagerProxy.java |
Removes obsolete trust-manager proxy. |
pulsar-common/.../tls/JdkSslContexts.java |
Updates provider documentation. |
pulsar-common/.../tls/JcaProviders.java |
Documents native Conscrypt behavior. |
pulsar-common/.../SecurityUtility.java |
Removes superseded TLS utility. |
pulsar-common/.../PulsarSslFactory.java |
Removes PIP-337 SPI. |
pulsar-common/.../PulsarSslConfiguration.java |
Removes legacy configuration model. |
pulsar-common/.../keystoretls/SSLContextValidatorEngine.java |
Removes unused validator. |
pulsar-common/.../keystoretls/package-info.java |
Removes obsolete package metadata. |
pulsar-common/.../keystoretls/KeyStoreSSLContext.java |
Removes legacy keystore context. |
pulsar-common/.../KeyManagerProxy.java |
Removes obsolete key-manager proxy. |
pulsar-common/.../DefaultPulsarSslFactory.java |
Removes legacy factory implementation. |
pulsar-common/.../tls/TlsHostnameVerifier.java |
Removes custom hostname verifier. |
pulsar-common/.../tls/SubjectName.java |
Removes verifier helper. |
pulsar-common/.../tls/PublicSuffixMatcher.java |
Removes verifier helper. |
pulsar-common/.../tls/PublicSuffixList.java |
Removes verifier helper. |
pulsar-common/.../tls/package-info.java |
Documents provider-specific CN fallback. |
pulsar-common/.../tls/impl/TlsContexts.java |
Updates legacy references. |
pulsar-common/.../tls/DomainType.java |
Removes verifier helper. |
pulsar-common/build.gradle.kts |
Updates dependency comment. |
pulsar-client-messagecrypto-bc/.../MessageCryptoBc.java |
Uses JcaProviders for Bouncy Castle. |
pulsar-client-messagecrypto-bc/build.gradle.kts |
Updates provider-resolution comment. |
pulsar-broker-common/.../TlsFactorySupport.java |
Corrects hostname-verification documentation. |
pulsar-broker-common/build.gradle.kts |
Updates dependency comment. |
pip/pip-478.md |
Corrects compatibility and upgrade guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Consolidating the sweep into one checklist, now that the Copilot review has landed — it found two sites I missed, and chasing those turned up an eighth. Same defect in all of them: the retracted "a CN-only certificate is no longer accepted" claim, in the wording each file happens to use.
Six of the eight are in files this PR already edits, and two of those ( Why my original three missed the rest, in case it helps you check for a ninth: I grepped for the retracted phrasings — The last one is milder than the others and I'd treat it separately: Nothing here changes my read of the removal itself, which I still think is sound. |
9d245c2 to
9424ecf
Compare
…name sweep ### Motivation Review of #26322 found the hostname-verification correction was itself inaccurate in two ways, and incomplete in a third. **The RFC citation is wrong.** RFC 2818 section 3.1 *mandates* the CN fallback ("Otherwise, the (most specific) Common Name field ... MUST be used"), and RFC 6125 section 6.4.4 still permits it as a last resort. So Conscrypt refusing a SAN-less certificate is *stricter* than RFC 2818, not "its own RFC 2818 verification". The phrase was inherited from the `SecurityUtility` comment this series deletes, which had it wrong too. **The sweep stopped short.** Three comments still asserted the retracted claim that CN matching no longer works at all, including in the suite the PR names as its evidence and in two operator-facing conf files. **Two fixture comments state the wrong reason for a passing test.** The no-SAN certificates fail because their CN does not match the advertised host, not because a SAN-less certificate can never match. A reader correcting the fixture by adding a SAN would be surprised; one who set `CN=localhost` would be more surprised. ### Modifications - `JcaProviders`: describe Conscrypt's default as SAN-only and stricter than both RFCs, rather than as an application of RFC 2818. - `pip-478.md`: same correction, with the mandating text quoted. - `ProxyWithAuthorizationTest` (2 comments) and `AuthenticationTlsHostnameVerificationTest` (3): state that the fixtures fail on both counts — no SAN to match, and a non-matching CN. - `conf/client.conf`, `conf/functions_worker.yml`: SANs are authoritative and the CN is consulted only for a certificate with no dNSName SAN; drop "CN matching is not supported". - `generate_keystore.sh`, `key_store_generation.txt`: the SAN makes the fixture portable across providers and endpoint forms; `CN=localhost` alone satisfies the default engines but not Conscrypt and never an IP literal. - Two build files: `JcaProviders` resolves BouncyCastle reflectively on first use, not in a static initializer — that was true of the deleted `SecurityUtility`, and is the very property this PR preserves. Comment-only; no behaviour change. Assisted-by: Claude Code (Opus 5)
|
Your three findings are fixed in Thank you — and the part I most needed checking is the part you checked: that a PR whose stated 2 first, because 1 and 3 fall out of it. The RFC citation is wrong, and you have the mandate the right way round. RFC 2818 §3.1: "If a You also traced where it came from, and it is worth stating for the record, because it explains why
Both halves are wrong, and they are wrong in mutually inconsistent ways — RFC 2818 does not
I took your suggestion to align on 1. The sweep was incomplete — and worse than you could see from the diff. Fixed, and extended Your three:
You were right that this matters more than staleness on the Both are reached at Two files you could not have found from the diff, and they are the ones that matter most.
That is the retracted claim, in the wrong-citation form, in operator-facing configuration files — 3. The build-file comments — taken, and the inversion is the point. Two things I deliberately did not change, so they are visible rather than silent:
On scope: three of the corrected files ( Three more, found by running an adversarial pass over the fix itself — worth listing because two
And one more, which finishes the thought your finding started. You said the record should cite
Noted on disclosure: static analysis over the stacked branch, no local suite run. On this side |
1854e8b to
80a3f4f
Compare
|
Re-ran my inventory on the current head: all eight sites are corrected, including the two the cert scripts stated in wording my greps had missed. The sweep is complete as far as I can find. More usefully — you corrected my correction, and you are right. I told you to cite RFC 6125 for the CN fallback. That was wrong, and the new text says why: RFC 6125 §6.4.4 permits the CN check only when the certificate presents no DNS-ID, SRV-ID or URI-ID at all, which is a stricter condition than what the JDK's The RFC 9525 point is the part I did not know and should have: 9525 (2023) obsoletes 6125 and forbids the CN check outright, so Conscrypt is not being eccentric — it implements the current standard, and the default engines are the lenient ones. That reframes the whole compatibility note from "Conscrypt is stricter than the spec" to "the default engines are still doing what the IETF retired", which is a materially better thing for an operator to read. The IP-literal clarification is a good addition too — a SAN-less certificate has never worked for an IP connection, so the fixtures were always right even where the stated reason was not. Net: my finding that "Conscrypt applies its own RFC 2818 verification" was wrong stands, but my proposed replacement was also wrong, and what landed is better than either. Worth recording here rather than leaving the thread implying I was right on both halves. No further findings on this one. |
david-streamlio
left a comment
There was a problem hiding this comment.
Approving. I re-ran my full inventory on this head and every site is corrected, including the two the cert scripts stated in wording my own greps had missed.
The RFC work went further than my finding: my "RFC 2818 is the wrong citation for Conscrypt" was right, but my proposed replacement was also wrong, and what landed — 2818 for the mandated fallback, 6125 §6.4.4 for the last-resort CN-ID, 9525 for the current position — is better than either. Details in the comment above.
The removal itself I still consider sound: deadness independently verified, the BouncyCastle move semantics-preserving, and the deleted tests have named successors in FileBasedTlsFactoryTest.
…ry stack The PIP-337 TLS infrastructure has no callers left. The core migration replaced it with the PulsarTlsFactory SPI and the common.util.tls helpers, and the configuration keys that once named DefaultPulsarSslFactory by class name (sslFactoryPlugin, brokerClientSslFactoryPlugin) are gone — they now sit on the removed-key tolerance lists, which PulsarConfigurationLoaderTest and ConfigurationDataUtilsTest already pin. Deleted, each verified unreachable from any production path: PulsarSslFactory, DefaultPulsarSslFactory, PulsarSslConfiguration, KeyManagerProxy, TrustManagerProxy, KeyStoreSSLContext, SSLContextValidatorEngine, and the four tests that exist only to exercise them. SSLContextValidatorEngine had no references at all, not even from its sibling. SecurityUtility.createAutoRefreshSslContextForClient goes with them: it was the sole production referent of KeyManagerProxy and TrustManagerProxy and had no callers of its own. The rest of SecurityUtility is removed in the next commit, once its last live consumer is migrated. Despite its name and package, SslContextTest is not a general Netty test — every case constructs a DefaultPulsarSslFactory with a PulsarSslConfiguration. Assisted-by: Claude Code (Opus 5)
…move SecurityUtility SecurityUtility had exactly one live consumer left after the PIP-337 stack went: message crypto's BouncyCastle provider lookup. Point it at the decomposed equivalent and delete the monolith. MessageCryptoBc now resolves the provider through JcaProviders.requireBouncyCastleProvider(), which performs the same FIPS-agnostic lookup — non-FIPS "BC" or FIPS "BCFIPS", whichever is on the classpath. The lookup stays inside the initialization-on-demand holder, so a resolution failure still surfaces at first asymmetric-crypto use rather than at class loading. One deliberate difference: an absent BouncyCastle now fails with an IllegalStateException naming the caller instead of a RuntimeException wrapping the classpath-load failure. Same failure point, better message. Conscrypt registration is unaffected. Both classes register it eagerly in their static initializers, so it still lands in the JCA provider list; what SecurityUtility additionally installed — a process-global CN-tolerant hostname verifier — is dealt with in the next commit, where the classes it depended on are removed. Assisted-by: Claude Code (Opus 5)
…hing Pulsar carried its own hostname verifier — TlsHostnameVerifier plus SubjectName, PublicSuffixMatcher, PublicSuffixList and DomainType — whose only referent was SecurityUtility, now gone. Delete them; verification is whatever the configured provider implements under the standard endpoint-identification algorithm "HTTPS". Be precise about the compatibility story, because the obvious statement of it is wrong. The standard algorithm is NOT SAN-only. Per RFC 6125 the JDK and OpenSSL/BoringSSL engines consult a certificate's CN when it carries no dNSName SAN, and ignore the CN once any is present — verified here with real JDK handshakes against this repo's own fixtures: the CN-only hn-verification/broker-cert.pem is accepted for its CN host and rejected for another, and the SAN-bearing broker.cert.pem is rejected for its CN host. So on the default engines a CN-only certificate is accepted exactly as before. What does change is Conscrypt-pinned clients. SecurityUtility's static initializer installed the CN-tolerant verifier as Conscrypt's process-global default; without it, Conscrypt applies its own RFC 2818 verification and rejects a server certificate with no subjectAltName. That reaches a deployment only if it names Conscrypt as its client-side JSSE provider and still uses CN-only server certificates. Conscrypt is never the client-side default — it is defaulted only for TlsPurpose.WEB, i.e. server listeners, which do not verify hostnames. Four documents asserted the SAN-only guarantee and are corrected rather than kept: pip-478.md's summary line, its hostname-matching bullet, its breaking-change note, and the common.tls package javadoc. Deliberately no new "CN-only must be rejected" test: it would fail today on the default engines, which is the point. Both in-repo SAN-less server certificates are negative fixtures on the default engine and keep passing for the same reason as before — AuthenticationTlsHostnameVerificationTest fails on CN mismatch, not on SAN absence. Assisted-by: Claude Code (Opus 5)
…record Review found that three of the four CN/SAN passages were corrected and the fourth was not — and the one missed is the breaking-change bullet under Backward & Forward Compatibility, which is exactly where the other two send compatibility readers. It still announced "SAN required" and "a CN-only certificate no longer suffices", contradicting the Security Considerations text two sections above. An operator following the document's own routing would have reissued fleet certificates for a break that does not exist on the default engines. The bullet now states what actually changes: hostname verification is on by default, a matching SAN always suffices, the CN is still consulted on the default engines for a certificate carrying no dNSName SAN, and Conscrypt-pinned clients are what tighten. The mechanism is stated precisely too. Netty's native OpenSSL/BoringSSL engine does not verify the hostname itself — it delegates to the Java trust manager — so the JDK's HostnameChecker and its CN fallback govern both default engines, not BoringSSL's own matching. Also swept the framing out of the code: the broker TLS support javadoc claimed PIP-478 turns on "SAN-only verification", and eleven proxy tests plus two certificate-generation scripts carried the same phrase, so the corrected record would have been contradicted by the next file a reader opened. Two mislabels found in the same review, pre-existing but adjacent: the trust-manager fallback is documented as being for "a provider that offers none (e.g. Conscrypt)", but real Conscrypt registers both PKIX factories — the fallback is for a third-party provider, and the test stub named "Conscrypt-shaped" models a provider that does not exist. Renamed and re-described. The same javadoc still advertised a Conscrypt hostname-verifier propagation workaround whose body was removed when Conscrypt 2.6.1 landed. Assisted-by: Claude Code (Opus 5)
…name sweep ### Motivation Review of #26322 found the hostname-verification correction was itself inaccurate in two ways, and incomplete in a third. **The RFC citation is wrong.** RFC 2818 section 3.1 *mandates* the CN fallback ("Otherwise, the (most specific) Common Name field ... MUST be used"), and RFC 6125 section 6.4.4 still permits it as a last resort. So Conscrypt refusing a SAN-less certificate is *stricter* than RFC 2818, not "its own RFC 2818 verification". The phrase was inherited from the `SecurityUtility` comment this series deletes, which had it wrong too. **The sweep stopped short.** Three comments still asserted the retracted claim that CN matching no longer works at all, including in the suite the PR names as its evidence and in two operator-facing conf files. **Two fixture comments state the wrong reason for a passing test.** The no-SAN certificates fail because their CN does not match the advertised host, not because a SAN-less certificate can never match. A reader correcting the fixture by adding a SAN would be surprised; one who set `CN=localhost` would be more surprised. ### Modifications - `JcaProviders`: describe Conscrypt's default as SAN-only and stricter than both RFCs, rather than as an application of RFC 2818. - `pip-478.md`: same correction, with the mandating text quoted. - `ProxyWithAuthorizationTest` (2 comments) and `AuthenticationTlsHostnameVerificationTest` (3): state that the fixtures fail on both counts — no SAN to match, and a non-matching CN. - `conf/client.conf`, `conf/functions_worker.yml`: SANs are authoritative and the CN is consulted only for a certificate with no dNSName SAN; drop "CN matching is not supported". - `generate_keystore.sh`, `key_store_generation.txt`: the SAN makes the fixture portable across providers and endpoint forms; `CN=localhost` alone satisfies the default engines but not Conscrypt and never an IP literal. - Two build files: `JcaProviders` resolves BouncyCastle reflectively on first use, not in a static initializer — that was true of the deleted `SecurityUtility`, and is the very property this PR preserves. Comment-only; no behaviour change. Assisted-by: Claude Code (Opus 5)
… fixture comments ### Motivation A local adversarial pass over the previous commit found three more instances of the defect that commit was fixing — including one in the correction itself. **The CN fallback is for hostnames only.** The corrected text said the CN is consulted "when a certificate carries no `dNSName` SAN", with no reference identity qualifier. The JDK's `HostnameChecker` dispatches an IP literal to `matchIP`, which consults `iPAddress` SANs and never the CN — so a SAN-less certificate has never worked for an IP connection, on any provider. The previous commit stated this correctly in `generate_keystore.sh` and omitted it from the three normative places, which is an inconsistency inside one commit. **Two OAuth2 test comments assert a fixture has no SAN when it does.** `broker.keystore.jks` carries `DNSName: localhost` and `IPAddress: 127.0.0.1`. It fails because it is self-signed and does not chain to `ca.cert.pem` — a trust failure, not a hostname one. Same origin as the conf files (#26282). **Two public javadocs now over-promise.** `ClientBuilder` and `PulsarAdminBuilder` document hostname verification as matching `hostname(CN/SAN)` per RFC 2818 §3.1. Both builders also expose `sslProvider(String)`, and this PR is what makes that promise false for a Conscrypt-pinned client: before it, `SecurityUtility` installed the CN-tolerant verifier process-globally, so the promise held there too. ### Modifications - `pip-478.md`, `JcaProviders`, `common.tls` `package-info`: the CN fallback applies only when connecting by hostname; an IP literal is matched against `iPAddress` SANs on every provider. - `OAuth2IdpTlsFrameworkClientTest`, `AdminOAuth2IdpTlsEndToEndTest`: state the real reason the shared keystore is unusable (self-signed, fails trust), not a SAN it actually has. - `ClientBuilder`, `PulsarAdminBuilder`: record that the CN is a fallback, that it applies only on the default engines and only for hostnames, and that a Conscrypt-pinned client never falls back to it. Comment/javadoc-only; no behaviour change. `quickCheck` and `sanityCheck` pass. Assisted-by: Claude Code (Opus 5)
### Motivation A third review pass over the two preceding commits found the corrected RFC record stops one RFC short, and misattributes one condition. **RFC 6125 is obsoleted.** The text said Conscrypt's SAN-only verification is stricter than "the last-resort CN-ID check RFC 6125 §6.4.4 still permits". RFC 9525 (2023) obsoletes RFC 6125 and forbids the check outright — "The Common Name RDN MUST NOT be used to identify a service". So Conscrypt is not extra-standard at all; it implements the current rule, and the default engines are the lenient ones. Framing it the other way invites a future maintainer to cite RFC 6125 as the last word and reopen a closed question. **The fallback condition is RFC 2818's, not RFC 6125's.** The text attributed "the CN is consulted when a certificate carries no dNSName SAN" to RFC 6125 §6.4.4, whose condition is stricter — it permits the CN check only when the certificate presents no DNS-ID, SRV-ID or URI-ID at all. The JDK's `HostnameChecker` implements the looser RFC 2818 condition (it falls back on a certificate carrying only a URI SAN, which RFC 6125 forbids), so RFC 2818 is the accurate citation for the behaviour Pulsar actually gets. ### Modifications - `pip-478.md`, `JcaProviders`, `common.tls` `package-info`: cite RFC 9525 as the current rule and RFC 2818 §3.1 for the fallback condition the JDK implements; note explicitly that the default engines are the lenient party. - `ClientBuilder`: fix the pre-existing `@see` link text, which read "RFC 818". Comment/javadoc-only; no behaviour change. Assisted-by: Claude Code (Opus 5)
80a3f4f to
be570cf
Compare
|
/pulsarbot rerun |
Main Issue: #25890
PIP: #25890
Motivation
With the PIP-478 migration complete — the
PulsarTlsFactorySPI, the default file-based factory, the core migration and the v5-native auth inversion all landed — the PIP-337 TLS infrastructure it replaced has no callers left. So does theSecurityUtilitymonolith that PIP-478 decomposed into thecommon.util.tlshelpers, and the CN-based hostname-matching machinery whose only referent was that monolith.Removing them is the point of the exercise: PIP-478 justified its breaking changes partly on the maintenance cost of carrying two TLS stacks, and that cost is only actually paid down when the old one goes.
−3293 lines, +109.
Modifications
Six commits, each independently compiling, ordered so the reference graph unwinds cleanly:
PulsarSslFactory,DefaultPulsarSslFactory,PulsarSslConfiguration,KeyManagerProxy,TrustManagerProxy,KeyStoreSSLContext,SSLContextValidatorEngine, and the four tests that exist only to exercise them. Each verified unreachable, including by class-name string — the configuration keys that once named the default factory are gone and now sit on the removed-key tolerance lists, whichPulsarConfigurationLoaderTestandConfigurationDataUtilsTestalready pin.SecurityUtility.createAutoRefreshSslContextForClientgoes with them: it was the sole production referent of the two proxies and had no callers of its own.SecurityUtility. One live consumer remained — message crypto's BouncyCastle provider lookup — which moves toJcaProviders.requireBouncyCastleProvider(). Same FIPS-agnostic resolution (non-FIPSBCor FIPSBCFIPS, whichever is present), same initialization-on-demand timing, so a resolution failure still surfaces at first asymmetric-crypto use rather than at class loading. Conscrypt registration is unaffected: both classes register it in their static initializers.TlsHostnameVerifier,PublicSuffixMatcher,PublicSuffixList,SubjectName,DomainType.SecurityUtilitycomment this PR deletes, which had it wrong in both directions), the three files the first sweep missed plus the two conf files, and two build-file comments that creditedJcaProviderswithSecurityUtility's eager static initializer when it resolves BouncyCastle lazily — the very property this PR preserves. Comment-only.iPAddressSANs and never the CN — commit 4 said this in the keystore script and omitted it from the three normative places); two OAuth2 test comments claiming the sharedbroker.keystore.jkshas no SAN when it carriesDNS:localhost+IP:127.0.0.1and actually fails as self-signed; and the two builder javadocs below. Comment/javadoc-only.HostnameCheckeractually implements (RFC 6125 §6.4.4's condition is stricter — no DNS-ID, SRV-ID or URI-ID at all). Comment/javadoc-only.The compatibility statement, stated carefully
An earlier draft of this change announced that hostname verification becomes SAN-only and a CN-only certificate is no longer accepted. That is false, and shipping it would have been worse than saying nothing: operators would have reissued certificates for a break that does not exist, and the deployments that genuinely do break would have had no warning.
Verified with real JDK handshakes against this repository's own fixtures:
HostnameCheckerimplements it — the CN is consulted when the client connects by hostname and the certificate carries nodNSNameSAN, and ignored once any is present. This holds on the JDK engine and on Netty's native OpenSSL/BoringSSL engine — the native engine does not verify hostnames itself, it delegates to the Java trust manager, so the JDK'sHostnameCheckergoverns both. The fallback never applied to an IP literal, which is matched againstiPAddressSANs only.SecurityUtility's static initializer installed the CN-tolerantTlsHostnameVerifieras Conscrypt's process-global default. With it gone, Conscrypt applies its own SAN-only verification and rejects a server certificate with nosubjectAltName. That is stricter than RFC 2818, not an application of it — §3.1 mandates the CN fallback ("Otherwise, the (most specific) Common Name field ... MUST be used") and RFC 6125 §6.4.4 permitted it as a last resort. But it is not a Conscrypt eccentricity either: RFC 9525 (2023) obsoletes RFC 6125 and forbids the CN check outright, so Conscrypt implements the current rule and the default engines are the lenient party. A deployment is affected only if it names Conscrypt as its client-side JSSE provider and still uses CN-only server certificates; the remedy is to reissue those with the hostname in the SAN. Conscrypt is never the client-side default — it is defaulted only forTlsPurpose.WEB, i.e. server listeners, which do not verify hostnames.Four documents asserted the wrong claim and are corrected, including the breaking-change bullet under Backward & Forward Compatibility that the other corrected passages route readers to. Test comments in fourteen suites, two certificate-generation scripts, two operator-facing conf files and two public-API javadocs repeated the phrase or a variant of it and are swept too, so the corrected record is not contradicted by the next file a reader opens. The two conf files —
conf/client.confandconf/functions_worker.yml— shipped with the merged core-migration PR (#26282) and are the most consequential of the set: an operator reading them to decide whether to reissue certificates before a 5.0 upgrade would have got the retracted answer.Deliberately no new "a CN-only certificate must be rejected" test: it would fail today on the default engines, which is the whole point.
What this PR does not remove
An earlier draft also removed
ClusterData.brokerClientSslFactoryPlugin/...Params. That is dropped, because its stated rationale — "factory selection is broker-level" — was refuted by the core migration, which made per-cluster selection real.pip-478.mdargues their retention as a deliberate compatibility decision in nine places, one of them promising that a 5.0 broker "preserves it rather than erasing it on rewrite". Removing them would also delete the only operator-facing signal for a stale value, and erase the fields from stored metadata on any peer-cluster or migration update — silently downgrading a 4.x broker in a mixed fleet to the default factory.Verifying this change
This change is a removal covered by existing tests:
sanityCheck(all modules, main and test sources) andquickCheckpass — the deletion's correctness is a reference-graph property, and a full compile is what checks it.:pulsar-common:test --tests '*tls*' --tests '*Crypto*'— the decomposed helpers and the migrated BouncyCastle lookup.AuthenticationTlsHostnameVerificationTestandProxyWithAuthorizationTest— the two suites carrying SAN-less negative fixtures, which would notice if hostname behaviour moved. Both still pass, and for the same reason as before: they fail on CN mismatch, not on SAN absence.HostnameChecker(matchDNSfalls back to the CN whenever nodNSNameSAN entry exists;matchIPnever consults the CN), the Conscrypt 2.6.1OkHostnameVerifier(SAN types 2 and 7 only, no CN path at all), and RFC 2818 §3.1 / RFC 6125 §6.4.4 / RFC 9525 directly.openssl x509 -noout -subject -ext subjectAltName,keytool -list -v), not inferred:no-subject-alt-cert.pemisCN=Brokerwith no extensions,hn-verification/broker-cert.pemisCN=broker.pulsar.apache.orgwith no extensions, both suites advertiselocalhost, and the sharedbroker.keystore.jkscarriesDNS:localhost+IP:127.0.0.1while being self-signed.Deadness was verified independently by review, including reflective and class-name-string loading, with the false positives triaged.
Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
The public API: thirteen public classes in
pulsar-commonare removed, all of them the PIP-337 stack or its CN-matching helpers, and all inventoried in PIP-478's removal impact section.InetAddressUtilsandNoopHostnameVerifiersurvive in the same package. Additionally,enableTlsHostnameVerification's javadoc on bothClientBuilderandPulsarAdminBuilderis corrected: it documented hostname verification as matchinghostname(CN/SAN)per RFC 2818 §3.1 unconditionally, which this PR makes false for a client that pins Conscrypt viasslProvider(...). Javadoc only — no signature change.Deployment: see the compatibility statement above — a Conscrypt-pinned client using CN-only server certificates must reissue them.
Documentation
doc-requireddoc-not-neededdocdoc-completeThe operator-facing consequence is documented in
pip-478.md's compatibility section, which this PR corrects.Matching PR in forked repository
PR in forked repository: lhotari#254
Prepared with the assistance of Claude Code (Opus 5).
Stack created with GitHub Stacks CLI • Give Feedback 💬