diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index 505d2c55fa6..d299406aad0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/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) - && 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() + - "] license unless TLS is configured or security is disabled"); + 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 - } else { - clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new - AckedClusterStateUpdateTask(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(); - } - }); + 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() + + "] license unless TLS is configured or security is disabled"); + } 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(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(); + } + }); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index 8c4c5e2c760..1a80c6d873b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -87,6 +87,10 @@ public class XPackSettings { public static final Setting 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 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 SQL_ENABLED = Setting.boolSetting("xpack.sql.enabled", true, Setting.Property.NodeScope); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java new file mode 100644 index 00000000000..c432a207fcb --- /dev/null +++ b/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 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 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)); + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java index 4a2c7b97195..cd5a720eef1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java +++ b/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); } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java new file mode 100644 index 00000000000..d1bce0dcdd2 --- /dev/null +++ b/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 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(); + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java index 57e12a211a3..751d63be4fb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java +++ b/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); } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java index c766dd0ffaa..a8cb32d545e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java +++ b/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; } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index df5e8dcae1b..18bcfeb94a5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/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 FIPS_MODE_ENABLED = - Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope); static final Setting> 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 { + 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)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java new file mode 100644 index 00000000000..a2ec8f9fb20 --- /dev/null +++ b/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()); + } + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java index b424986876a..8632400866a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java +++ b/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); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index e88b1905a7a..857b1694ac8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/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 joinValidator = security.getJoinValidator();