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:
parent
5fd7202808
commit
0788188574
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue