Przeglądaj źródła

Add licensing enforcement for FIPS mode (#32437)

This commit adds licensing enforcement for FIPS mode through the use of
a bootstrap check, a node join validator, and a check in the license
service. The work done here is based on the current implementation of
the TLS enforcement with a production license.

The bootstrap check is always enforced since we need to enforce the
licensing and this is the best option to do so at the present time.
Jay Modi 7 lat temu
rodzic
commit
0788188574

+ 33 - 27
x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java

@@ -209,38 +209,44 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
                 }
             }
 
-            if (newLicense.isProductionLicense()
-                    && XPackSettings.SECURITY_ENABLED.get(settings)
+            if (XPackSettings.SECURITY_ENABLED.get(settings)) {
+                // TODO we should really validate that all nodes have xpack installed and are consistently configured but this
+                // should happen on a different level and not in this code
+                if (newLicense.isProductionLicense()
                     && XPackSettings.TRANSPORT_SSL_ENABLED.get(settings) == false
                     && isProductionMode(settings, clusterService.localNode())) {
-                // security is on but TLS is not configured we gonna fail the entire request and throw an exception
-                throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
+                    // security is on but TLS is not configured we gonna fail the entire request and throw an exception
+                    throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
                         "] license unless TLS is configured or security is disabled");
-                // TODO we should really validate that all nodes have xpack installed and are consistently configured but this
-                // should happen on a different level and not in this code
-            } else {
-                clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new
-                        AckedClusterStateUpdateTask<PutLicenseResponse>(request, listener) {
-                            @Override
-                            protected PutLicenseResponse newResponse(boolean acknowledged) {
-                                return new PutLicenseResponse(acknowledged, LicensesStatus.VALID);
-                            }
+                } else if (XPackSettings.FIPS_MODE_ENABLED.get(settings)
+                    && newLicense.operationMode() != License.OperationMode.PLATINUM
+                    && newLicense.operationMode() != License.OperationMode.TRIAL) {
+                    throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
+                        "] license unless FIPS mode is disabled");
+                }
+            }
+
+            clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new
+                    AckedClusterStateUpdateTask<PutLicenseResponse>(request, listener) {
+                        @Override
+                        protected PutLicenseResponse newResponse(boolean acknowledged) {
+                            return new PutLicenseResponse(acknowledged, LicensesStatus.VALID);
+                        }
 
-                            @Override
-                            public ClusterState execute(ClusterState currentState) throws Exception {
-                                XPackPlugin.checkReadyForXPackCustomMetadata(currentState);
-                                MetaData currentMetadata = currentState.metaData();
-                                LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE);
-                                Version trialVersion = null;
-                                if (licensesMetaData != null) {
-                                    trialVersion = licensesMetaData.getMostRecentTrialVersion();
-                                }
-                                MetaData.Builder mdBuilder = MetaData.builder(currentMetadata);
-                                mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion));
-                                return ClusterState.builder(currentState).metaData(mdBuilder).build();
+                        @Override
+                        public ClusterState execute(ClusterState currentState) throws Exception {
+                            XPackPlugin.checkReadyForXPackCustomMetadata(currentState);
+                            MetaData currentMetadata = currentState.metaData();
+                            LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE);
+                            Version trialVersion = null;
+                            if (licensesMetaData != null) {
+                                trialVersion = licensesMetaData.getMostRecentTrialVersion();
                             }
-                        });
-            }
+                            MetaData.Builder mdBuilder = MetaData.builder(currentMetadata);
+                            mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion));
+                            return ClusterState.builder(currentState).metaData(mdBuilder).build();
+                        }
+                    });
         }
     }
 

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

