diff --git a/.buildkite/pipelines/periodic.template.yml b/.buildkite/pipelines/periodic.template.yml index 301c45d2d8902..5f42947e194ce 100644 --- a/.buildkite/pipelines/periodic.template.yml +++ b/.buildkite/pipelines/periodic.template.yml @@ -119,6 +119,73 @@ steps: allowed: true permit_on_passed: false reason: "Retry with smart test selection if desired" + - group: java-fips-140-3-matrix + steps: + - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.GRADLE_TASK}} / java-fips-140-3-matrix" + command: .ci/scripts/run-gradle.sh --continue -Dbwc.checkout.align=true -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 $$GRADLE_TASK + timeout_in_minutes: 300 + matrix: + setup: + ES_RUNTIME_JAVA: + - adoptopenjdk17 + GRADLE_TASK: + - checkPart1 + - checkPart2 + - checkPart3 + - checkPart4 + - checkPart5 + - checkPart6 + - checkRestCompat + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: n1-standard-32 + buildDirectory: /dev/shm/bk + env: + ES_RUNTIME_JAVA: "{{matrix.ES_RUNTIME_JAVA}}" + GRADLE_TASK: "{{matrix.GRADLE_TASK}}" + retry: + automatic: + - exit_status: "-1" + limit: 3 + signal_reason: none + - signal_reason: agent_stop + limit: 3 + - exit_status: "1" + limit: 1 + manual: + allowed: true + permit_on_passed: false + reason: "Retry with smart test selection if desired" + - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.BWC_VERSION}} / java-fips-140-3-matrix-bwc" + command: .ci/scripts/run-gradle.sh --continue -Dbwc.checkout.align=true -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 v$$BWC_VERSION#bwcTest + timeout_in_minutes: 300 + matrix: + setup: + ES_RUNTIME_JAVA: + - adoptopenjdk17 + BWC_VERSION: $BWC_LIST + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: n1-standard-32 + buildDirectory: /dev/shm/bk + env: + ES_RUNTIME_JAVA: "{{matrix.ES_RUNTIME_JAVA}}" + BWC_VERSION: "{{matrix.BWC_VERSION}}" + retry: + automatic: + - exit_status: "-1" + limit: 3 + signal_reason: none + - signal_reason: agent_stop + limit: 3 + - exit_status: "1" + limit: 1 + manual: + allowed: true + permit_on_passed: false + reason: "Retry with smart test selection if desired" - group: java-matrix steps: - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.GRADLE_TASK}} / java-matrix" diff --git a/.buildkite/pipelines/periodic.yml b/.buildkite/pipelines/periodic.yml index 8a044ea1dbd36..fa61e3aba79f2 100644 --- a/.buildkite/pipelines/periodic.yml +++ b/.buildkite/pipelines/periodic.yml @@ -918,6 +918,73 @@ steps: allowed: true permit_on_passed: false reason: "Retry with smart test selection if desired" + - group: java-fips-140-3-matrix + steps: + - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.GRADLE_TASK}} / java-fips-140-3-matrix" + command: .ci/scripts/run-gradle.sh --continue -Dbwc.checkout.align=true -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 $$GRADLE_TASK + timeout_in_minutes: 300 + matrix: + setup: + ES_RUNTIME_JAVA: + - adoptopenjdk17 + GRADLE_TASK: + - checkPart1 + - checkPart2 + - checkPart3 + - checkPart4 + - checkPart5 + - checkPart6 + - checkRestCompat + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: n1-standard-32 + buildDirectory: /dev/shm/bk + env: + ES_RUNTIME_JAVA: "{{matrix.ES_RUNTIME_JAVA}}" + GRADLE_TASK: "{{matrix.GRADLE_TASK}}" + retry: + automatic: + - exit_status: "-1" + limit: 3 + signal_reason: none + - signal_reason: agent_stop + limit: 3 + - exit_status: "1" + limit: 1 + manual: + allowed: true + permit_on_passed: false + reason: "Retry with smart test selection if desired" + - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.BWC_VERSION}} / java-fips-140-3-matrix-bwc" + command: .ci/scripts/run-gradle.sh --continue -Dbwc.checkout.align=true -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 v$$BWC_VERSION#bwcTest + timeout_in_minutes: 300 + matrix: + setup: + ES_RUNTIME_JAVA: + - adoptopenjdk17 + BWC_VERSION: ["7.17.30", "8.19.11"] + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: n1-standard-32 + buildDirectory: /dev/shm/bk + env: + ES_RUNTIME_JAVA: "{{matrix.ES_RUNTIME_JAVA}}" + BWC_VERSION: "{{matrix.BWC_VERSION}}" + retry: + automatic: + - exit_status: "-1" + limit: 3 + signal_reason: none + - signal_reason: agent_stop + limit: 3 + - exit_status: "1" + limit: 1 + manual: + allowed: true + permit_on_passed: false + reason: "Retry with smart test selection if desired" - group: java-matrix steps: - label: "{{matrix.ES_RUNTIME_JAVA}} / {{matrix.GRADLE_TASK}} / java-matrix" diff --git a/.buildkite/pipelines/pull-request/part-1-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-1-fips-140-3.yml new file mode 100644 index 0000000000000..87cfa63c101fe --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-1-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-1-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart1 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: custom-32-98304 + buildDirectory: /dev/shm/bk diff --git a/.buildkite/pipelines/pull-request/part-2-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-2-fips-140-3.yml new file mode 100644 index 0000000000000..cbc0e1da03a3a --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-2-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-2-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart2 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: custom-32-98304 + buildDirectory: /dev/shm/bk diff --git a/.buildkite/pipelines/pull-request/part-3-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-3-fips-140-3.yml new file mode 100644 index 0000000000000..f0a5c41bfe7f0 --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-3-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-3-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart3 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: custom-32-98304 + buildDirectory: /dev/shm/bk diff --git a/.buildkite/pipelines/pull-request/part-4-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-4-fips-140-3.yml new file mode 100644 index 0000000000000..ae0e3dc4d97f4 --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-4-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-4-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart4 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: n1-standard-32 + buildDirectory: /dev/shm/bk diff --git a/.buildkite/pipelines/pull-request/part-5-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-5-fips-140-3.yml new file mode 100644 index 0000000000000..696a2a4ca7ec3 --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-5-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-5-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart5 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2404 + machineType: custom-32-98304 + buildDirectory: /dev/shm/bk diff --git a/.buildkite/pipelines/pull-request/part-6-fips-140-3.yml b/.buildkite/pipelines/pull-request/part-6-fips-140-3.yml new file mode 100644 index 0000000000000..eee5050a1d0b3 --- /dev/null +++ b/.buildkite/pipelines/pull-request/part-6-fips-140-3.yml @@ -0,0 +1,12 @@ +config: + allow-labels: + - test-fips +steps: + - label: part-6-fips-140-3 + command: .ci/scripts/run-gradle.sh -Dignore.tests.seed -Dtests.fips.enabled=true -Dtests.fips.mode=140-3 checkPart6 + timeout_in_minutes: 300 + agents: + provider: gcp + image: family/elasticsearch-ubuntu-2004 + machineType: custom-32-98304 + buildDirectory: /dev/shm/bk diff --git a/build-tools-internal/src/main/groovy/elasticsearch.fips.gradle b/build-tools-internal/src/main/groovy/elasticsearch.fips.gradle index 6281e542930c6..266825b8498e6 100644 --- a/build-tools-internal/src/main/groovy/elasticsearch.fips.gradle +++ b/build-tools-internal/src/main/groovy/elasticsearch.fips.gradle @@ -23,15 +23,40 @@ if (buildParams.inFipsJvm) { String javaSecurityFilename = buildParams.runtimeJavaDetails.get().toLowerCase().contains('oracle') ? 'fips_java_oracle.security' : 'fips_java.security' File fipsResourcesDir = new File(project.buildDir, 'fips-resources') File fipsSecurity = new File(fipsResourcesDir, javaSecurityFilename) - File fipsPolicy = new File(fipsResourcesDir, 'fips_java.policy') File fipsTrustStore = new File(fipsResourcesDir, 'cacerts.bcfks') - def bcFips = dependencies.create('org.bouncycastle:bc-fips:1.0.2.6') - def bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.19') + + def bcFips + def bcTlsFips + def bcUtilFips def manualDebug = false; //change this to manually debug bouncy castle in an IDE - if(manualDebug) { - bcFips = dependencies.create('org.bouncycastle:bc-fips-debug:1.0.2.6') - bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.19'){ - exclude group: 'org.bouncycastle', module: 'bc-fips' // to avoid jar hell + def isFips140_3 = buildParams.fipsMode == '140-3' + String javaPolicyFilename = isFips140_3 ? 'fips_java_bc2.policy' : 'fips_java.policy' + File fipsPolicy = new File(fipsResourcesDir, javaPolicyFilename) + + if (isFips140_3) { + // FIPS 140-3 certified BouncyCastle libraries + bcFips = dependencies.create('org.bouncycastle:bc-fips:2.0.1') + bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:2.0.22') + bcUtilFips = dependencies.create('org.bouncycastle:bcutil-fips:2.0.5') + if (manualDebug) { + bcFips = dependencies.create('org.bouncycastle:bc-fips-debug:2.0.1') + bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:2.0.22'){ + exclude group: 'org.bouncycastle', module: 'bcutil-fips' // to avoid jar hell + } + bcUtilFips = dependencies.create('org.bouncycastle:bcutil-fips:2.0.5'){ + exclude group: 'org.bouncycastle', module: 'bc-fips' // to avoid jar hell + } + } + } else { + // FIPS 140-2 certified BouncyCastle libraries (default) + bcFips = dependencies.create('org.bouncycastle:bc-fips:1.0.2.6') + bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.19') + bcUtilFips = null + if (manualDebug) { + bcFips = dependencies.create('org.bouncycastle:bc-fips-debug:1.0.2.6') + bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.19'){ + exclude group: 'org.bouncycastle', module: 'bc-fips' // to avoid jar hell + } } } pluginManager.withPlugin('java-base') { @@ -39,7 +64,7 @@ if (buildParams.inFipsJvm) { fipsResourcesTask.configure { outputDir = fipsResourcesDir copy javaSecurityFilename - copy 'fips_java.policy' + copy javaPolicyFilename copy 'cacerts.bcfks' } @@ -47,6 +72,9 @@ if (buildParams.inFipsJvm) { withDependencies { add(bcFips) add(bcTlsFips) + if (bcUtilFips != null) { + add(bcUtilFips) + } } } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java index dcf34a1e8af56..e0d3fba046578 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java @@ -27,8 +27,8 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import javax.inject.Inject; @@ -43,7 +43,8 @@ public class ExportElasticsearchBuildResourcesTask extends DefaultTask { private static final Logger logger = Logging.getLogger(ExportElasticsearchBuildResourcesTask.class); - private final Set resources = new HashSet<>(); + // Maps resource path -> destination filename + private final Map resources = new HashMap<>(); private DirectoryProperty outputDir; @@ -58,8 +59,8 @@ public DirectoryProperty getOutputDir() { } @Input - public Set getResources() { - return Collections.unmodifiableSet(resources); + public Map getResources() { + return Collections.unmodifiableMap(resources); } @Classpath @@ -73,13 +74,23 @@ public void setOutputDir(File outputDir) { this.outputDir.set(outputDir); } + /** + * Copy a resource to the output directory, keeping the original filename. + */ public void copy(String resource) { + copy(resource, resource); + } + + /** + * Copy a resource to the output directory with a different filename. + */ + public void copy(String resource, String destName) { if (getState().getExecuted() || getState().getExecuting()) { throw new GradleException( "buildResources can't be configured after the task ran. " + "Make sure task is not used after configuration time" ); } - resources.add(resource); + resources.put(resource, destName); } @TaskAction @@ -88,8 +99,10 @@ public void doExport() { setDidWork(false); throw new StopExecutionException(); } - resources.stream().parallel().forEach(resourcePath -> { - Path destination = outputDir.get().file(resourcePath).getAsFile().toPath(); + resources.entrySet().stream().parallel().forEach(entry -> { + String resourcePath = entry.getKey(); + String destName = entry.getValue(); + Path destination = outputDir.get().file(destName).getAsFile().toPath(); try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) { Files.createDirectories(destination.getParent()); if (is == null) { diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/BuildParameterExtension.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/BuildParameterExtension.java index cac13eac83fe6..2c5adca0c089c 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/BuildParameterExtension.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/BuildParameterExtension.java @@ -71,4 +71,6 @@ public interface BuildParameterExtension { Provider getRandom(); Boolean getGraalVmRuntime(); + + String getFipsMode(); } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/DefaultBuildParameterExtension.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/DefaultBuildParameterExtension.java index afa3ed26cfb9f..2532f4f21eea6 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/DefaultBuildParameterExtension.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/DefaultBuildParameterExtension.java @@ -27,6 +27,7 @@ public abstract class DefaultBuildParameterExtension implements BuildParameterExtension { private final Provider inFipsJvm; + private final Provider fipsMode; private final Provider runtimeJavaHome; private final RuntimeJava runtimeJava; private final List javaVersions; @@ -65,6 +66,7 @@ public DefaultBuildParameterExtension( Provider bwcVersions ) { this.inFipsJvm = providers.systemProperty("tests.fips.enabled").map(DefaultBuildParameterExtension::parseBoolean); + this.fipsMode = providers.systemProperty("tests.fips.mode"); this.runtimeJava = runtimeJava; this.runtimeJavaHome = cache(providers, runtimeJava.getJavahome()); this.javaToolChainSpec = cache(providers, javaToolChainSpec); @@ -101,6 +103,11 @@ public boolean getInFipsJvm() { return inFipsJvm.getOrElse(false); } + @Override + public String getFipsMode() { + return fipsMode.getOrNull(); + } + @Override public Provider getRuntimeJavaHome() { return runtimeJavaHome; diff --git a/build-tools-internal/src/main/resources/fips_java_bc2.policy b/build-tools-internal/src/main/resources/fips_java_bc2.policy new file mode 100644 index 0000000000000..e3be0e2343388 --- /dev/null +++ b/build-tools-internal/src/main/resources/fips_java_bc2.policy @@ -0,0 +1,28 @@ +grant { + permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; + permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; + permission java.security.SecurityPermission "getProperty.keystore.type.compat"; + permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters"; + permission java.security.SecurityPermission "getProperty.org.bouncycastle.ec.max_f2m_field_size"; + permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; + //io.netty.handler.codec.DecoderException + permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; + //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission java.util.PropertyPermission "intellij.debug.agent", "read"; + permission java.util.PropertyPermission "intellij.debug.agent", "write"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; + permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; +}; + +// rely on the caller's socket permissions, the JSSE TLS implementation here is always allowed to connect +// FIPS 140-3: bctls-fips 2.0.22 +grant codeBase "file:${jdk.module.path}/bctls-fips-2.0.22.jar" { + permission java.net.SocketPermission "*", "connect"; +}; + diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index 316888bdaffdc..3e89679d0d115 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -24,6 +24,10 @@ apply plugin: 'elasticsearch.repositories' String buildId = providers.systemProperty('build.id').getOrNull() boolean useLocalArtifacts = buildId != null && buildId.isBlank() == false && useDra == false +// FIPS version for Docker builds: '140-2' (default) or '140-3' +// Use -Ddocker.fips.version=140-3 to build with FIPS 140-3 / BC 2.x libraries +ext.isFips140_3 = providers.systemProperty('docker.fips.version').getOrElse('140-2') == '140-3' + repositories { // Define a repository that allows Gradle to fetch a resource from GitHub. This // is only used to fetch the `tini` binary, when building the Iron Bank docker image @@ -105,6 +109,7 @@ configurations { metricbeat_x86_64 metricbeat_fips_x86_64 fips + fips_140_3 } String tiniArch = Architecture.current() == Architecture.AARCH64 ? 'arm64' : 'amd64' @@ -129,8 +134,14 @@ dependencies { metricbeat_fips_aarch64 "beats:metricbeat-fips:${VersionProperties.elasticsearch}:linux-arm64@tar.gz" metricbeat_fips_x86_64 "beats:metricbeat-fips:${VersionProperties.elasticsearch}:linux-x86_64@tar.gz" + // FIPS 140-2 libraries (default) fips "org.bouncycastle:bc-fips:1.0.2.6" fips "org.bouncycastle:bctls-fips:1.0.19" + + // FIPS 140-3 libraries + fips_140_3 "org.bouncycastle:bc-fips:2.0.1" + fips_140_3 "org.bouncycastle:bctls-fips:2.0.22" + fips_140_3 "org.bouncycastle:bcutil-fips:2.0.5" } ext.expansions = { Architecture architecture, DockerBase base -> @@ -143,6 +154,9 @@ ext.expansions = { Architecture architecture, DockerBase base -> // build time to midnight so that the Docker build cache is usable. def buildDate = buildParams.ci ? buildParams.buildDate : buildParams.buildDate.truncatedTo(ChronoUnit.DAYS).toString() + // BC-FIPS version depends on FIPS version (140-2 vs 140-3) + def bcFipsVersion = isFips140_3 ? '2.0.1' : '1.0.2.6' + return [ 'arch' : architecture.classifier, 'base_image' : base.image, @@ -155,7 +169,8 @@ ext.expansions = { Architecture architecture, DockerBase base -> 'docker_base' : base.name().toLowerCase(), 'version' : VersionProperties.elasticsearch, 'major_minor_version': "${major}.${minor}", - 'retry' : ShellRetry + 'retry' : ShellRetry, + 'bc_fips_version' : bcFipsVersion ] } @@ -333,7 +348,7 @@ void addBuildDockerContextTask(Architecture architecture, DockerBase base) { into("fips") { into("libs") { - from configurations.fips + from(isFips140_3 ? configurations.fips_140_3 : configurations.fips) } into("resources") { from tasks.named('fipsDockerResources') @@ -558,11 +573,13 @@ void addBuildCloudDockerImageTasks(Architecture architecture) { } // fips +String fipsPolicySourceFilename = isFips140_3 ? 'fips_java_bc2.policy' : 'fips_java.policy' + TaskProvider fipsResourcesTask = tasks.register('fipsDockerResources', ExportElasticsearchBuildResourcesTask) fipsResourcesTask.configure { outputDir = project.layout.buildDirectory.dir('fips-docker-resources').get().asFile copy 'fips_java.security' - copy 'fips_java.policy' + copy fipsPolicySourceFilename, 'fips_java.policy' } for (final Architecture architecture : Architecture.values()) { diff --git a/distribution/docker/src/docker/dockerfiles/cloud_ess_fips/Dockerfile b/distribution/docker/src/docker/dockerfiles/cloud_ess_fips/Dockerfile index 0451499559982..f2bbfd90f2bc8 100644 --- a/distribution/docker/src/docker/dockerfiles/cloud_ess_fips/Dockerfile +++ b/distribution/docker/src/docker/dockerfiles/cloud_ess_fips/Dockerfile @@ -163,7 +163,7 @@ RUN printf "\\n" | jdk/bin/keytool -importkeystore \ -deststorepass passwordcacert \ -deststoretype BCFKS \ -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ - -providerpath lib/bc-fips-1.0.2.6.jar \ + -providerpath lib/bc-fips-${bc_fips_version}.jar \ -destprovidername BCFIPS 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 80de3430eb79b..42452ae4caa20 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3498,11 +3498,21 @@ + + + + + + + + + + @@ -3513,6 +3523,11 @@ + + + + + @@ -3548,11 +3563,26 @@ + + + + + + + + + + + + + + + @@ -3563,6 +3593,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 fae2846d34547..23fc5e1e540f3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -92,6 +92,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', diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 6369b528ad444..15389e4135982 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -42,6 +42,7 @@ import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Setting; @@ -1861,9 +1862,9 @@ static void validateForFips(Settings settings) { } }); - Set foundProviders = new HashSet<>(); + Set foundProviders = new HashSet<>(); for (Provider provider : java.security.Security.getProviders()) { - foundProviders.add(provider.getName().toLowerCase(Locale.ROOT)); + foundProviders.add(new SecurityProvider(provider.getName().toLowerCase(Locale.ROOT), provider.getVersionStr())); if (logger.isTraceEnabled()) { logger.trace("Security Provider: " + provider.getName() + ", Version: " + provider.getVersionStr()); provider.entrySet().forEach(entry -> { logger.trace("\t" + entry.getKey()); }); @@ -1875,7 +1876,7 @@ static void validateForFips(Settings settings) { if (requiredProviders != null && requiredProviders.isEmpty() == false) { List unsatisfiedProviders = requiredProviders.stream() .map(s -> s.toLowerCase(Locale.ROOT)) - .filter(element -> foundProviders.contains(element) == false) + .filter(element -> foundProviders.stream().noneMatch(prov -> prov.test(element))) .toList(); if (unsatisfiedProviders.isEmpty() == false) { @@ -1896,6 +1897,19 @@ static void validateForFips(Settings settings) { } } + record SecurityProvider(String name, String version) { + boolean test(String secProvPattern) { + int i = secProvPattern.indexOf(':'); + if (i < 0) { + return name.equals(secProvPattern); + } else { + String provName = secProvPattern.substring(0, i); + String provVersion = secProvPattern.substring(i + 1); + return name.equals(provName) && Regex.simpleMatch(provVersion, version); + } + } + } + @Override public List getTransportInterceptors(NamedWriteableRegistry namedWriteableRegistry, ThreadContext threadContext) { if (enabled == false) { // don't register anything if we are not enabled diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 3ff8f16165547..14a3f548d0019 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -764,6 +764,17 @@ public void testValidateForFipsNoErrorsOrLogsForDefaultSettings() throws Illegal assertThatLogger(() -> Security.validateForFips(settings), Security.class); } + public void testSecurityProvider() { + assertTrue(new Security.SecurityProvider("bcfips", "2.0").test("bcfips")); + assertTrue(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:2.0")); + assertTrue(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:2*")); + assertTrue(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:*")); + assertFalse(new Security.SecurityProvider("bcfips", "2.0").test("sun")); + assertFalse(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:")); + assertFalse(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:1.0")); + assertFalse(new Security.SecurityProvider("bcfips", "2.0").test("bcfips:1*")); + } + public void testLicenseUpdateFailureHandlerUpdate() throws Exception { final Path kerbKeyTab = createTempFile("es", "keytab"); Files.write(kerbKeyTab, new byte[0]);