Skip to content

Commit be40a69

Browse files
authored
Introduce fips_mode setting and associated checks (#32326)
* Introduce fips_mode setting and associated checks Introduce xpack.security.fips_mode.enabled setting ( default false) When it is set to true, a number of Bootstrap checks are performed: - Check that Secure Settings are of the latest version (3) - Check that no JKS keystores are configured - Check that compliant algorithms ( PBKDF2 family ) are used for password hashing
1 parent e0b7e4b commit be40a69

File tree

8 files changed

+368
-2
lines changed

8 files changed

+368
-2
lines changed

server/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ private KeyStoreWrapper(int formatVersion, boolean hasPassword, byte[] dataBytes
166166
this.dataBytes = dataBytes;
167167
}
168168

169+
/**
170+
* Get the metadata format version for the keystore
171+
**/
172+
public int getFormatVersion() {
173+
return formatVersion;
174+
}
175+
169176
/** Returns a path representing the ES keystore in the given config dir. */
170177
public static Path keystorePath(Path configDir) {
171178
return configDir.resolve(KEYSTORE_FILENAME);
@@ -593,8 +600,10 @@ private void ensureOpen() {
593600
@Override
594601
public synchronized void close() {
595602
this.closed = true;
596-
for (Entry entry : entries.get().values()) {
597-
Arrays.fill(entry.bytes, (byte)0);
603+
if (null != entries.get() && entries.get().isEmpty() == false) {
604+
for (Entry entry : entries.get().values()) {
605+
Arrays.fill(entry.bytes, (byte) 0);
606+
}
598607
}
599608
}
600609
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
package org.elasticsearch.xpack.security;
7+
8+
import org.elasticsearch.bootstrap.BootstrapCheck;
9+
import org.elasticsearch.bootstrap.BootstrapContext;
10+
import org.elasticsearch.common.settings.Settings;
11+
12+
13+
public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {
14+
15+
private final boolean fipsModeEnabled;
16+
17+
FIPS140JKSKeystoreBootstrapCheck(Settings settings) {
18+
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
19+
}
20+
21+
/**
22+
* Test if the node fails the check.
23+
*
24+
* @param context the bootstrap context
25+
* @return the result of the bootstrap check
26+
*/
27+
@Override
28+
public BootstrapCheckResult check(BootstrapContext context) {
29+
30+
if (fipsModeEnabled) {
31+
final Settings settings = context.settings;
32+
Settings keystoreTypeSettings = settings.filter(k -> k.endsWith("keystore.type"))
33+
.filter(k -> settings.get(k).equalsIgnoreCase("jks"));
34+
if (keystoreTypeSettings.isEmpty() == false) {
35+
return BootstrapCheckResult.failure("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
36+
"revisit [" + keystoreTypeSettings.toDelimitedString(',') + "] settings");
37+
}
38+
// Default Keystore type is JKS if not explicitly set
39+
Settings keystorePathSettings = settings.filter(k -> k.endsWith("keystore.path"))
40+
.filter(k -> settings.hasValue(k.replace(".path", ".type")) == false);
41+
if (keystorePathSettings.isEmpty() == false) {
42+
return BootstrapCheckResult.failure("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
43+
"revisit [" + keystorePathSettings.toDelimitedString(',') + "] settings");
44+
}
45+
46+
}
47+
return BootstrapCheckResult.success();
48+
}
49+
50+
@Override
51+
public boolean alwaysEnforce() {
52+
return fipsModeEnabled;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
package org.elasticsearch.xpack.security;
7+
8+
import org.elasticsearch.bootstrap.BootstrapCheck;
9+
import org.elasticsearch.bootstrap.BootstrapContext;
10+
import org.elasticsearch.common.settings.Settings;
11+
import org.elasticsearch.xpack.core.XPackSettings;
12+
13+
import java.util.Locale;
14+
15+
public class FIPS140PasswordHashingAlgorithmBootstrapCheck implements BootstrapCheck {
16+
17+
private final boolean fipsModeEnabled;
18+
19+
FIPS140PasswordHashingAlgorithmBootstrapCheck(Settings settings) {
20+
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
21+
}
22+
23+
/**
24+
* Test if the node fails the check.
25+
*
26+
* @param context the bootstrap context
27+
* @return the result of the bootstrap check
28+
*/
29+
@Override
30+
public BootstrapCheckResult check(BootstrapContext context) {
31+
final String selectedAlgorithm = XPackSettings.PASSWORD_HASHING_ALGORITHM.get(context.settings);
32+
if (selectedAlgorithm.toLowerCase(Locale.ROOT).startsWith("pbkdf2") == false) {
33+
return BootstrapCheckResult.failure("Only PBKDF2 is allowed for password hashing in a FIPS-140 JVM. Please set the " +
34+
"appropriate value for [ " + XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey() + " ] setting.");
35+
}
36+
return BootstrapCheckResult.success();
37+
}
38+
39+
@Override
40+
public boolean alwaysEnforce() {
41+
return fipsModeEnabled;
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
package org.elasticsearch.xpack.security;
7+
8+
import org.elasticsearch.bootstrap.BootstrapCheck;
9+
import org.elasticsearch.bootstrap.BootstrapContext;
10+
import org.elasticsearch.common.settings.KeyStoreWrapper;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.env.Environment;
13+
14+
import java.io.IOException;
15+
import java.io.UncheckedIOException;
16+
17+
public class FIPS140SecureSettingsBootstrapCheck implements BootstrapCheck {
18+
19+
private final boolean fipsModeEnabled;
20+
private final Environment environment;
21+
22+
FIPS140SecureSettingsBootstrapCheck(Settings settings, Environment environment) {
23+
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
24+
this.environment = environment;
25+
}
26+
27+
/**
28+
* Test if the node fails the check.
29+
*
30+
* @param context the bootstrap context
31+
* @return the result of the bootstrap check
32+
*/
33+
@Override
34+
public BootstrapCheckResult check(BootstrapContext context) {
35+
if (fipsModeEnabled) {
36+
try (KeyStoreWrapper secureSettings = KeyStoreWrapper.load(environment.configFile())) {
37+
if (secureSettings != null && secureSettings.getFormatVersion() < 3) {
38+
return BootstrapCheckResult.failure("Secure settings store is not of the latest version. Please use " +
39+
"bin/elasticsearch-keystore create to generate a new secure settings store and migrate the secure settings there.");
40+
}
41+
} catch (IOException e) {
42+
throw new UncheckedIOException(e);
43+
}
44+
}
45+
return BootstrapCheckResult.success();
46+
}
47+
48+
@Override
49+
public boolean alwaysEnforce() {
50+
return fipsModeEnabled;
51+
}
52+
}

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

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

258258
private static final Logger logger = Loggers.getLogger(Security.class);
259+
static final Setting<Boolean> FIPS_MODE_ENABLED =
260+
Setting.boolSetting("xpack.security.fips_mode.enabled", false, Property.NodeScope);
259261

260262
static final Setting<List<String>> AUDIT_OUTPUTS_SETTING =
261263
Setting.listSetting(SecurityField.setting("audit.outputs"),
@@ -305,6 +307,9 @@ public Security(Settings settings, final Path configPath) {
305307
new PkiRealmBootstrapCheck(getSslService()),
306308
new TLSLicenseBootstrapCheck(),
307309
new PasswordHashingAlgorithmBootstrapCheck(),
310+
new FIPS140SecureSettingsBootstrapCheck(settings, env),
311+
new FIPS140JKSKeystoreBootstrapCheck(settings),
312+
new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings),
308313
new KerberosRealmBootstrapCheck(env)));
309314
checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
310315
this.bootstrapChecks = Collections.unmodifiableList(checks);
@@ -592,6 +597,7 @@ public static List<Setting<?>> getSettings(boolean transportClientMode, List<Sec
592597
}
593598

594599
// The following just apply in node mode
600+
settingsList.add(FIPS_MODE_ENABLED);
595601

596602
// IP Filter settings
597603
IPFilter.addSettings(settingsList);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
package org.elasticsearch.xpack.security;
7+
8+
import org.elasticsearch.bootstrap.BootstrapContext;
9+
import org.elasticsearch.common.settings.Settings;
10+
import org.elasticsearch.test.ESTestCase;
11+
12+
public class FIPS140JKSKeystoreBootstrapCheckTests extends ESTestCase {
13+
14+
public void testNoKeystoreIsAllowed() {
15+
final Settings.Builder settings = Settings.builder()
16+
.put("xpack.security.fips_mode.enabled", "true");
17+
assertFalse(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
18+
}
19+
20+
public void testSSLKeystoreTypeIsNotAllowed() {
21+
final Settings.Builder settings = Settings.builder()
22+
.put("xpack.security.fips_mode.enabled", "true")
23+
.put("xpack.ssl.keystore.path", "/this/is/the/path")
24+
.put("xpack.ssl.keystore.type", "JKS");
25+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
26+
}
27+
28+
public void testSSLImplicitKeystoreTypeIsNotAllowed() {
29+
final Settings.Builder settings = Settings.builder()
30+
.put("xpack.security.fips_mode.enabled", "true")
31+
.put("xpack.ssl.keystore.path", "/this/is/the/path")
32+
.put("xpack.ssl.keystore.type", "JKS");
33+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
34+
}
35+
36+
public void testTransportSSLKeystoreTypeIsNotAllowed() {
37+
final Settings.Builder settings = Settings.builder()
38+
.put("xpack.security.fips_mode.enabled", "true")
39+
.put("xpack.security.transport.ssl.keystore.path", "/this/is/the/path")
40+
.put("xpack.security.transport.ssl.keystore.type", "JKS");
41+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
42+
}
43+
44+
public void testHttpSSLKeystoreTypeIsNotAllowed() {
45+
final Settings.Builder settings = Settings.builder()
46+
.put("xpack.security.fips_mode.enabled", "true")
47+
.put("xpack.security.http.ssl.keystore.path", "/this/is/the/path")
48+
.put("xpack.security.http.ssl.keystore.type", "JKS");
49+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
50+
}
51+
52+
public void testRealmKeystoreTypeIsNotAllowed() {
53+
final Settings.Builder settings = Settings.builder()
54+
.put("xpack.security.fips_mode.enabled", "true")
55+
.put("xpack.security.authc.realms.ldap.ssl.keystore.path", "/this/is/the/path")
56+
.put("xpack.security.authc.realms.ldap.ssl.keystore.type", "JKS");
57+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
58+
}
59+
60+
public void testImplicitRealmKeystoreTypeIsNotAllowed() {
61+
final Settings.Builder settings = Settings.builder()
62+
.put("xpack.security.fips_mode.enabled", "true")
63+
.put("xpack.security.authc.realms.ldap.ssl.keystore.path", "/this/is/the/path");
64+
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
package org.elasticsearch.xpack.security;
7+
8+
import org.elasticsearch.bootstrap.BootstrapContext;
9+
import org.elasticsearch.common.settings.Settings;
10+
import org.elasticsearch.test.ESTestCase;
11+
import org.elasticsearch.xpack.core.XPackSettings;
12+
13+
public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCase {
14+
15+
public void testPBKDF2AlgorithmIsAllowed() {
16+
Settings settings = Settings.builder().put("xpack.security.fips_mode.enabled", "true").build();
17+
18+
settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000").build();
19+
assertFalse(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
20+
21+
settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2").build();
22+
assertFalse(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
23+
}
24+
25+
public void testBCRYPTAlgorithmIsNotAllowed() {
26+
Settings settings = Settings.builder().put("xpack.security.fips_mode.enabled", "true").build();
27+
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
28+
settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT").build();
29+
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
30+
31+
settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT11").build();
32+
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
33+
}
34+
}

0 commit comments

Comments
 (0)