diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 0952f0057b..cb257d12ec 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -357,6 +357,24 @@ private boolean sslCertificatesHotReloadEnabled(final Settings settings) { return settings.getAsBoolean(SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED, false); } + static void validateFipsMode(final String fipsModeEnvValue, final Settings settings) { + if ("true".equalsIgnoreCase(fipsModeEnvValue)) { + String hashingAlgorithm = settings.get( + ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, + ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM_DEFAULT + ); + if (!ConfigConstants.PBKDF2.equalsIgnoreCase(hashingAlgorithm)) { + throw new IllegalStateException( + "FIPS mode is enabled (OPENSEARCH_FIPS_MODE=true) but password hashing algorithm is set to '" + + hashingAlgorithm + + "'. Only PBKDF2 is allowed in FIPS mode. Set '" + + ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM + + "' to 'pbkdf2'. Note: changing the hashing algorithm requires all existing passwords to be rehashed." + ); + } + } + } + public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) { super(settings, configPath, isDisabled(settings)); @@ -489,6 +507,8 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) ); } + validateFipsMode(System.getenv("OPENSEARCH_FIPS_MODE"), settings); + if (!client && !settings.getAsBoolean(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false)) { // check for demo certificates final List files = AccessController.doPrivileged(() -> { diff --git a/src/test/java/org/opensearch/security/OpenSearchSecurityPluginFIPSValidationTest.java b/src/test/java/org/opensearch/security/OpenSearchSecurityPluginFIPSValidationTest.java new file mode 100644 index 0000000000..8e8e93877b --- /dev/null +++ b/src/test/java/org/opensearch/security/OpenSearchSecurityPluginFIPSValidationTest.java @@ -0,0 +1,93 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security; + +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.support.ConfigConstants; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThrows; + +public class OpenSearchSecurityPluginFIPSValidationTest { + + @Test + public void testFipsModeWithDefaultAlgorithmThrows() { + // Default algorithm is bcrypt, which is not FIPS-compliant + Settings settings = Settings.builder().build(); + + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> OpenSearchSecurityPlugin.validateFipsMode("true", settings) + ); + assertThat(ex.getMessage(), containsString("FIPS mode is enabled")); + assertThat(ex.getMessage(), containsString("Only PBKDF2 is allowed in FIPS mode")); + assertThat(ex.getMessage(), containsString("changing the hashing algorithm requires all existing passwords to be rehashed")); + } + + @Test + public void testFipsModeWithBcryptThrows() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "bcrypt").build(); + + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> OpenSearchSecurityPlugin.validateFipsMode("true", settings) + ); + assertThat(ex.getMessage(), containsString("bcrypt")); + assertThat(ex.getMessage(), containsString("FIPS mode is enabled")); + } + + @Test + public void testFipsModeWithArgon2Throws() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "argon2").build(); + + IllegalStateException ex = assertThrows( + IllegalStateException.class, + () -> OpenSearchSecurityPlugin.validateFipsMode("true", settings) + ); + assertThat(ex.getMessage(), containsString("argon2")); + } + + @Test + public void testFipsModeWithPbkdf2Succeeds() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "pbkdf2").build(); + + // Should not throw + OpenSearchSecurityPlugin.validateFipsMode("true", settings); + } + + @Test + public void testFipsModeWithPbkdf2UpperCaseSucceeds() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "PBKDF2").build(); + + // Should not throw + OpenSearchSecurityPlugin.validateFipsMode("true", settings); + } + + @Test + public void testFipsModeDisabledAllowsAnyAlgorithm() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "bcrypt").build(); + + // Should not throw when FIPS mode is not enabled + OpenSearchSecurityPlugin.validateFipsMode("false", settings); + } + + @Test + public void testFipsModeNullEnvAllowsAnyAlgorithm() { + Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "bcrypt").build(); + + // Should not throw when env var is null + OpenSearchSecurityPlugin.validateFipsMode(null, settings); + } +}