Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Security.addProvider(new BouncyCastleProvider()) into a Callable block to avoid NoClassDefFoundError #5312

Merged
merged 2 commits into from
Feb 19, 2024

Conversation

jmarti44
Copy link

@jmarti44 jmarti44 commented Jul 5, 2023

Description

Fix #5296

  • Moved Security.addProvider call inside a Callable block so that whole block does not throw NoClassDefFoundError for BouncyCastleProvider (In user's scenario bc-fips is used instead of bcprov-jdk18on that does not contain org.bouncycastle.jce.provider.BouncyCastleProvider
  • Added a new module kubernetes-client-deps-compatibility-tests to Kubernetes Client project for having project specific tests where we can test behavior of client in presence of different dependencies on the classpath
  • Added a test for the abovementioned scenario in a separate module named kubernetes-client-deps-compatibility-tests/kubernetes-client-init-bc-fips with org.bouncycastle:bc-fips and org.bouncycastle:bcpkix-fips dependencies.

where CertUtils would automatically try to create an instance of the bouncy castle provider within the callable when there is a not bouncy castle fips provider provided. Wrapping it in a callable in this fashion would automatically throw a NoClassDefFound error despite already providing a security bouncy castle fips provider. Moving the conditional where the application searches for the fips provider just outside and before the callable will successfully create an instance of PEMKeyPair and will return successfully when a BouncyCastleFipsProvider is created.

I have added two zipped maven projects to help illustrate the issue and solution. They are both simple projects that print a successful message when both a Kubernetes Client and OpenShift Client is successfully built when a FIPS provider is provided. The project fabric8_issue utilizes a version of the kubernetes client api with this proposed fix. It is able to print a success message as shown in the screenshot below
Screenshot 2023-07-05 at 4 20 02 PM

The second project (fabric8_issue2) is the same project with the current version of the Kubernetes Client API (pulled from master). The NoClassDefFound Error is thrown here.
Uploading Screenshot 2023-07-05 at 4.24.12 PM.png…

These projects were built using JDK17.

simple_projects.zip

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change
  • Chore (non-breaking change which doesn't affect codebase;
    test, version modification, documentation, etc.)

Checklist

  • Code contributed by me aligns with current project license: Apache 2.0
  • I Added CHANGELOG entry regarding this change
  • I have implemented unit tests to cover my changes
  • I have added/updated the javadocs and other documentation accordingly
  • No new bugs, code smells, etc. in SonarCloud report
  • I tested my code in Kubernetes
  • I tested my code in OpenShift

@rohanKanojia
Copy link
Member

@jmarti44 : Could you please link the issue which you're trying to fix in this PR?

Would be nice to add some tests to validate the fix as well.

@rohanKanojia
Copy link
Member

@jmarti44 : Sorry, your PR description was hidden by comment. I've updated it now

@jmarti44
Copy link
Author

jmarti44 commented Jul 6, 2023

@rohanKanojia Would unit testing be neccessary for the method I changed since it's private method? What kind of testing could I utilize? My attempt was to illustrate screenshots from my sample projects and zipping the projects themselves as well

@sonarcloud
Copy link

sonarcloud bot commented Jul 6, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 1 Code Smell

0.0% 0.0% Coverage
0.0% 0.0% Duplication

@@ -149,13 +149,19 @@ private static PrivateKey loadKey(InputStream keyInputStream, String clientKeyAl
private static PrivateKey handleECKey(InputStream keyInputStream) {
// Let's wrap the code to a callable inner class to avoid NoClassDef when loading this class.
try {
if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
new Callable<String>() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to understand why you are creating a anonymous Callable object just to execute the call method without reusing it afterwards?
Could probably be simplified with just

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in case the dependency is not present at runtime. There's a utility for this sort of thing called OptionalDependencyWrapper, but it's in common, not api. We could consider moving that up to the api module for this.

@rohanKanojia
Copy link
Member

rohanKanojia commented Feb 12, 2024

@jmarti44 : Hello, Sorry for late reply. I downloaded your reproducer project and extracted it to my machine.

However I couldn't reproduce the issue either on fabric8-issue or fabric8-issue-2 reproducer projects. On my machine both apps print CLIENT BUILD SUCCESS:

fabric8-issue (using 6.11-SNAPSHOT)

fabric8-issue : $ mvn exec:java -Dexec.mainClass=com.mycompany.app.App
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- exec:3.1.1:java (default-cli) @ my-app ---
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
CLIENT BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.180 s
[INFO] Finished at: 2024-02-12T22:21:02+05:30
[INFO] ------------------------------------------------------------------------

fabric8-issue-2 (using Fabric8 Kubernetes Client 6.3.1)

fabric8-issue-2 : $ mvn exec:java -Dexec.mainClass=com.mycompany.app.App
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- exec:3.1.1:java (default-cli) @ my-app ---
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
CLIENT BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.043 s
[INFO] Finished at: 2024-02-12T22:22:28+05:30
[INFO] ------------------------------------------------------------------------

@rohanKanojia
Copy link
Member

rohanKanojia commented Feb 13, 2024

@jmarti44 : I also tried reproducing on MacOS environment using GitHub action but couldn't reproduce it ( I uploaded your reproducer repository to this github project).

Could you please check what I might be missing?

edit: This can only be reproduced in a FIPS enabled environment.

@rohanKanojia
Copy link
Member

rohanKanojia commented Feb 13, 2024

@jmarti44 : I tried your project by deploying it on a Red Hat OpenShift FIPS complaint cluster.

I deployed your application to OpenShift cluster using OpenShift Maven Plugin.
However, I'm facing NoClassDefFoundError in both fabric8-issue 'fabric8-issue 2' sample projects

Along with a FIPS complaint cluster, you also need a cluster that uses Elliptic Curve keys

@rohanKanojia
Copy link
Member

The problem reported in #5296 is caused due to a mix of how CertUtils.handleECKey is written and user's project configuration.

The user is relying in these bouncycastle dependencies:

These dependencies contain all classes used in CertUtils.handleECKey except org.bouncycastle.jce.provider.BouncyCastleProvider (which comes from org.bouncycastle:bcprov-jdk18on)

Upon debugging this, I noticed that NoClassDefFoundError is thrown even when the if statement in this block is evaluated as false

User sets Security.addProvider(new BouncyCastleFipsProvider()) before initializing KubernetesClient, so Security.getProvider("BCFIPS") == null evaluates to false

if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}

When Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()) is moved inside a Callable, no NoClassDefFoundError is thrown.

          if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
            new Callable<String>() {
              @Override
              public String call() {
                Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
                return null;
              }
            }.call();
          }

If I move this Callable block outside if block, I start getting NoClassDefFound for org/bouncycastle/jce/provider/BouncyCastleProvider

I checked by adding debug statements, control does not go inside the if block; it goes to next line where keyInputStream is read into pomObject and returned as PemKeyPair:

Object pemObject = new PEMParser(new InputStreamReader(keyInputStream)).readObject();

@manusa
Copy link
Member

manusa commented Feb 16, 2024

Yes, as we discussed internally, this fix is basically swallowing the NoClassDefFound exception and moving on to the next statements.

We should try to make the if-statement comply with the valid user setup instead.

@rohanKanojia rohanKanojia changed the title proposed fix for JCPEM FIPS provider bug Move Security.addProvider(new BouncyCastleProvider()) into a Callable block to avoid NoClassDefFoundError Feb 16, 2024
+ Add a comment for the reasoning behing moving Security.addProvider
  into a Callable block
+ Add a test inside a separate module using `bc-fips` and `bcpkis-fips`
  that reproduces the issue

Signed-off-by: Rohan Kumar <[email protected]>
Copy link

sonarcloud bot commented Feb 16, 2024

@manusa manusa added this to the 6.11.0 milestone Feb 19, 2024
@manusa manusa merged commit 2f11261 into fabric8io:main Feb 19, 2024
17 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Kubernetes Client API throws a NoClassDefFoundError despite providing a BouncyCastleFipsProvider
5 participants