@@ -87,6 +87,10 @@ public class XPackSettings {
     public static final Setting<Boolean> TOKEN_SERVICE_ENABLED_SETTING = Setting.boolSetting("xpack.security.authc.token.enabled",
         XPackSettings.HTTP_SSL_ENABLED::getRaw, Setting.Property.NodeScope);
 
+    /** Setting for enabling or disabling FIPS mode. Defaults to false */
+    public static final Setting<Boolean> FIPS_MODE_ENABLED =
+        Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope);
+
     /** Setting for enabling or disabling sql. Defaults to true. */
     public static final Setting<Boolean> SQL_ENABLED = Setting.boolSetting("xpack.sql.enabled", true, Setting.Property.NodeScope);
 

+ 72 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.license;
+
+import org.elasticsearch.action.support.PlainActionFuture;
+import org.elasticsearch.cluster.ClusterStateUpdateTask;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.protocol.xpack.license.PutLicenseResponse;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+
+public class LicenseFIPSTests extends AbstractLicenseServiceTestCase {
+
+    public void testFIPSCheckWithAllowedLicense() throws Exception {
+        License newLicense = TestUtils.generateSignedLicense(randomFrom("trial", "platinum"), TimeValue.timeValueHours(24L));
+        PutLicenseRequest request = new PutLicenseRequest();
+        request.acknowledge(true);
+        request.license(newLicense);
+        Settings settings = Settings.builder()
+            .put("xpack.security.enabled", true)
+            .put("xpack.security.transport.ssl.enabled", true)
+            .put("xpack.security.fips_mode.enabled", randomBoolean())
+            .build();
+        XPackLicenseState licenseState = new XPackLicenseState(settings);
+
+        setInitialState(null, licenseState, settings);
+        licenseService.start();
+        PlainActionFuture<PutLicenseResponse> responseFuture = new PlainActionFuture<>();
+        licenseService.registerLicense(request, responseFuture);
+        verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
+    }
+
+    public void testFIPSCheckWithoutAllowedLicense() throws Exception {
+        License newLicense = TestUtils.generateSignedLicense(randomFrom("gold", "standard"), TimeValue.timeValueHours(24L));
+        PutLicenseRequest request = new PutLicenseRequest();
+        request.acknowledge(true);
+        request.license(newLicense);
+        Settings settings = Settings.builder()
+            .put("xpack.security.enabled", true)
+            .put("xpack.security.transport.ssl.enabled", true)
+            .put("xpack.security.fips_mode.enabled", true)
+            .build();
+        XPackLicenseState licenseState = new XPackLicenseState(settings);
+
+        setInitialState(null, licenseState, settings);
+        licenseService.start();
+        PlainActionFuture<PutLicenseResponse> responseFuture = new PlainActionFuture<>();
+        IllegalStateException e = expectThrows(IllegalStateException.class, () -> licenseService.registerLicense(request, responseFuture));
+        assertThat(e.getMessage(),
+            containsString("Cannot install a [" + newLicense.operationMode() + "] license unless FIPS mode is disabled"));
+        licenseService.stop();
+
+        settings = Settings.builder()
+            .put("xpack.security.enabled", true)
+            .put("xpack.security.transport.ssl.enabled", true)
+            .put("xpack.security.fips_mode.enabled", false)
+            .build();
+        licenseState = new XPackLicenseState(settings);
+
+        setInitialState(null, licenseState, settings);
+        licenseService.start();
+        licenseService.registerLicense(request, responseFuture);
+        verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
+    }
+}

+ 2 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java

