Skip to content

Commit

Permalink
Add licensing enforcement for FIPS mode (#32437)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jaymode committed Jul 31, 2018
1 parent 7551fbc commit 87927ce
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,38 +209,44 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ private XPackSettings() {
}
}, 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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
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 {

private final boolean fipsModeEnabled;

FIPS140JKSKeystoreBootstrapCheck(Settings settings) {
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,6 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
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);

public static final String NAME4 = XPackField.SECURITY + "4";
public static final Setting<Optional<String>> USER_SETTING =
Expand Down Expand Up @@ -616,7 +614,7 @@ public static List<Setting<?>> getSettings(boolean transportClientMode, List<Sec
}

// The following just apply in node mode
settingsList.add(FIPS_MODE_ENABLED);
settingsList.add(XPackSettings.FIPS_MODE_ENABLED);

// IP Filter settings
IPFilter.addSettings(settingsList);
Expand Down Expand Up @@ -1084,8 +1082,9 @@ public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
if (enabled) {
return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings),
DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings))
.andThen(new ValidateUpgradedSecurityIndex())
.andThen(new ValidateLicenseCanBeDeserialized());
.andThen(new ValidateUpgradedSecurityIndex())
.andThen(new ValidateLicenseCanBeDeserialized())
.andThen(new ValidateLicenseForFIPS(XPackSettings.FIPS_MODE_ENABLED.get(settings)));
}
return null;
}
Expand Down Expand Up @@ -1133,6 +1132,27 @@ public void accept(DiscoveryNode node, ClusterState state) {
}
}

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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -31,7 +31,7 @@ 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")
.build();
final BootstrapCheck.BootstrapCheckResult result =
Expand All @@ -49,7 +49,7 @@ public void testBCRYPTAlgorithmDependsOnFipsMode() {
}

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);
}
Expand Down
Loading

0 comments on commit 87927ce

Please sign in to comment.