Update plugin-cli tool to isolate plugin Bouncy Castle from FIPS BC#138949
Update plugin-cli tool to isolate plugin Bouncy Castle from FIPS BC#138949ebarlas merged 26 commits intoelastic:mainfrom
Conversation
…h class loader isolation.
…ss loader parent to achieve isolation.
|
There are a few things that bother me slightly, that it would be nice to fix - if we can do so without adding too much complexity.
|
…umer interface rather than PgpSignatureVerifier.
|
@tvernum , I addressed all three points. The Gradle build file increased in complexity, but I think the changes are a net positive and worth the added complexity.
|
|
That approach looks good to me. |
|
Pinging @elastic/es-security (Team:Security) |
|
Hi @ebarlas, I've created a changelog YAML for you. |
Can we elaborate more on this? Is the concern here that when customers are running in FIPS mode, they provide their own bouncycastle implementation and this would in turn leak into the plugin CLI classpath causing potential issues? If so, I think we already have a solution for this, which is using |
Yes, the goal is for We evaded this issue in the past by ensuring that the Now, with FIPS 140-3 and BC FIPS 2.x, the landscape is more complicated. The isolation technique used here ensures that the ES classpath (ES
|
| 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2' | ||
| ) | ||
| if (buildParams.inFipsJvm) { | ||
| // Disable tests in FIPS mode due to jar hell between plugin-cli's |
There was a problem hiding this comment.
We didn't disable plugin cli tests under BC 1.x, why is the situation different with 2.x?
There was a problem hiding this comment.
Previously, plugin-cli and ES in FIPS mode always used the same BC FIPS 1.x JARs. Now, the landscape has changed, since ES in FIPS mode could be BC FIPS 1.x or BC FIPS 2.x.
There was a problem hiding this comment.
We could expand the conditional expression as follows.
if (buildParams.inFipsJvm && buildParams.fipsMode == "140-2") {
...
}
There was a problem hiding this comment.
We never had jarhell though, even in FIPS mode when BC existed in lib (added by test CI/user) as well as under libs/tools/plugin-cli. How did that work?
There was a problem hiding this comment.
There is no conflict or issue when ES lib contains org.bouncycastle:bc-fips:1.0.2.6 and plugin lib contains both org.bouncycastle:bc-fips:1.0.2.6 and org.bouncycastle:bcpg-fips:1.0.7.1. That works just fine.
There's a key difference, EmbeddedimplClassLoader still can't conflict with anything in the boot classloader. Yet in the case of bouncycastle existing under lib when added by test CI or users, we would still have jarhell. |
|
Using source sets here seems unnecessarily complex and feels like a hack to tell the IDE about runtime implementation details. Couldn't we just as well put this in it's own library project to do the same thing? I know the lines on when you do which can be a bit merky but all we're trying to do is separate code based on dependencies, and separate projects/modules accomplishes the same thing, and is a much less confusing pattern. Generally we use sourcesets when the code we're talking about is highly coupled. |
fe2a222 to
4a446c6
Compare
e4358b5 to
bad5c55
Compare
bad5c55 to
0353e28
Compare
|
|
||
| tasks.named("test").configure { | ||
| // Use original classpath, not the shadow JAR, since test code imports non-relocated packages | ||
| classpath = sourceSets.test.runtimeClasspath |
There was a problem hiding this comment.
Do we still need to do this? We want to test against the shadow jar, right, and then just add bouncycastle back to the test runtime classpath so we don't get classnotfound errors.
There was a problem hiding this comment.
Yes, ideally we'd like to test against the shadowed JAR. But in this case, InstallPluginActionTests is deeply coupled directly with Bouncy Castle. The test defines an anonymous class that extends InstallPluginAction and manipulates signature verification logic.
Without this configuration, tests fail with NoClassDefFoundError.
There was a problem hiding this comment.
I believe mixing org.bouncycastle and shadow.org.bouncycastle from the test perspective will be problematic, but I'll give it a try.
There was a problem hiding this comment.
Yeah, bouncy castle will be on the classpath twice here, but one will be shadowed. That should be fine, no?
There was a problem hiding this comment.
It's problematic because of the nuances of InstallPluginActionTests.
Here's the simple diff to (1) use the default test class path with the shadowed JAR and (2) selectively add BC JARs.
tasks.named("test").configure {
// Use original classpath, not the shadow JAR, since test code imports non-relocated packages
- classpath = sourceSets.test.runtimeClasspath
+ classpath += configurations.runtimeClasspath.filter {
+ it.name.startsWith("bc")
+ }
As you can see from this test failure, there is disagreement about BC classes when they are referenced from tests since InstallPluginAction uses shadow.org.bouncycastle and InstallPluginActionTests uses org.bouncycastle.
REPRODUCE WITH: ./gradlew ":distribution:tools:plugin-cli:test" --tests "org.elasticsearch.plugins.cli.InstallPluginActionTests" -Dtests.method="testSlowSignatureVerificationMessage {p0=com.google.common.jimfs.JimfsFileSystem@3138953b p1=org.elasticsearch.plugins.cli.InstallPluginActionTests$1Parameter$$Lambda/0x00000fe0003dde88@27df95e}" -Dtests.seed=23C6D41096344C33 -Dtests.locale=cy-Latn-GB -Dtests.timezone=Africa/Banjul -Druntime.java=25
'void org.elasticsearch.plugins.cli.InstallPluginAction.computeSignatureForDownloadedPlugin(java.io.InputStream, java.io.InputStream, org.bouncycastle.openpgp.PGPSignature)'
java.lang.NoSuchMethodError: 'void org.elasticsearch.plugins.cli.InstallPluginAction.computeSignatureForDownloadedPlugin(java.io.InputStream, java.io.InputStream, org.bouncycastle.openpgp.PGPSignature)'
at __randomizedtesting.SeedInfo.seed([23C6D41096344C33:CABFD595F4D03C34]:0)
at org.elasticsearch.plugins.cli.InstallPluginActionTests.testSlowSignatureVerificationMessage(InstallPluginActionTests.java:511)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at com.carrotsearch.randomizedtesting.RandomizedRunner.invoke(RandomizedRunner.java:1763)
at com.carrotsearch.randomizedtesting.RandomizedRunner$8.evaluate(RandomizedRunner.java:946)
at com.carrotsearch.randomizedtesting.RandomizedRunner$9.evaluate(RandomizedRunner.java:982)
at com.carrotsearch.randomizedtesting.RandomizedRunner$10.evaluate(RandomizedRunner.java:996)
There was a problem hiding this comment.
This should be resolvable by moving the shadowing to an inner library/project of plugin-cli. That is, create a subproject like you did before which will depend on (and shade) bouncycastle. Then the plugin-cli would depend on (compile and runtime) on this lib and in InstallPluginAction use the classes from this library.
There was a problem hiding this comment.
This worked well!
I reinstated the bc sub-project library. It now houses PgpSignatureVerifier, which completely encapsulates Bouncy Castle. This library gets shadowed and included in the plugin-cli distribution.
BC JARs are added explicitly to plugin-cli test class path but no collision occurs due to the shadowing of bc.
Overall, the PR amounts to a number of structural changes to get things in place. The only logic change needed was confined to a few lines in InstallPluginActionTests.
2c0392a to
7f955cc
Compare
…thin plugin-cli and shadow that bc sub-library only
distribution/build.gradle
Outdated
| libsWindowsServiceCli project(':distribution:tools:windows-service-cli') | ||
| libsAnsiConsole project(':distribution:tools:ansi-console') | ||
| libsPluginCli project(':distribution:tools:plugin-cli') | ||
| libsPluginCli project(path: ':distribution:tools:plugin-cli') |
There was a problem hiding this comment.
This change shouldn't be necessary. Not sure why we use path explicitly sometimes but it's redundant, and I think it's possible Gradle will be deprecating this map-style configuration syntax at some point in the future.
There was a problem hiding this comment.
Good catch. I stripped away the shadow config and left the explicit path. It's no longer needed.
Introduce bc sub-project library to encapsulate BC dependencies and shading. Update plugin-cli to use this new library.
1. Update plugin-cli tool to isolate BC (#138949) - Introduce bc sub-project library to encapsulate BC dependencies and shading. Update plugin-cli to use this new library. 2. FIPS 140-3 support with BC FIPS 2.0.x (#139319) - Comprehensive changes for the addition of FIPS 140-3 compliance with Bouncy Castle 2.0.x - Testing with BC FIPS 2.0.x activated with Gradle build property - FIPS Docker image activated with Gradle build property - ES launch verification of BC FIPS provider - Buildkite jobs activated with test-fips-140-3 label 3. Periodic FIPS 140-3 buildkite pipelines (#139909) - Add periodic FIPS 140-3 buildkite pipelines - Use test-fips allow-label for CI
The goals of this change to
plugin-cliare:A new
bcsub-project library was introduced withinplugin-cli. Thebcproject encapsulated PGP signature verification using Bouncy Castle. Thebclibrary is shadowed into a single uber JAR during distribution.JAR shadowing is used to ensure that
plugin-cliuses its own BC libraries rather than any in the ES lib directory.The following PR diff view shows a prior iteration that used a class loader to achieve isolation: https://github.com/elastic/elasticsearch/pull/138949/files/17a688703f62fdaa8cbdee73121465e4c5155e18