Browse Source

Add setting xpack.security.fips_mode.required_providers (#103483)

This commit adds an optional setting xpack.security.fips_mode.required_providers
to allow enforcing specific JCE/JSSE security providers while running in FIPS mode.

If running in FIPS mode with this value set and the required provider(s) is not found
an exception will be thrown during startup preventing the service from starting.
Jake Landis 1 year ago
parent
commit
64026130fd

+ 1 - 0
build-tools-internal/src/main/groovy/elasticsearch.fips.gradle

@@ -79,6 +79,7 @@ if (BuildParams.inFipsJvm) {
           // with no x-pack. Tests having security explicitly enabled/disabled will override this setting
           setting 'xpack.security.enabled', 'false'
           setting 'xpack.security.fips_mode.enabled', 'true'
+          setting 'xpack.security.fips_mode.required_providers', '["BCFIPS", "BCJSSE"]'
           setting 'xpack.license.self_generated.type', 'trial'
           setting 'xpack.security.authc.password_hashing.algorithm', 'pbkdf2_stretch'
           keystorePassword 'keystore-password'

+ 5 - 0
docs/reference/settings/security-settings.asciidoc

@@ -71,6 +71,11 @@ the sensitive nature of the information.
 (<<static-cluster-setting,Static>>)
 Enables fips mode of operation. Set this to `true` if you run this {es} instance in a FIPS 140-2 enabled JVM. For more information, see <<fips-140-compliance>>. Defaults to `false`.
 
+`xpack.security.fips_mode.required_providers`::
+(<<static-cluster-setting,Static>>)
+Optionally enforce specific Java JCE/JSSE security providers. For example, set this to `["BCFIPS", "BCJSSE"]` (case-insensitive) to require
+the Bouncy Castle FIPS JCE and JSSE security providers. Only applicable when `xpack.security.fips_mode.enabled` is set to `true`.
+
 [discrete]
 [[password-hashing-settings]]
 ==== Password hashing settings

+ 6 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java

@@ -160,6 +160,12 @@ public class XPackSettings {
         Property.NodeScope
     );
 
+    /** Optional setting to prevent startup if required providers are not discovered at runtime */
+    public static final Setting<List<String>> FIPS_REQUIRED_PROVIDERS = Setting.stringListSetting(
+        "xpack.security.fips_mode.required_providers",
+        Property.NodeScope
+    );
+
     /**
      * Setting for enabling the enrollment process, ie the enroll APIs are enabled, and the initial cluster node generates and displays
      * enrollment tokens (for Kibana and sometimes for ES nodes) when starting up for the first time.

+ 14 - 0
x-pack/plugin/ml/qa/multi-cluster-tests-with-security/build.gradle

@@ -2,6 +2,7 @@ import org.elasticsearch.gradle.internal.test.RestIntegTestTask
 import org.elasticsearch.gradle.Version
 import org.elasticsearch.gradle.VersionProperties
 import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
+import org.elasticsearch.gradle.internal.info.BuildParams
 
 apply plugin: 'elasticsearch.internal-testclusters'
 apply plugin: 'elasticsearch.standalone-rest-test'
@@ -49,16 +50,29 @@ testClusters.register('mixed-cluster') {
 tasks.register('remote-cluster', RestIntegTestTask) {
   mustRunAfter("precommit")
   systemProperty 'tests.rest.suite', 'remote_cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.register('mixed-cluster', RestIntegTestTask) {
   dependsOn 'remote-cluster'
   useCluster remoteCluster
   systemProperty 'tests.rest.suite', 'multi_cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.register("integTest") {
   dependsOn 'mixed-cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.named("check").configure { dependsOn("integTest") }
+
+//TODO: remove with version 8.14. A new FIPS setting was added in 8.13. Since FIPS configures all test clusters and this specific integTest uses
+// the previous minor version, that setting is not available when running in FIPS until 8.14.
+def maybeDisableForFips(task) {
+  if (BuildParams.inFipsJvm) {
+    if(Version.fromString(project.version).before(Version.fromString('8.14.0'))) {
+      task.enabled = false
+    }
+  }
+}

+ 26 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -375,6 +375,7 @@ import org.elasticsearch.xpack.security.transport.netty4.SecurityNetty4ServerTra
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.security.Provider;
 import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1178,6 +1179,7 @@ public class Security extends Plugin
 
         // The following just apply in node mode
         settingsList.add(XPackSettings.FIPS_MODE_ENABLED);
+        settingsList.add(XPackSettings.FIPS_REQUIRED_PROVIDERS);
 
         SSLService.registerSettings(settingsList);
         // IP Filter settings
@@ -1561,6 +1563,30 @@ public class Security extends Plugin
             }
         });
 
+        Set<String> foundProviders = new HashSet<>();
+        for (Provider provider : java.security.Security.getProviders()) {
+            foundProviders.add(provider.getName().toLowerCase(Locale.ROOT));
+            if (logger.isTraceEnabled()) {
+                logger.trace("Security Provider: " + provider.getName() + ", Version: " + provider.getVersionStr());
+                provider.entrySet().forEach(entry -> { logger.trace("\t" + entry.getKey()); });
+            }
+        }
+
+        final List<String> requiredProviders = XPackSettings.FIPS_REQUIRED_PROVIDERS.get(settings);
+        logger.info("JVM Security Providers: " + foundProviders);
+        if (requiredProviders != null && requiredProviders.isEmpty() == false) {
+            List<String> unsatisfiedProviders = requiredProviders.stream()
+                .map(s -> s.toLowerCase(Locale.ROOT))
+                .filter(element -> foundProviders.contains(element) == false)
+                .toList();
+
+            if (unsatisfiedProviders.isEmpty() == false) {
+                String errorMessage = "Could not find required FIPS security provider: " + unsatisfiedProviders;
+                logger.error(errorMessage);
+                validationErrors.add(errorMessage);
+            }
+        }
+
         if (validationErrors.isEmpty() == false) {
             final StringBuilder sb = new StringBuilder();
             sb.append("Validation for FIPS 140 mode failed: \n");

+ 26 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java

@@ -582,6 +582,32 @@ public class SecurityTests extends ESTestCase {
         assertThat(iae.getMessage(), containsString("Only PBKDF2 is allowed for stored credential hashing in a FIPS 140 JVM."));
     }
 
+    public void testValidateForFipsRequiredProvider() {
+        final Settings settings = Settings.builder()
+            .put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
+            .putList(XPackSettings.FIPS_REQUIRED_PROVIDERS.getKey(), List.of("BCFIPS"))
+            .build();
+        if (inFipsJvm()) {
+            Security.validateForFips(settings);
+            // no exceptions since gradle has wired in the bouncy castle FIPS provider
+        } else {
+            final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
+            assertThat(iae.getMessage(), containsString("Could not find required FIPS security provider: [bcfips]"));
+        }
+
+        final Settings settings2 = Settings.builder()
+            .put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
+            .putList(XPackSettings.FIPS_REQUIRED_PROVIDERS.getKey(), List.of("junk0", "BCFIPS", "junk1", "junk2"))
+            .build();
+        if (inFipsJvm()) {
+            final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings2));
+            assertThat(iae.getMessage(), containsString("Could not find required FIPS security provider: [junk0, junk1, junk2]"));
+        } else {
+            final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings2));
+            assertThat(iae.getMessage(), containsString("Could not find required FIPS security provider: [junk0, bcfips, junk1, junk2]"));
+        }
+    }
+
     public void testValidateForFipsMultipleValidationErrors() {
         final Settings settings = Settings.builder()
             .put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)

+ 14 - 0
x-pack/plugin/transform/qa/multi-cluster-tests-with-security/build.gradle

@@ -2,6 +2,7 @@ import org.elasticsearch.gradle.internal.test.RestIntegTestTask
 import org.elasticsearch.gradle.Version
 import org.elasticsearch.gradle.VersionProperties
 import static org.elasticsearch.gradle.PropertyNormalization.IGNORE_VALUE
+import org.elasticsearch.gradle.internal.info.BuildParams
 
 apply plugin: 'elasticsearch.internal-testclusters'
 apply plugin: 'elasticsearch.standalone-rest-test'
@@ -53,16 +54,29 @@ testClusters.register('mixed-cluster') {
 tasks.register('remote-cluster', RestIntegTestTask) {
   mustRunAfter("precommit")
   systemProperty 'tests.rest.suite', 'remote_cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.register('mixed-cluster', RestIntegTestTask) {
   dependsOn 'remote-cluster'
   useCluster remoteCluster
   systemProperty 'tests.rest.suite', 'multi_cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.register("integTest") {
   dependsOn 'mixed-cluster'
+  maybeDisableForFips(it)
 }
 
 tasks.named("check").configure { dependsOn("integTest") }
+
+//TODO: remove with version 8.14. A new FIPS setting was added in 8.13. Since FIPS configures all test clusters and this specific integTest uses
+// the previous minor version, that setting is not available when running in FIPS until 8.14.
+def maybeDisableForFips(task) {
+  if (BuildParams.inFipsJvm) {
+    if(Version.fromString(project.version).before(Version.fromString('8.14.0'))) {
+      task.enabled = false
+    }
+  }
+}