Skip to content

Commit 0788188

Browse files
authored
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.
1 parent 5fd7202 commit 0788188

File tree

11 files changed

+245
-37
lines changed

11 files changed

+245
-37
lines changed

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

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -209,38 +209,44 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene
209209
}
210210
}
211211

212-
if (newLicense.isProductionLicense()
213-
&& XPackSettings.SECURITY_ENABLED.get(settings)
212+
if (XPackSettings.SECURITY_ENABLED.get(settings)) {
213+
// TODO we should really validate that all nodes have xpack installed and are consistently configured but this
214+
// should happen on a different level and not in this code
215+
if (newLicense.isProductionLicense()
214216
&& XPackSettings.TRANSPORT_SSL_ENABLED.get(settings) == false
215217
&& isProductionMode(settings, clusterService.localNode())) {
216-
// security is on but TLS is not configured we gonna fail the entire request and throw an exception
217-
throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
218+
// security is on but TLS is not configured we gonna fail the entire request and throw an exception
219+
throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
218220
"] license unless TLS is configured or security is disabled");
219-
// TODO we should really validate that all nodes have xpack installed and are consistently configured but this
220-
// should happen on a different level and not in this code
221-
} else {
222-
clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new
223-
AckedClusterStateUpdateTask<PutLicenseResponse>(request, listener) {
224-
@Override
225-
protected PutLicenseResponse newResponse(boolean acknowledged) {
226-
return new PutLicenseResponse(acknowledged, LicensesStatus.VALID);
227-
}
221+
} else if (XPackSettings.FIPS_MODE_ENABLED.get(settings)
222+
&& newLicense.operationMode() != License.OperationMode.PLATINUM
223+
&& newLicense.operationMode() != License.OperationMode.TRIAL) {
224+
throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() +
225+
"] license unless FIPS mode is disabled");
226+
}
227+
}
228+
229+
clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new
230+
AckedClusterStateUpdateTask<PutLicenseResponse>(request, listener) {
231+
@Override
232+
protected PutLicenseResponse newResponse(boolean acknowledged) {
233+
return new PutLicenseResponse(acknowledged, LicensesStatus.VALID);
234+
}
228235

229-
@Override
230-
public ClusterState execute(ClusterState currentState) throws Exception {
231-
XPackPlugin.checkReadyForXPackCustomMetadata(currentState);
232-
MetaData currentMetadata = currentState.metaData();
233-
LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE);
234-
Version trialVersion = null;
235-
if (licensesMetaData != null) {
236-
trialVersion = licensesMetaData.getMostRecentTrialVersion();
237-
}
238-
MetaData.Builder mdBuilder = MetaData.builder(currentMetadata);
239-
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion));
240-
return ClusterState.builder(currentState).metaData(mdBuilder).build();
236+
@Override
237+
public ClusterState execute(ClusterState currentState) throws Exception {
238+
XPackPlugin.checkReadyForXPackCustomMetadata(currentState);
239+
MetaData currentMetadata = currentState.metaData();
240+
LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE);
241+
Version trialVersion = null;
242+
if (licensesMetaData != null) {
243+
trialVersion = licensesMetaData.getMostRecentTrialVersion();
241244
}
242-
});
243-
}
245+
MetaData.Builder mdBuilder = MetaData.builder(currentMetadata);
246+
mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion));
247+
return ClusterState.builder(currentState).metaData(mdBuilder).build();
248+
}
249+
});
244250
}
245251
}
246252

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ private XPackSettings() {
8787
public static final Setting<Boolean> TOKEN_SERVICE_ENABLED_SETTING = Setting.boolSetting("xpack.security.authc.token.enabled",
8888
XPackSettings.HTTP_SSL_ENABLED::getRaw, Setting.Property.NodeScope);
8989

90+
/** Setting for enabling or disabling FIPS mode. Defaults to false */
91+
public static final Setting<Boolean> FIPS_MODE_ENABLED =
92+
Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope);
93+
9094
/** Setting for enabling or disabling sql. Defaults to true. */
9195
public static final Setting<Boolean> SQL_ENABLED = Setting.boolSetting("xpack.sql.enabled", true, Setting.Property.NodeScope);
9296

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.license;
8+
9+
import org.elasticsearch.action.support.PlainActionFuture;
10+
import org.elasticsearch.cluster.ClusterStateUpdateTask;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.common.unit.TimeValue;
13+
import org.elasticsearch.protocol.xpack.license.PutLicenseResponse;
14+
15+
import static org.hamcrest.Matchers.containsString;
16+
import static org.mockito.Matchers.any;
17+
import static org.mockito.Mockito.verify;
18+
19+
public class LicenseFIPSTests extends AbstractLicenseServiceTestCase {
20+
21+
public void testFIPSCheckWithAllowedLicense() throws Exception {
22+
License newLicense = TestUtils.generateSignedLicense(randomFrom("trial", "platinum"), TimeValue.timeValueHours(24L));
23+
PutLicenseRequest request = new PutLicenseRequest();
24+
request.acknowledge(true);
25+
request.license(newLicense);
26+
Settings settings = Settings.builder()
27+
.put("xpack.security.enabled", true)
28+
.put("xpack.security.transport.ssl.enabled", true)
29+
.put("xpack.security.fips_mode.enabled", randomBoolean())
30+
.build();
31+
XPackLicenseState licenseState = new XPackLicenseState(settings);
32+
33+
setInitialState(null, licenseState, settings);
34+
licenseService.start();
35+
PlainActionFuture<PutLicenseResponse> responseFuture = new PlainActionFuture<>();
36+
licenseService.registerLicense(request, responseFuture);
37+
verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
38+
}
39+
40+
public void testFIPSCheckWithoutAllowedLicense() throws Exception {
41+
License newLicense = TestUtils.generateSignedLicense(randomFrom("gold", "standard"), TimeValue.timeValueHours(24L));
42+
PutLicenseRequest request = new PutLicenseRequest();
43+
request.acknowledge(true);
44+
request.license(newLicense);
45+
Settings settings = Settings.builder()
46+
.put("xpack.security.enabled", true)
47+
.put("xpack.security.transport.ssl.enabled", true)
48+
.put("xpack.security.fips_mode.enabled", true)
49+
.build();
50+
XPackLicenseState licenseState = new XPackLicenseState(settings);
51+
52+
setInitialState(null, licenseState, settings);
53+
licenseService.start();
54+
PlainActionFuture<PutLicenseResponse> responseFuture = new PlainActionFuture<>();
55+
IllegalStateException e = expectThrows(IllegalStateException.class, () -> licenseService.registerLicense(request, responseFuture));
56+
assertThat(e.getMessage(),
57+
containsString("Cannot install a [" + newLicense.operationMode() + "] license unless FIPS mode is disabled"));
58+
licenseService.stop();
59+
60+
settings = Settings.builder()
61+
.put("xpack.security.enabled", true)
62+
.put("xpack.security.transport.ssl.enabled", true)
63+
.put("xpack.security.fips_mode.enabled", false)
64+
.build();
65+
licenseState = new XPackLicenseState(settings);
66+
67+
setInitialState(null, licenseState, settings);
68+
licenseService.start();
69+
licenseService.registerLicense(request, responseFuture);
70+
verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
71+
}
72+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
import org.elasticsearch.bootstrap.BootstrapCheck;
99
import org.elasticsearch.bootstrap.BootstrapContext;
1010
import org.elasticsearch.common.settings.Settings;
11+
import org.elasticsearch.xpack.core.XPackSettings;
1112

1213

1314
public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {
1415

1516
private final boolean fipsModeEnabled;
1617

1718
FIPS140JKSKeystoreBootstrapCheck(Settings settings) {
18-
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
19+
this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
1920
}
2021

2122
/**
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.security;
8+
9+
import org.elasticsearch.bootstrap.BootstrapCheck;
10+
import org.elasticsearch.bootstrap.BootstrapContext;
11+
import org.elasticsearch.license.License;
12+
import org.elasticsearch.license.LicenseService;
13+
14+
import java.util.EnumSet;
15+
16+
/**
17+
* A bootstrap check which enforces the licensing of FIPS
18+
*/
19+
final class FIPS140LicenseBootstrapCheck implements BootstrapCheck {
20+
21+
static final EnumSet<License.OperationMode> ALLOWED_LICENSE_OPERATION_MODES =
22+
EnumSet.of(License.OperationMode.PLATINUM, License.OperationMode.TRIAL);
23+
24+
private final boolean isInFipsMode;
25+
26+
FIPS140LicenseBootstrapCheck(boolean isInFipsMode) {
27+
this.isInFipsMode = isInFipsMode;
28+
}
29+
30+
@Override
31+
public BootstrapCheckResult check(BootstrapContext context) {
32+
if (isInFipsMode) {
33+
License license = LicenseService.getLicense(context.metaData);
34+
if (license != null && ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode()) == false) {
35+
return BootstrapCheckResult.failure("FIPS mode is only allowed with a Platinum or Trial license");
36+
}
37+
}
38+
return BootstrapCheckResult.success();
39+
}
40+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheck implements BootstrapC
1717
private final boolean fipsModeEnabled;
1818

1919
FIPS140PasswordHashingAlgorithmBootstrapCheck(final Settings settings) {
20-
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
20+
this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
2121
}
2222

2323
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.common.settings.KeyStoreWrapper;
1111
import org.elasticsearch.common.settings.Settings;
1212
import org.elasticsearch.env.Environment;
13+
import org.elasticsearch.xpack.core.XPackSettings;
1314

1415
import java.io.IOException;
1516
import java.io.UncheckedIOException;
@@ -20,7 +21,7 @@ public class FIPS140SecureSettingsBootstrapCheck implements BootstrapCheck {
2021
private final Environment environment;
2122

2223
FIPS140SecureSettingsBootstrapCheck(Settings settings, Environment environment) {
23-
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
24+
this.fipsModeEnabled = XPackSettings.FIPS_MODE_ENABLED.get(settings);
2425
this.environment = environment;
2526
}
2627

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,6 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
255255
DiscoveryPlugin, MapperPlugin, ExtensiblePlugin {
256256

257257
private static final Logger logger = Loggers.getLogger(Security.class);
258-
static final Setting<Boolean> FIPS_MODE_ENABLED =
259-
Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope);
260258

261259
static final Setting<List<String>> AUDIT_OUTPUTS_SETTING =
262260
Setting.listSetting(SecurityField.setting("audit.outputs"),
@@ -593,7 +591,7 @@ public static List<Setting<?>> getSettings(boolean transportClientMode, List<Sec
593591
}
594592

595593
// The following just apply in node mode
596-
settingsList.add(FIPS_MODE_ENABLED);
594+
settingsList.add(XPackSettings.FIPS_MODE_ENABLED);
597595

598596
// IP Filter settings
599597
IPFilter.addSettings(settingsList);
@@ -1000,7 +998,8 @@ public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
1000998
return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings),
1001999
DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings))
10021000
.andThen(new ValidateUpgradedSecurityIndex())
1003-
.andThen(new ValidateLicenseCanBeDeserialized());
1001+
.andThen(new ValidateLicenseCanBeDeserialized())
1002+
.andThen(new ValidateLicenseForFIPS(XPackSettings.FIPS_MODE_ENABLED.get(settings)));
10041003
}
10051004
return null;
10061005
}
@@ -1048,6 +1047,27 @@ public void accept(DiscoveryNode node, ClusterState state) {
10481047
}
10491048
}
10501049