@@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security;
 import org.elasticsearch.bootstrap.BootstrapCheck;
 import org.elasticsearch.bootstrap.BootstrapContext;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.xpack.core.XPackSettings;
 
 
 public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {
@@ -15,7 +16,7 @@ public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {
     private final boolean fipsModeEnabled;
 
     FIPS140JKSKeystoreBootstrapCheck(Settings settings) {
-        this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
+        this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
     }
 
     /**

+ 40 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security;
+
+import org.elasticsearch.bootstrap.BootstrapCheck;
+import org.elasticsearch.bootstrap.BootstrapContext;
+import org.elasticsearch.license.License;
+import org.elasticsearch.license.LicenseService;
+
+import java.util.EnumSet;
+
+/**
+ * A bootstrap check which enforces the licensing of FIPS
+ */
+final class FIPS140LicenseBootstrapCheck implements BootstrapCheck {
+
+    static final EnumSet<License.OperationMode> ALLOWED_LICENSE_OPERATION_MODES =
+        EnumSet.of(License.OperationMode.PLATINUM, License.OperationMode.TRIAL);
+
+    private final boolean isInFipsMode;
+
+    FIPS140LicenseBootstrapCheck(boolean isInFipsMode) {
+        this.isInFipsMode = isInFipsMode;
+    }
+
+    @Override
+    public BootstrapCheckResult check(BootstrapContext context) {
+        if (isInFipsMode) {
+            License license = LicenseService.getLicense(context.metaData);
+            if (license != null && ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode()) == false) {
+                return BootstrapCheckResult.failure("FIPS mode is only allowed with a Platinum or Trial license");
+            }
+        }
+        return BootstrapCheckResult.success();
+    }
+}

+ 1 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java

@@ -17,7 +17,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheck implements BootstrapC
     private final boolean fipsModeEnabled;
 
     FIPS140PasswordHashingAlgorithmBootstrapCheck(final Settings settings) {
-        this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
+        this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
     }
 
     /**

+ 2 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java

@@ -10,6 +10,7 @@ import org.elasticsearch.bootstrap.BootstrapContext;
 import org.elasticsearch.common.settings.KeyStoreWrapper;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.env.Environment;
+import org.elasticsearch.xpack.core.XPackSettings;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -20,7 +21,7 @@ public class FIPS140SecureSettingsBootstrapCheck implements BootstrapCheck {
     private final Environment environment;
 
     FIPS140SecureSettingsBootstrapCheck(Settings settings, Environment environment) {
-        this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
+        this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
         this.environment = environment;
     }
 

+ 24 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -255,8 +255,6 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         DiscoveryPlugin, MapperPlugin, ExtensiblePlugin {
 
     private static final Logger logger = Loggers.getLogger(Security.class);
-    static final Setting<Boolean> FIPS_MODE_ENABLED =
-        Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope);
 
     static final Setting<List<String>> AUDIT_OUTPUTS_SETTING =
         Setting.listSetting(SecurityField.setting("audit.outputs"),
@@ -593,7 +591,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         }
 
         // The following just apply in node mode
-        settingsList.add(FIPS_MODE_ENABLED);
+        settingsList.add(XPackSettings.FIPS_MODE_ENABLED);
 
         // IP Filter settings
         IPFilter.addSettings(settingsList);
@@ -1000,7 +998,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
             return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings),
                     DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings))
                 .andThen(new ValidateUpgradedSecurityIndex())
-                .andThen(new ValidateLicenseCanBeDeserialized());
+                .andThen(new ValidateLicenseCanBeDeserialized())
+                .andThen(new ValidateLicenseForFIPS(XPackSettings.FIPS_MODE_ENABLED.get(settings)));
         }
         return null;
     }
@@ -1048,6 +1047,27 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         }
     }
 
+    static final class ValidateLicenseForFIPS implements BiConsumer<DiscoveryNode, ClusterState> {
+        private final boolean inFipsMode;
+
+        ValidateLicenseForFIPS(boolean inFipsMode) {
+            this.inFipsMode = inFipsMode;
+        }
+
+        @Override
+        public void accept(DiscoveryNode node, ClusterState state) {
+            if (inFipsMode) {
+                License license = LicenseService.getLicense(state.metaData());
+                if (license != null &&
+                    FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode()) == false) {
+                    throw new IllegalStateException("FIPS mode cannot be used with a [" + license.operationMode() +
+                        "] license. It is only allowed with a Platinum or Trial license.");
+
+                }
+            }
+        }
+    }
+
     @Override
     public void reloadSPI(ClassLoader loader) {
         securityExtensions.addAll(SecurityExtension.loadExtensions(loader));

+ 44 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security;
+
+import org.elasticsearch.bootstrap.BootstrapContext;
+import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.license.License;
+import org.elasticsearch.license.TestUtils;
+import org.elasticsearch.test.ESTestCase;
+
+public class FIPS140LicenseBootstrapCheckTests extends ESTestCase {
+
+    public void testBootstrapCheck() throws Exception {
+        assertTrue(new FIPS140LicenseBootstrapCheck(false)
+            .check(new BootstrapContext(Settings.EMPTY, MetaData.EMPTY_META_DATA)).isSuccess());
+        assertTrue(new FIPS140LicenseBootstrapCheck(randomBoolean())
+            .check(new BootstrapContext(Settings.EMPTY, MetaData.EMPTY_META_DATA)).isSuccess());
+
+        License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
+        MetaData.Builder builder = MetaData.builder();
+        TestUtils.putLicense(builder, license);
+        MetaData metaData = builder.build();
+        if (FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode())) {
+            assertTrue(new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
+                Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).isSuccess());
+            assertTrue(new FIPS140LicenseBootstrapCheck(false).check(new BootstrapContext(
+                Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
+        } else {
+            assertTrue(new FIPS140LicenseBootstrapCheck(false).check(new BootstrapContext(
+                Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
+            assertTrue(new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
+                Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).isFailure());
+            assertEquals("FIPS mode is only allowed with a Platinum or Trial license",
+                new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
+                    Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).getMessage());
+        }
+    }
+}

+ 3 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java

@@ -21,7 +21,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCa
     public void testPBKDF2AlgorithmIsAllowed() {
         {
             final Settings settings = Settings.builder()
-                    .put(Security.FIPS_MODE_ENABLED.getKey(), true)
+                    .put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
                     .put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000")
                     .build();
             final BootstrapCheck.BootstrapCheckResult result =
@@ -31,7 +31,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCa
 
         {
             final Settings settings = Settings.builder()
-                    .put(Security.FIPS_MODE_ENABLED.getKey(), true)
+                    .put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
                     .put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2")
                     .build();
             final BootstrapCheck.BootstrapCheckResult result =
@@ -49,7 +49,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCa
     }
 
     private void runBCRYPTTest(final boolean fipsModeEnabled, final String passwordHashingAlgorithm) {
-        final Settings.Builder builder = Settings.builder().put(Security.FIPS_MODE_ENABLED.getKey(), fipsModeEnabled);
+        final Settings.Builder builder = Settings.builder().put(XPackSettings.FIPS_MODE_ENABLED.getKey(), fipsModeEnabled);
         if (passwordHashingAlgorithm != null) {
             builder.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), passwordHashingAlgorithm);
         }

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

@@ -292,6 +292,26 @@ public class SecurityTests extends ESTestCase {
         assertThat(e.getMessage(), containsString("cannot deserialize the license format"));
     }
 
+    public void testJoinValidatorForFIPSLicense() throws Exception {
+        DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(),
+            VersionUtils.randomVersionBetween(random(), null, Version.CURRENT));
+        MetaData.Builder builder = MetaData.builder();
+        License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
+        TestUtils.putLicense(builder, license);
+        ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(builder.build()).build();
+        new Security.ValidateLicenseForFIPS(false).accept(node, state);
+
+        final boolean isLicenseValidForFips =
+            FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode());
+        if (isLicenseValidForFips) {
+            new Security.ValidateLicenseForFIPS(true).accept(node, state);
+        } else {
+            IllegalStateException e = expectThrows(IllegalStateException.class,
+                () -> new Security.ValidateLicenseForFIPS(true).accept(node, state));
+            assertThat(e.getMessage(), containsString("FIPS mode cannot be used"));
+        }
+    }
+
     public void testIndexJoinValidator_Old_And_Rolling() throws Exception {
         createComponents(Settings.EMPTY);
         BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();