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.
This commit is contained in:
Jay Modi 2018-07-31 12:16:22 -06:00 committed by GitHub
parent 5fd7202808
commit 0788188574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 247 additions and 39 deletions

View File

@ -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<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();
}
});
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<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();
}
});
}
}

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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);
}
/**

View File

@ -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();
}
}

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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));

View File

@ -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());
}
}
}

View File

@ -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);
}

View File

@ -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();