diff --git a/buildSrc/src/main/java/org/opensearch/gradle/test/FipsPlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/test/FipsPlugin.java new file mode 100644 index 0000000000000..6ecf04941cc29 --- /dev/null +++ b/buildSrc/src/main/java/org/opensearch/gradle/test/FipsPlugin.java @@ -0,0 +1,153 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.gradle.test; + +import groovy.lang.Closure; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.testing.Test; +import org.opensearch.gradle.testclusters.OpenSearchCluster; +import org.opensearch.gradle.testclusters.OpenSearchNode; +import org.opensearch.gradle.testclusters.TestClustersPlugin; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; + +/** + * This plugin automatically applies FIPS configuration to all test clusters when invoked with: + * ./gradlew integTest -Pcrypto.standard=FIPS-140-3 + *

+ * Features: + *

+ * - Configures test clusters with FIPS-compliant SSL/TLS settings
+ * - Supports BouncyCastle FIPS provider (BCFKS keystore format)
+ * - Works with both internal and external plugins
+ * 
+ */ +public class FipsPlugin implements Plugin { + + private static final String TRUSTSTORE_PASSWORD = "changeit"; + private static final String TRUSTSTORE_FILENAME = "opensearch-fips-truststore.bcfks"; + private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; + private static final String JAVAX_NET_SSL_TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType"; + private static final String JAVAX_NET_SSL_TRUST_STORE_PROVIDER = "javax.net.ssl.trustStoreProvider"; + private static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword"; + + @Override + public void apply(Project project) { + project.getLogger().lifecycle("FIPS mode enabled: Configuring test clusters for FIPS 140-3 compliance"); + File truststoreFile = findTruststoreFile(project); + + if (truststoreFile != null && truststoreFile.exists()) { + project.getPlugins().withType( + TestClustersPlugin.class, plugin -> { + configureTestClusters(project, truststoreFile); + } + ); + + project.getTasks().withType(Test.class).configureEach(testTask -> { + configureTestTask(project, testTask, truststoreFile); + }); + } + } + + private void configureTestClusters(Project project, File truststoreFile) { + project.getExtensions().getByName("testClusters").getClass().getMethods(); + + // Use dynamic approach since testClusters is a NamedDomainObjectContainer + project.afterEvaluate(p -> { + try { + Object testClusters = p.getExtensions().getByName("testClusters"); + Method allMethod = testClusters.getClass().getMethod("all", groovy.lang.Closure.class); + + allMethod.invoke( + testClusters, new Closure(this) { + public void doCall(Object cluster) { + if (cluster instanceof OpenSearchCluster) { + configureSingleCluster(p, (OpenSearchCluster) cluster, truststoreFile); + } + } + } + ); + } catch (Exception e) { + p.getLogger().warn("Failed to configure FIPS for test clusters: {}", e.getMessage()); + } + }); + } + + private void configureSingleCluster(Project project, OpenSearchCluster cluster, File truststoreFile) { + // Set keystore password for the cluster + cluster.keystorePassword(TRUSTSTORE_PASSWORD); + + // Add the truststore file to cluster config + cluster.extraConfigFile(TRUSTSTORE_FILENAME, truststoreFile); + + // Configure SSL/TLS system properties for all nodes in the cluster + for (OpenSearchNode node : cluster.getNodes()) { + node.systemProperty(JAVAX_NET_SSL_TRUST_STORE, "${OPENSEARCH_PATH_CONF}/" + TRUSTSTORE_FILENAME); + node.systemProperty(JAVAX_NET_SSL_TRUST_STORE_TYPE, "BCFKS"); + node.systemProperty(JAVAX_NET_SSL_TRUST_STORE_PROVIDER, "BCFIPS"); + node.systemProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, TRUSTSTORE_PASSWORD); + } + + project.getLogger().info("FIPS: Configured cluster '{}' with truststore from '{}'", cluster.getName(), truststoreFile); + } + + private void configureTestTask(Project project, Test testTask, File truststoreFile) { + testTask.systemProperty(JAVAX_NET_SSL_TRUST_STORE, truststoreFile.getAbsolutePath()); + testTask.systemProperty(JAVAX_NET_SSL_TRUST_STORE_TYPE, "BCFKS"); + testTask.systemProperty(JAVAX_NET_SSL_TRUST_STORE_PROVIDER, "BCFIPS"); + testTask.systemProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, TRUSTSTORE_PASSWORD); + + project.getLogger().info("FIPS: Configured test task '{}' with truststore from '{}'", testTask.getName(), truststoreFile); + } + + /** + * Find the FIPS truststore file from classpath resources (packaged in build-tools.jar) + * or fall back to filesystem locations for development builds. + */ + private File findTruststoreFile(Project project) { + // First, try to extract from classpath (works for external plugins using build-tools.jar) + try { + InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(TRUSTSTORE_FILENAME); + if (resourceStream != null) { + // Extract to build directory + File tempDir = new File(project.getBuildDir(), "fips"); + tempDir.mkdirs(); + File truststoreFile = new File(tempDir, TRUSTSTORE_FILENAME); + + // Only copy if it doesn't exist or is outdated + if (!truststoreFile.exists()) { + java.nio.file.Files.copy(resourceStream, truststoreFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + project.getLogger().debug("FIPS: Extracted truststore from classpath to {}", truststoreFile); + } + resourceStream.close(); + return truststoreFile; + } + } catch (Exception e) { + project.getLogger().debug("FIPS: Could not extract truststore from classpath: " + e.getMessage()); + } + + // Fallback: check filesystem locations (for development builds) + // Location 1: buildSrc/src/main/resources (OpenSearch core development) + File coreLocation = project.getRootProject().file("buildSrc/src/main/resources/" + TRUSTSTORE_FILENAME); + if (coreLocation.exists()) { + return coreLocation; + } + + // Location 2: config/ directory + File configLocation = project.getRootProject().file("config/" + TRUSTSTORE_FILENAME); + if (configLocation.exists()) { + return configLocation; + } + + return null; + } +} diff --git a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/TestClustersPlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/TestClustersPlugin.java index 354c2946889ee..f8f3164aa841e 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/TestClustersPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/TestClustersPlugin.java @@ -36,8 +36,10 @@ import org.opensearch.gradle.ReaperPlugin; import org.opensearch.gradle.ReaperService; import org.opensearch.gradle.info.BuildParams; +import org.opensearch.gradle.info.FipsBuildParams; import org.opensearch.gradle.info.GlobalBuildInfoPlugin; import org.opensearch.gradle.internal.InternalDistributionDownloadPlugin; +import org.opensearch.gradle.test.FipsPlugin; import org.opensearch.gradle.util.GradleUtils; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Plugin; @@ -111,6 +113,11 @@ public void apply(Project project) { // register cluster hooks project.getRootProject().getPluginManager().apply(TestClustersHookPlugin.class); + + // apply FIPS plugin for FIPS 140-3 compliance support + if (FipsBuildParams.isInFipsMode()) { + project.getPluginManager().apply(FipsPlugin.class); + } } private NamedDomainObjectContainer createTestClustersContainerExtension(Project project, ReaperService reaper) {