diff --git a/distribution/tools/plugin-cli/bc/build.gradle b/distribution/tools/plugin-cli/bc/build.gradle
new file mode 100644
index 0000000000000..38b05119aaf5d
--- /dev/null
+++ b/distribution/tools/plugin-cli/bc/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+apply plugin: 'elasticsearch.build'
+apply plugin: 'com.gradleup.shadow'
+
+base {
+ archivesName = 'elasticsearch-plugin-cli-bc'
+}
+
+dependencies {
+ implementation "org.bouncycastle:bcpg-jdk18on:1.83"
+ implementation "org.bouncycastle:bcprov-jdk18on:1.83"
+ implementation "org.bouncycastle:bcutil-jdk18on:1.83"
+}
+
+tasks.named("dependencyLicenses").configure {
+ mapping from: /bc.*/, to: 'bouncycastle'
+}
+
+tasks.named("shadowJar").configure {
+ relocate 'org.bouncycastle', 'shadow.org.bouncycastle'
+}
+
+// Add Lucene to forbiddenApis classpath (needed to parse signature files that reference Lucene classes)
+tasks.named("forbiddenApisMain").configure {
+ classpath += project(":server").sourceSets.main.runtimeClasspath
+}
diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt b/distribution/tools/plugin-cli/bc/licenses/bouncycastle-LICENSE.txt
similarity index 100%
rename from distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt
rename to distribution/tools/plugin-cli/bc/licenses/bouncycastle-LICENSE.txt
diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt b/distribution/tools/plugin-cli/bc/licenses/bouncycastle-NOTICE.txt
similarity index 100%
rename from distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt
rename to distribution/tools/plugin-cli/bc/licenses/bouncycastle-NOTICE.txt
diff --git a/distribution/tools/plugin-cli/bc/src/main/java/org/elasticsearch/plugins/cli/bc/PgpSignatureVerifier.java b/distribution/tools/plugin-cli/bc/src/main/java/org/elasticsearch/plugins/cli/bc/PgpSignatureVerifier.java
new file mode 100644
index 0000000000000..09d950c7a5fa1
--- /dev/null
+++ b/distribution/tools/plugin-cli/bc/src/main/java/org/elasticsearch/plugins/cli/bc/PgpSignatureVerifier.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.plugins.cli.bc;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * A PGP signature verifier that uses Bouncy Castle implementation.
+ *
+ * This implementation was lifted from InstallPluginAction to isolate Bouncy Castle usage.
+ *
+ */
+public class PgpSignatureVerifier {
+
+ /**
+ * @param publicKeyId the public key ID of the signing key that is expected to have signed the official plugin.
+ * @param urlString the URL source of the downloaded plugin ZIP
+ * @param pluginZipInputStream an input stream to the raw bytes of the plugin ZIP
+ * @param ascInputStream an input stream to the signature corresponding to the downloaded plugin zip
+ * @param publicKeyInputStream an input stream to the public key of the signing key.
+ */
+ public static void verifySignature(
+ String publicKeyId,
+ String urlString,
+ InputStream pluginZipInputStream,
+ InputStream ascInputStream,
+ InputStream publicKeyInputStream
+ ) throws IOException {
+
+ try (
+ InputStream fin = pluginZipInputStream;
+ InputStream sin = ascInputStream;
+ InputStream ain = new ArmoredInputStream(publicKeyInputStream) // input stream to the public key in ASCII-Armor format (RFC4880)
+ ) {
+ final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(sin));
+ final PGPSignature signature = ((PGPSignatureList) factory.nextObject()).get(0);
+
+ // validate the signature has key ID matching our public key ID
+ final String keyId = Long.toHexString(signature.getKeyID()).toUpperCase(Locale.ROOT);
+ if (publicKeyId.equals(keyId) == false) {
+ throw new IllegalStateException("key id [" + keyId + "] does not match expected key id [" + publicKeyId + "]");
+ }
+
+ // compute the signature of the downloaded plugin zip
+ computeSignatureForDownloadedPlugin(fin, ain, signature);
+
+ // finally we verify the signature of the downloaded plugin zip matches the expected signature
+ if (signature.verify() == false) {
+ throw new IllegalStateException("signature verification for [" + urlString + "] failed");
+ }
+ } catch (PGPException e) {
+ throw new IOException("PGP exception during signature verification for [" + urlString + "]", e);
+ }
+ }
+
+ private static void computeSignatureForDownloadedPlugin(InputStream fin, InputStream ain, PGPSignature signature) throws PGPException,
+ IOException {
+ final PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(ain, new JcaKeyFingerprintCalculator());
+ final PGPPublicKey key = collection.getPublicKey(signature.getKeyID());
+ signature.init(new JcaPGPContentVerifierBuilderProvider(), key);
+ final byte[] buffer = new byte[1024];
+ int read;
+ while ((read = fin.read(buffer)) != -1) {
+ signature.update(buffer, 0, read);
+ }
+ }
+
+}
diff --git a/distribution/tools/plugin-cli/build.gradle b/distribution/tools/plugin-cli/build.gradle
index 3026ee74e00d0..44b79a7d181f8 100644
--- a/distribution/tools/plugin-cli/build.gradle
+++ b/distribution/tools/plugin-cli/build.gradle
@@ -25,19 +25,18 @@ dependencies {
implementation project(":libs:plugin-api")
implementation project(":libs:plugin-scanner")
implementation project(":libs:entitlement")
+ implementation project(path: "bc", configuration: 'shadow')
// TODO: asm is picked up from the plugin scanner and entitlements, we should consolidate so it is not defined twice
implementation 'org.ow2.asm:asm:9.9'
implementation 'org.ow2.asm:asm-tree:9.9'
- api "org.bouncycastle:bcpg-fips:1.0.7.1"
- api "org.bouncycastle:bc-fips:1.0.2.6"
testImplementation project(":test:framework")
testImplementation "com.google.jimfs:jimfs:${versions.jimfs}"
testRuntimeOnly "com.google.guava:guava:${versions.jimfs_guava}"
-}
-tasks.named("dependencyLicenses").configure {
- mapping from: /bc.*/, to: 'bouncycastle'
+ testImplementation "org.bouncycastle:bcpg-jdk18on:1.83"
+ testImplementation "org.bouncycastle:bcprov-jdk18on:1.83"
+ testImplementation "org.bouncycastle:bcutil-jdk18on:1.83"
}
tasks.named("test").configure {
@@ -51,31 +50,10 @@ tasks.named("test").configure {
}
}
-/*
- * these two classes intentionally use the following JDK internal APIs in order to offer the necessary
- * functionality
- *
- * sun.security.internal.spec.TlsKeyMaterialParameterSpec
- * sun.security.internal.spec.TlsKeyMaterialSpec
- * sun.security.internal.spec.TlsMasterSecretParameterSpec
- * sun.security.internal.spec.TlsPrfParameterSpec
- * sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec
- * sun.security.provider.SecureRandom
- *
- */
-tasks.named("thirdPartyAudit").configure {
- ignoreViolations(
- 'org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$BaseTLSKeyGeneratorSpi',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator$2',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator$2',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSPRFKeyGenerator',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator$2',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator',
- 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2'
- )
+if (buildParams.inFipsJvm) {
+ // Disable tests in FIPS mode due to JAR hell between plugin-cli's
+ // BC dependencies and the BC FIPS dependencies added by the FIPS gradle config
+ // We support running plugin-cli with BC FIPS JARs in ES lib via shadowing.
+ // Running these tests with the JVM in FIPS mode isn't related.
+ tasks.named("test").configure { enabled = false }
}
diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java
index 0aaa19846f6d0..7525adc326eda 100644
--- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java
+++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java
@@ -12,17 +12,6 @@
import org.apache.lucene.search.spell.LevenshteinDistance;
import org.apache.lucene.util.CollectionUtil;
import org.apache.lucene.util.Constants;
-import org.bouncycastle.bcpg.ArmoredInputStream;
-import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.bouncycastle.openpgp.PGPSignatureList;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
-import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
-import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.elasticsearch.Build;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.Terminal;
@@ -42,6 +31,7 @@
import org.elasticsearch.plugins.Platforms;
import org.elasticsearch.plugins.PluginDescriptor;
import org.elasticsearch.plugins.PluginsUtils;
+import org.elasticsearch.plugins.cli.bc.PgpSignatureVerifier;
import org.objectweb.asm.ClassReader;
import java.io.BufferedReader;
@@ -568,12 +558,11 @@ private InputStream urlOpenStream(final URL url) throws IOException {
* @param officialPlugin true if the plugin is an official plugin
* @return the path to the downloaded plugin ZIP
* @throws IOException if an I/O exception occurs download or reading files and resources
- * @throws PGPException if an exception occurs verifying the downloaded ZIP signature
* @throws UserException if checksum validation fails
* @throws URISyntaxException is the url is invalid
*/
private Path downloadAndValidate(final String urlString, final Path tmpDir, final boolean officialPlugin) throws IOException,
- PGPException, UserException, URISyntaxException {
+ UserException, URISyntaxException {
Path zip = downloadZip(urlString, tmpDir);
pathsToDeleteOnShutdown.add(zip);
String checksumUrlString = urlString + ".sha512";
@@ -667,41 +656,10 @@ private Path downloadAndValidate(final String urlString, final Path tmpDir, fina
*
* @param zip the path to the downloaded plugin ZIP
* @param urlString the URL source of the downloaded plugin ZIP
- * @throws IOException if an I/O exception occurs reading from various input streams
- * @throws PGPException if the PGP implementation throws an internal exception during verification
+ * @throws IOException if an I/O exception occurs reading from various input streams or
+ * if the PGP implementation throws an internal exception during verification
*/
- void verifySignature(final Path zip, final String urlString) throws IOException, PGPException {
- final String ascUrlString = urlString + ".asc";
- final URL ascUrl = openUrl(ascUrlString);
- try (
- // fin is a file stream over the downloaded plugin zip whose signature to verify
- InputStream fin = pluginZipInputStream(zip);
- // sin is a URL stream to the signature corresponding to the downloaded plugin zip
- InputStream sin = urlOpenStream(ascUrl);
- // ain is a input stream to the public key in ASCII-Armor format (RFC4880)
- InputStream ain = new ArmoredInputStream(getPublicKey())
- ) {
- final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(sin));
- final PGPSignature signature = ((PGPSignatureList) factory.nextObject()).get(0);
-
- // validate the signature has key ID matching our public key ID
- final String keyId = Long.toHexString(signature.getKeyID()).toUpperCase(Locale.ROOT);
- if (getPublicKeyId().equals(keyId) == false) {
- throw new IllegalStateException("key id [" + keyId + "] does not match expected key id [" + getPublicKeyId() + "]");
- }
-
- // compute the signature of the downloaded plugin zip, wrapped with long execution warning
- timedComputeSignatureForDownloadedPlugin(fin, ain, signature);
-
- // finally we verify the signature of the downloaded plugin zip matches the expected signature
- if (signature.verify() == false) {
- throw new IllegalStateException("signature verification for [" + urlString + "] failed");
- }
- }
- }
-
- private void timedComputeSignatureForDownloadedPlugin(InputStream fin, InputStream ain, PGPSignature signature) throws PGPException,
- IOException {
+ void verifySignature(final Path zip, final String urlString) throws IOException {
final Timer timer = new Timer();
try {
@@ -712,22 +670,16 @@ public void run() {
}
}, acceptableSignatureVerificationDelay());
- computeSignatureForDownloadedPlugin(fin, ain, signature);
+ doVerifySignature(zip, urlString);
} finally {
timer.cancel();
}
}
- // package private for testing
- void computeSignatureForDownloadedPlugin(InputStream fin, InputStream ain, PGPSignature signature) throws PGPException, IOException {
- final PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(ain, new JcaKeyFingerprintCalculator());
- final PGPPublicKey key = collection.getPublicKey(signature.getKeyID());
- signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleFipsProvider()), key);
- final byte[] buffer = new byte[1024];
- int read;
- while ((read = fin.read(buffer)) != -1) {
- signature.update(buffer, 0, read);
- }
+ void doVerifySignature(final Path zip, final String urlString) throws IOException {
+ final String ascUrlString = urlString + ".asc";
+ final URL ascUrl = openUrl(ascUrlString);
+ PgpSignatureVerifier.verifySignature(getPublicKeyId(), urlString, pluginZipInputStream(zip), urlOpenStream(ascUrl), getPublicKey());
}
// package private for testing
diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/cli/InstallPluginActionTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/cli/InstallPluginActionTests.java
index ead08787269c3..bf020e7e6e58f 100644
--- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/cli/InstallPluginActionTests.java
+++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/cli/InstallPluginActionTests.java
@@ -17,7 +17,6 @@
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
@@ -98,6 +97,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -117,9 +117,7 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -480,6 +478,21 @@ public void testSlowSignatureVerificationMessage() throws Exception {
+ ".zip";
final MessageDigest digest = MessageDigest.getInstance("SHA-512");
+ // Control for timeout on waiting for signature verification to complete
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ AtomicBoolean called = new AtomicBoolean(false);
+
+ Runnable callback = () -> {
+ if (called.compareAndSet(false, true)) {
+ try {
+ countDownLatch.await(); // wait until we trip the timer
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
InstallPluginAction action = makeActionPluginThatDownloads(
"analysis-icu",
url,
@@ -488,14 +501,12 @@ public void testSlowSignatureVerificationMessage() throws Exception {
".sha512",
checksumAndFilename(digest, url),
newSecretKey(),
- this::signature
+ this::signature,
+ callback
);
final InstallPluginAction spied = spy(action);
- // Control for timeout on waiting for signature verification to complete
- CountDownLatch countDownLatch = new CountDownLatch(1);
-
doAnswer(i -> {
i.callRealMethod();
countDownLatch.countDown();
@@ -505,12 +516,6 @@ public void testSlowSignatureVerificationMessage() throws Exception {
// Make the slow verification acceptable delay artificially low for testing
doReturn(100L).when(spied).acceptableSignatureVerificationDelay();
- doAnswer(i -> {
- countDownLatch.await(); // wait until we trip the timer
- i.callRealMethod();
- return null;
- }).when(spied).computeSignatureForDownloadedPlugin(any(InputStream.class), any(InputStream.class), any(PGPSignature.class));
-
installPlugin(new InstallablePlugin("analysis-icu", null), env.v1(), spied);
assertThat(terminal.getOutput(), containsString("The plugin installer is trying to verify the signature "));
@@ -522,10 +527,6 @@ public void testSlowSignatureVerificationMessage() throws Exception {
// Divide by two to meet the limitation of the Timer.schedule API.
doReturn(Long.MAX_VALUE / 2).when(spied).acceptableSignatureVerificationDelay();
- // Make sure we don't see any slow error messages when the signature verification is fast
- doCallRealMethod().when(spied)
- .computeSignatureForDownloadedPlugin(any(InputStream.class), any(InputStream.class), any(PGPSignature.class));
-
terminal.reset();
installPlugin(new InstallablePlugin("analysis-icu", null), env.v1(), spied);
@@ -1020,7 +1021,8 @@ void assertInstallPluginFromUrl(
shaExtension,
shaCalculator,
secretKey,
- signature
+ signature,
+ () -> {}
);
installPlugin(new InstallablePlugin(pluginId, pluginUrl), env.v1(), action);
assertPlugin(pluginId, pluginDir, env.v2());
@@ -1035,7 +1037,8 @@ private InstallPluginAction makeActionPluginThatDownloads(
final String shaExtension,
final Function shaCalculator,
final PGPSecretKey secretKey,
- final BiFunction signature
+ final BiFunction signature,
+ final Runnable onReadPluginZip
) throws Exception {
InstallablePlugin pluginZip = createPlugin(pluginId, pluginDir);
Path pluginZipPath = Path.of(URI.create(pluginZip.getLocation()));
@@ -1068,7 +1071,7 @@ URL openUrl(String urlString) throws IOException {
}
@Override
- void verifySignature(Path zip, String urlString) throws IOException, PGPException {
+ void verifySignature(Path zip, String urlString) throws IOException {
if (InstallPluginAction.OFFICIAL_PLUGINS.contains(pluginId)) {
super.verifySignature(zip, urlString);
} else {
@@ -1078,7 +1081,13 @@ void verifySignature(Path zip, String urlString) throws IOException, PGPExceptio
@Override
InputStream pluginZipInputStream(Path zip) throws IOException {
- return new ByteArrayInputStream(Files.readAllBytes(zip));
+ return new ByteArrayInputStream(Files.readAllBytes(zip)) {
+ @Override
+ public int read(byte[] b) throws IOException {
+ onReadPluginZip.run();
+ return super.read(b);
+ }
+ };
}
@Override
@@ -1463,8 +1472,7 @@ public PGPSecretKey newSecretKey() throws NoSuchAlgorithmException, PGPException
null,
null,
new JcaPGPContentSignerBuilder(pkp.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256),
- new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_192, sha1Calc).setProvider(new BouncyCastleFipsProvider())
- .build("passphrase".toCharArray())
+ new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_192, sha1Calc).build("passphrase".toCharArray())
);
}
diff --git a/docs/changelog/138949.yaml b/docs/changelog/138949.yaml
new file mode 100644
index 0000000000000..a0b085dabcdd0
--- /dev/null
+++ b/docs/changelog/138949.yaml
@@ -0,0 +1,5 @@
+pr: 138949
+summary: Shadow plugin-cli JAR to avoid conflicts with BC FIPS classes
+area: Security
+type: enhancement
+issues: []
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 5e3bd1ceedac7..af62ecffd549d 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -3688,6 +3688,11 @@
+
+
+
+
+
@@ -3723,6 +3728,11 @@
+
+
+
+
+
@@ -3738,6 +3748,11 @@
+
+
+
+
+
diff --git a/qa/evil-tests/build.gradle b/qa/evil-tests/build.gradle
index 242fb381e176d..c7fbd56cb3c71 100644
--- a/qa/evil-tests/build.gradle
+++ b/qa/evil-tests/build.gradle
@@ -20,7 +20,6 @@ dependencies {
testImplementation "com.google.jimfs:jimfs:1.3.0"
testImplementation "com.google.guava:guava:${versions.jimfs_guava}"
testImplementation project(":test:framework")
- testImplementation project(':distribution:tools:plugin-cli')
}
// TODO: give each evil test its own fresh JVM for more isolation.
diff --git a/settings.gradle b/settings.gradle
index 82fbc566efb02..22f9b2a098784 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -91,6 +91,7 @@ List projects = [
'distribution:tools:server-cli',
'distribution:tools:windows-service-cli',
'distribution:tools:plugin-cli',
+ 'distribution:tools:plugin-cli:bc',
'distribution:tools:keystore-cli',
'distribution:tools:geoip-cli',
'distribution:tools:ansi-console',