1050+
static final class ValidateLicenseForFIPS implements BiConsumer<DiscoveryNode, ClusterState> {
1051+
private final boolean inFipsMode;
1052+
1053+
ValidateLicenseForFIPS(boolean inFipsMode) {
1054+
this.inFipsMode = inFipsMode;
1055+
}
1056+
1057+
@Override
1058+
public void accept(DiscoveryNode node, ClusterState state) {
1059+
if (inFipsMode) {
1060+
License license = LicenseService.getLicense(state.metaData());
1061+
if (license != null &&
1062+
FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode()) == false) {
1063+
throw new IllegalStateException("FIPS mode cannot be used with a [" + license.operationMode() +
1064+
"] license. It is only allowed with a Platinum or Trial license.");
1065+
1066+
}
1067+
}
1068+
}
1069+
}
1070+
10511071
@Override
10521072
public void reloadSPI(ClassLoader loader) {
10531073
securityExtensions.addAll(SecurityExtension.loadExtensions(loader));
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.security;
8+
9+
import org.elasticsearch.bootstrap.BootstrapContext;
10+
import org.elasticsearch.cluster.metadata.MetaData;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.common.unit.TimeValue;
13+
import org.elasticsearch.license.License;
14+
import org.elasticsearch.license.TestUtils;
15+
import org.elasticsearch.test.ESTestCase;
16+
17+
public class FIPS140LicenseBootstrapCheckTests extends ESTestCase {
18+
19+
public void testBootstrapCheck() throws Exception {
20+
assertTrue(new FIPS140LicenseBootstrapCheck(false)
21+
.check(new BootstrapContext(Settings.EMPTY, MetaData.EMPTY_META_DATA)).isSuccess());
22+
assertTrue(new FIPS140LicenseBootstrapCheck(randomBoolean())
23+
.check(new BootstrapContext(Settings.EMPTY, MetaData.EMPTY_META_DATA)).isSuccess());
24+
25+
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
26+
MetaData.Builder builder = MetaData.builder();
27+
TestUtils.putLicense(builder, license);
28+
MetaData metaData = builder.build();
29+
if (FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode())) {
30+
assertTrue(new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
31+
Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).isSuccess());
32+
assertTrue(new FIPS140LicenseBootstrapCheck(false).check(new BootstrapContext(
33+
Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
34+
} else {
35+
assertTrue(new FIPS140LicenseBootstrapCheck(false).check(new BootstrapContext(
36+
Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
37+
assertTrue(new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
38+
Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).isFailure());
39+
assertEquals("FIPS mode is only allowed with a Platinum or Trial license",
40+
new FIPS140LicenseBootstrapCheck(true).check(new BootstrapContext(
41+
Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).getMessage());
42+
}
43+
}
44+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCa
2121
public void testPBKDF2AlgorithmIsAllowed() {
2222
{
2323
final Settings settings = Settings.builder()
24-
.put(Security.FIPS_MODE_ENABLED.getKey(), true)
24+
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
2525
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000")
2626
.build();
2727
final BootstrapCheck.BootstrapCheckResult result =
@@ -31,7 +31,7 @@ public void testPBKDF2AlgorithmIsAllowed() {
3131

3232
{
3333
final Settings settings = Settings.builder()
34-
.put(Security.FIPS_MODE_ENABLED.getKey(), true)
34+
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
3535
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2")
3636
.build();
3737
final BootstrapCheck.BootstrapCheckResult result =
@@ -49,7 +49,7 @@ public void testBCRYPTAlgorithmDependsOnFipsMode() {
4949
}
5050

5151
private void runBCRYPTTest(final boolean fipsModeEnabled, final String passwordHashingAlgorithm) {
52-
final Settings.Builder builder = Settings.builder().put(Security.FIPS_MODE_ENABLED.getKey(), fipsModeEnabled);
52+
final Settings.Builder builder = Settings.builder().put(XPackSettings.FIPS_MODE_ENABLED.getKey(), fipsModeEnabled);
5353
if (passwordHashingAlgorithm != null) {
5454
builder.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), passwordHashingAlgorithm);
5555
}

0 commit comments

Comments
 (0)