Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2df9885
Add build-tooling to run in the FIPS environment
iigonin Aug 5, 2025
5df7ee7
replace java.util with lfs4j logger; use AccessController to read cac…
iigonin Aug 6, 2025
b08999b
replace fallback mechanism with cluster-setting to determine the stra…
iigonin Aug 14, 2025
69d46e3
cleanup changelog.md
iigonin Aug 14, 2025
8cf332c
add default enum value; extend MultiProviderTrustStoreHandler test-class
iigonin Aug 15, 2025
738091b
Merge branch 'main' into HEAD
iigonin Aug 15, 2025
630e804
fix :server:forbiddenApisTest
iigonin Aug 15, 2025
60728d6
configure docker and testClusters builds to use generated truststore
iigonin Aug 19, 2025
a9a1551
Merge branch 'main' into fips_build_tooling2
iigonin Aug 19, 2025
814af0a
Merge branch 'main' into HEAD
iigonin Aug 22, 2025
0bbdf6e
add more unit tests
iigonin Aug 22, 2025
6014c2c
replace MultiProviderTrustStoreHandler with demo script
iigonin Sep 22, 2025
9f3c9c2
Merge branch 'main' into fips_build_tooling2
iigonin Oct 9, 2025
fb177e3
fix CHANGELOG; update CLI's build.gradle; write additional docu
iigonin Oct 10, 2025
32189ea
add more tests
iigonin Oct 12, 2025
b87e926
remove '--enable-native-access=ALL-UNNAMED'
iigonin Oct 12, 2025
d4bac69
Merge branch 'main' into fips_build_tooling2
iigonin Oct 13, 2025
a7fc277
apply BouncyCastleThreadFilter
iigonin Oct 14, 2025
e282c03
Merge branch 'main' into fips_build_tooling2
iigonin Oct 14, 2025
31aaff3
replace BuildParams.inFipsJvm with getter
iigonin Oct 14, 2025
a5b935f
make use of 'testFipsRuntimeOnly' inside StandaloneRestTestPlugin; ov…
iigonin Oct 15, 2025
4368d76
use BCFIPS_RNG; make java.util.Scanner test-friendly; write additiona…
iigonin Oct 16, 2025
88c630c
get SecureRandom from server-module
iigonin Oct 20, 2025
f826d03
Merge branch 'main' into fips_build_tooling2
iigonin Oct 20, 2025
7cfeb72
add '--password option' to CLI
iigonin Oct 21, 2025
ea101e1
increase tests coverage for FipsTrustStoreValidator
iigonin Oct 22, 2025
2adc90f
Merge branch 'main' into fips_build_tooling2
iigonin Oct 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add pluggable gRPC interceptors with explicit ordering([#19005](https://github.com/opensearch-project/OpenSearch/pull/19005))
- Add metrics for the merged segment warmer feature ([#18929](https://github.com/opensearch-project/OpenSearch/pull/18929))
- Add pointer based lag metric in pull-based ingestion ([#19635](https://github.com/opensearch-project/OpenSearch/pull/19635))
- Add build-tooling to run in FIPS environment ([#18921](https://github.com/opensearch-project/OpenSearch/pull/18921))

### Changed
- Faster `terms` query creation for `keyword` field with index and docValues enabled ([#19350](https://github.com/opensearch-project/OpenSearch/pull/19350))
Expand Down
21 changes: 11 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ apply from: 'gradle/run.gradle'
apply from: 'gradle/missing-javadoc.gradle'
apply from: 'gradle/code-coverage.gradle'

// Apply FIPS configuration to all projects
allprojects {
apply from: "$rootDir/gradle/fips.gradle"
}

// common maven publishing configuration
allprojects {
group = 'org.opensearch'
Expand Down Expand Up @@ -421,8 +426,12 @@ gradle.projectsEvaluated {
dependsOn(project(':libs:agent-sm:agent').prepareAgent)
jvmArgs += ["-javaagent:" + project(':libs:agent-sm:agent').jar.archiveFile.get()]
}
if (BuildParams.inFipsJvm) {
task.jvmArgs += ["-Dorg.bouncycastle.fips.approved_only=true"]
if (BuildParams.isInFipsJvm()) {
def fipsSecurityFile = project.rootProject.file('distribution/src/config/fips_java.security')
task.jvmArgs += [
"-Dorg.bouncycastle.fips.approved_only=true",
"-Djava.security.properties=${fipsSecurityFile}"
]
}
}
}
Expand Down Expand Up @@ -693,14 +702,6 @@ allprojects {
plugins.withId('lifecycle-base') {
checkPart1.configure { dependsOn 'check' }
}

plugins.withId('opensearch.testclusters') {
testClusters.configureEach {
if (BuildParams.inFipsJvm) {
keystorePassword 'notarealpasswordphrase'
}
}
}
}

subprojects {
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ if (project != rootProject) {
disableTasks('forbiddenApisMain', 'forbiddenApisTest', 'forbiddenApisIntegTest', 'forbiddenApisTestFixtures')
jarHell.enabled = false
thirdPartyAudit.enabled = false
if (org.opensearch.gradle.info.BuildParams.inFipsJvm) {
if (org.opensearch.gradle.info.BuildParams.isInFipsJvm()) {
// We don't support running gradle with a JVM that is in FIPS 140 mode, so we don't test it.
// WaitForHttpResourceTests tests would fail as they use JKS/PKCS12 keystores
test.enabled = false
Expand Down Expand Up @@ -255,7 +255,7 @@ if (project != rootProject) {
tasks.register("integTest", Test) {
inputs.dir(file("src/testKit")).withPropertyName("testkit dir").withPathSensitivity(PathSensitivity.RELATIVE)
systemProperty 'test.version_under_test', version
onlyIf { org.opensearch.gradle.info.BuildParams.inFipsJvm == false }
onlyIf { org.opensearch.gradle.info.BuildParams.isInFipsJvm() == false }
maxParallelForks = System.getProperty('tests.jvms', org.opensearch.gradle.info.BuildParams.defaultParallel.toString()) as Integer
testClassesDirs = sourceSets.integTest.output.classesDirs
classpath = sourceSets.integTest.runtimeClasspath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ class ClusterFormationTasks {
start.doLast(opensearchRunner)
start.doFirst {
// If the node runs in a FIPS 140-2 JVM, the BCFKS default keystore will be password protected
if (BuildParams.inFipsJvm) {
if (BuildParams.isInFipsJvm()) {
node.config.systemProperties.put('javax.net.ssl.trustStorePassword', 'password')
node.config.systemProperties.put('javax.net.ssl.keyStorePassword', 'password')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
package org.opensearch.gradle.test

import groovy.transform.CompileStatic
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.opensearch.gradle.OpenSearchJavaPlugin
import org.opensearch.gradle.ExportOpenSearchBuildResourcesTask
import org.opensearch.gradle.RepositoriesSetupPlugin
Expand Down Expand Up @@ -92,6 +94,10 @@ class StandaloneRestTestPlugin implements Plugin<Project> {
// create a compileOnly configuration as others might expect it
project.configurations.create("compileOnly")
project.dependencies.add('testImplementation', project.project(':test:framework'))
if (BuildParams.isInFipsJvm()) {
VersionCatalog libs = project.extensions.getByType(VersionCatalogsExtension).named("libs")
project.dependencies.add('testFipsRuntimeOnly', libs.findBundle("bouncycastle").get())
}

EclipseModel eclipse = project.extensions.getByType(EclipseModel)
eclipse.classpath.sourceSets = [testSourceSet]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ static void setupDependencies(Project project, SourceSet sourceSet) {
);
}

if (BuildParams.isInFipsJvm()) {
project.getDependencies()
.add(
sourceSet.getImplementationConfigurationName(),
"org.bouncycastle:bc-fips:" + VersionProperties.getVersions().get("bouncycastle_jce")
);
project.getDependencies()
.add(
sourceSet.getImplementationConfigurationName(),
"org.bouncycastle:bctls-fips:" + VersionProperties.getVersions().get("bouncycastle_tls")
);
}
}

}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.client;

import com.carrotsearch.randomizedtesting.ThreadFilter;

/**
* ThreadFilter to exclude ThreadLeak checks for BC’s global background threads
*
* <p>clone from the original, which is located in ':test:framework'</p>
*/
public class BouncyCastleThreadFilter implements ThreadFilter {
@Override
public boolean reject(Thread t) {
String n = t.getName();
// Ignore BC’s global background threads
return "BC Disposal Daemon".equals(n) || "BC Cleanup Executor".equals(n);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.carrotsearch.randomizedtesting.annotations.SeedDecorators;
import com.carrotsearch.randomizedtesting.annotations.TestMethodProviders;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakAction;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakGroup;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
Expand Down Expand Up @@ -65,6 +66,7 @@
@ThreadLeakAction({ ThreadLeakAction.Action.WARN, ThreadLeakAction.Action.INTERRUPT })
@ThreadLeakZombies(ThreadLeakZombies.Consequence.IGNORE_REMAINING_TESTS)
@ThreadLeakLingering(linger = 5000) // 5 sec lingering
@ThreadLeakFilters(filters = BouncyCastleThreadFilter.class)
@TimeoutSuite(millis = 2 * 60 * 60 * 1000)
public abstract class RestClientTestCase extends RandomizedTest {

Expand Down
9 changes: 7 additions & 2 deletions distribution/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
* Properties to expand when copying packaging files *
*****************************************************************************/
configurations {
['libs', 'libsPluginCli', 'libsKeystoreCli', 'bcFips'].each {
['libs', 'libsPluginCli', 'libsKeystoreCli', 'libsFipsInstallerCli', 'bcFips'].each {
create(it) {
canBeConsumed = false
canBeResolved = true
Expand All @@ -333,6 +333,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {

libsPluginCli project(':distribution:tools:plugin-cli')
libsKeystoreCli project(path: ':distribution:tools:keystore-cli')
libsFipsInstallerCli project(path: ':distribution:tools:fips-demo-installer-cli')

bcFips libs.bundles.bouncycastle
}
Expand All @@ -346,7 +347,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
copySpec {
// delay by using closures, since they have not yet been configured, so no jar task exists yet
from(configurations.libs)
if ( BuildParams.inFipsJvm ) {
if ( BuildParams.isInFipsJvm() ) {
from(configurations.bcFips)
}
into('tools/plugin-cli') {
Expand All @@ -355,6 +356,10 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
into('tools/keystore-cli') {
from(configurations.libsKeystoreCli)
}
// Add FIPS installer CLI
into('tools/fips-demo-installer-cli') {
from(configurations.libsFipsInstallerCli)
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions distribution/docker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,11 @@ subprojects { Project subProject ->
tasks.named("composeUp").configure {
dependsOn preProcessFixture
}

dockerCompose {
useComposeFiles = ['docker-compose.yml']
if (BuildParams.isInFipsJvm()) {
environment.put("KEYSTORE_PASSWORD", "notarealpasswordphrase")
environment.put("FIPS_GENERATE_TRUSTSTORE", "true")
}
}
4 changes: 4 additions & 0 deletions distribution/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ services:
- cluster.routing.allocation.disk.watermark.high=1b
- cluster.routing.allocation.disk.watermark.flood_stage=1b
- node.store.allow_mmap=false
- "KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD}"
Copy link
Member

Choose a reason for hiding this comment

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

outside of running in fips compliance mode, these will be blank?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that's the purpose. Docker warns you that this parameter is not set when running w/o FIPS. E.g.

WARN[0000] The "FIPS_GENERATE_TRUSTSTORE" variable is not set. Defaulting to a blank string.

Inside docker-entrypoint the check ensures the value is true otherwise no action is taken.

- "FIPS_GENERATE_TRUSTSTORE=${FIPS_GENERATE_TRUSTSTORE}"
volumes:
- ./build/repo:/tmp/opensearch-repo
- ./build/logs/1:/usr/share/opensearch/logs
Expand All @@ -40,6 +42,8 @@ services:
- cluster.routing.allocation.disk.watermark.high=1b
- cluster.routing.allocation.disk.watermark.flood_stage=1b
- node.store.allow_mmap=false
- "KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD}"
- "FIPS_GENERATE_TRUSTSTORE=${FIPS_GENERATE_TRUSTSTORE}"
volumes:
- ./build/repo:/tmp/opensearch-repo
- ./build/logs/2:/usr/share/opensearch/logs
Expand Down
11 changes: 8 additions & 3 deletions distribution/docker/src/docker/bin/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,24 @@ if [[ -f bin/opensearch-users ]]; then
fi

if ls "/usr/share/opensearch/lib" | grep -E -q "bc-fips.*\.jar"; then
# If BouncyCastle FIPS is detected - enforcing keystore password policy.

# If BouncyCastle FIPS is detected - configure FIPS trust store in test mode
if [[ "$FIPS_GENERATE_TRUSTSTORE" == "true" ]]; then
(run_as_other_user_if_needed opensearch-fips-demo-installer --non-interactive)
fi

# If BouncyCastle FIPS is detected - enforce keystore password policy.
if [[ -z "$KEYSTORE_PASSWORD" ]]; then
echo "[ERROR] FIPS mode requires a keystore password. KEYSTORE_PASSWORD is not set." >&2
exit 1
fi

if [[ ! -f /usr/share/opensearch/config/opensearch.keystore ]]; then
# Keystore not found - creating with password.
# Keystore not found - create with password.
COMMANDS="$(printf "%s\n%s" "$KEYSTORE_PASSWORD" "$KEYSTORE_PASSWORD")"
echo "$COMMANDS" | run_as_other_user_if_needed opensearch-keystore create -p
else
# Keystore already exists - checking encryption.
# Keystore already exists - check encryption.
if ! run_as_other_user_if_needed opensearch-keystore has-passwd --silent; then
# Keystore is unencrypted - securing it for FIPS mode.
COMMANDS="$(printf "%s\n%s" "$KEYSTORE_PASSWORD" "$KEYSTORE_PASSWORD")"
Expand Down
8 changes: 8 additions & 0 deletions distribution/src/bin/opensearch-fips-demo-installer
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

set -e -o pipefail

OPENSEARCH_MAIN_CLASS=org.opensearch.tools.cli.fips.truststore.FipsTrustStoreCommand \
OPENSEARCH_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/fips-demo-installer-cli \
"`dirname "$0"`"/opensearch-cli \
"$@"
16 changes: 16 additions & 0 deletions distribution/src/bin/opensearch-fips-demo-installer.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@echo off

setlocal enabledelayedexpansion
setlocal enableextensions

set OPENSEARCH_MAIN_CLASS=org.opensearch.tools.cli.fips.truststore.FipsTrustStoreCommand
set OPENSEARCH_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/fips-demo-installer-cli

call "%~dp0opensearch-cli.bat" ^
%%* ^
|| goto exit

endlocal
endlocal
:exit
exit /b %ERRORLEVEL%
36 changes: 36 additions & 0 deletions distribution/tools/fips-demo-installer-cli/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.
*/

import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis

apply plugin: 'opensearch.build'

dependencies {
api project(":libs:opensearch-cli")
api project(":server")
api project(':distribution:tools:java-version-checker')
api "info.picocli:picocli:${versions.picocli}"
api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}"

testImplementation project(":test:framework")
}

configurations.runtimeClasspath {
exclude group: 'org.apache.logging.log4j'
}

tasks.withType(CheckForbiddenApis).configureEach {
replaceSignatureFiles 'jdk-signatures'
}

tasks.named("dependencyLicenses").configure {
mapping from: /bc.*/, to: 'bouncycastle'
}

tasks.named("missingJavadoc").configure { it.enabled = false }
tasks.named("loggerUsageCheck").configure { it.enabled = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
34c72d0367d41672883283933ebec24843570bf5
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
82bcae3dc45ddeb08b4954e2f596772a42219715
Loading
Loading