[ 
https://issues.apache.org/jira/browse/HDDS-17353?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Attila Doroszlai updated HDDS-17353:
------------------------------------
    Fix Version/s: 2.3.0
       Resolution: Fixed
           Status: Resolved  (was: Patch Available)

> Ozone client registers the BouncyCastle security provider at start-up 
> although it doesn't need it
> -------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17353
>                 URL: https://issues.apache.org/jira/browse/HDDS-17353
>             Project: Apache Ozone
>          Issue Type: Bug
>          Components: Ozone Client
>            Reporter: Andrey Yarovoy
>            Assignee: Andrey Yarovoy
>            Priority: Major
>              Labels: pull-request-available
>             Fix For: 2.3.0
>
>
> *Problem*
> Every Ozone client registers the BouncyCastle JCE provider at start-up, and a 
> plain client uses almost none of it.
> - Client code creates a `SecurityConfig` just to read flags such as "is 
> security enabled":
> `BasicRootedOzoneClientAdapterImpl`, `BasicOzoneClientAdapterImpl`, 
> `XceiverClientGrpc`, `GrpcOmTransport`.
> It also calls `OzoneSecurityUtil.isSecurityEnabled(conf)`, from `RpcClient` 
> and `XceiverClientCreator`.
> - Both of these call `SecurityConfig.initSecurityProvider(conf)`. With the 
> default `hdds.security.provider=BC`,
> that runs `Security.addProvider(new BouncyCastleProvider())`.
> - BouncyCastle's jar is signed, so the JVM first verifies the jar, then loads 
> the provider's classes.
> What uses the provider:
> - **Services:** key generation (`HDDSKeyGenerator`), certificate requests and 
> signing (`CertificateSignRequest`,
> `SelfSignedCertificate`, `DefaultApprover`), and reading certificate paths 
> from PEM files
> (`CertificateCodec.generateCertPathFromInputStream`, which uses 
> `generateCertPath(in, "PEM")`). Only BouncyCastle
> supports that encoding.
> - **Clients:** one call. `ServiceInfoEx.getCaCertificates()` → 
> `OzoneSecurityUtil.convertToX509()` →
> `CertificateCodec.readX509Certificate()` parses each CA certificate OM 
> returns with
> `CertificateFactory.getInstance("X.509", "BC")`. Each is a single PEM 
> certificate, which the JDK's own X.509
> factory parses as well.
> - **Other lookups:** `addProvider` appends BouncyCastle after the JDK 
> providers, so lookups that don't name a
> provider already resolve to a JDK implementation. `STSTokenEncryption` in OM 
> uses its own `BouncyCastleProvider`
> instance and doesn't depend on the registration.
> The cost shows up wherever many short-lived client JVMs start: YARN 
> `ContainerLocalizer` JVMs, MapReduce tasks, CLI
> commands. A MapReduce job starts about four cold clients one after another on 
> its critical path. Measured on a
> secure compose cluster (Kerberos + gRPC TLS, JDK 25), in fresh JVMs:
> - **On its own,** after loading the configuration: registering the provider 
> takes **182 ms** median (176–224 ms,
> 9 JVMs) and loads about 1,000 extra classes.
> - **Inside a real cold client** (`FileSystem.get()` on `ofs://`, 
> getFileStatus, a 64 KB read), with
> `-Xlog:class+load`: the stretch from loading `SecurityConfig` to the next 
> Hadoop class takes **140–176 ms**. It
> starts with about 60 ms of jar verification and loads 498 BouncyCastle 
> classes. Another 117 BouncyCastle classes
> load later, when the CA certificates are parsed.
> That's roughly 0.6 s per MapReduce job, from a client that generates no keys 
> and signs nothing.
> *Proposed change*
> Register the provider only where it's needed:
> 1. `SecurityConfig`'s constructor and `OzoneSecurityUtil.isSecurityEnabled()` 
> no longer call
> `initSecurityProvider()`.
> 2. `SecurityConfig.getProvider()` calls `initSecurityProvider()` before 
> returning the provider name. Every service
> path that names the provider goes through it: key generation, certificate 
> requests, signing. The explicit calls
> in `HddsServerUtil.getScmSecurityClient*` stay.
> 3. `CertificateCodec`:
> - `readX509Certificate()` / `getX509Certificate()` parse with the JDK's X.509 
> factory
> (`CertificateFactory.getInstance("X.509")`);
> - `getCertFactory()` stays BouncyCastle-backed for `generateCertPath(in, 
> "PEM")`, and registers the provider
> itself before the lookup, instead of throwing "BouncyCastle JCE provider not 
> loaded".
> 4. Optional, for safety: every service registers the provider once at 
> start-up (SCM, OM, datanode, Recon, S3
> Gateway). Then any service-side lookup that relies on the provider being 
> registered, without going through
> `getProvider()` or `getCertFactory()`, keeps working.
> *Compatibility*
> - Services see no difference if step 4 is done. Without it, the risk is a 
> service-side lookup that names BouncyCastle
> without going through `getProvider()` or `getCertFactory()`; none was found 
> in `src/main`.
> - Clients parse CA certificates with the JDK instead of BouncyCastle. Ozone's 
> certificates are standard X.509
> issued by SCM. Equality and trust checks compare encoded certificates, so the 
> parser's provider doesn't matter. A
> certificate that BouncyCastle accepts but the JDK rejects would now fail on 
> the client.
> - Applications that relied on Ozone registering BouncyCastle as a side 
> effect, and then asked for it by name, would
> need to register it themselves. That was never documented behavior.
> - `hdds.security.provider` keeps its meaning for services.
> *Tests*
> - Unit: after creating a `SecurityConfig` and calling 
> `OzoneSecurityUtil.isSecurityEnabled()`,
> `Security.getProvider("BC")` is null. This needs a fresh JVM or a test that 
> removes the provider first, because
> registration is JVM-wide.
> - Unit: `CertificateCodec.readX509Certificate()` on an SCM-issued PEM 
> certificate gives a certificate equal to the
> one BouncyCastle parses, `getEncoded()` included. `getCertFactory()` works 
> when the provider isn't registered yet.
> - Unit: `SecurityConfig.getProvider()` leaves the provider registered.
> - Existing secure integration tests (key and certificate generation, CA 
> rotation, gRPC TLS) pass unchanged.
> - Measurement: the cold-client probe on a secure compose cluster shows 
> `FileSystem.get()` about 150 ms shorter, and
> no BouncyCastle classes in a client's class-load log.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to