Skip to content

Commit

Permalink
Introduce fips_mode setting and associated checks (#32326)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jkakavas committed Jul 25, 2018
1 parent 4f5f362 commit b687a50
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ private KeyStoreWrapper(int formatVersion, boolean hasPassword, byte[] dataBytes
this.dataBytes = dataBytes;
}

/**
* Get the metadata format version for the keystore
**/
public int getFormatVersion() {
return formatVersion;
}

/** Returns a path representing the ES keystore in the given config dir. */
public static Path keystorePath(Path configDir) {
return configDir.resolve(KEYSTORE_FILENAME);
Expand Down Expand Up @@ -593,8 +600,10 @@ private void ensureOpen() {
@Override
public synchronized void close() {
this.closed = true;
for (Entry entry : entries.get().values()) {
Arrays.fill(entry.bytes, (byte)0);
if (null != entries.get() && entries.get().isEmpty() == false) {
for (Entry entry : entries.get().values()) {
Arrays.fill(entry.bytes, (byte) 0);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.common.settings.Settings;


public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {

private final boolean fipsModeEnabled;

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

/**
* Test if the node fails the check.
*
* @param context the bootstrap context
* @return the result of the bootstrap check
*/
@Override
public BootstrapCheckResult check(BootstrapContext context) {

if (fipsModeEnabled) {
final Settings settings = context.settings;
Settings keystoreTypeSettings = settings.filter(k -> k.endsWith("keystore.type"))
.filter(k -> settings.get(k).equalsIgnoreCase("jks"));
if (keystoreTypeSettings.isEmpty() == false) {
return BootstrapCheckResult.failure("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
"revisit [" + keystoreTypeSettings.toDelimitedString(',') + "] settings");
}
// Default Keystore type is JKS if not explicitly set
Settings keystorePathSettings = settings.filter(k -> k.endsWith("keystore.path"))
.filter(k -> settings.hasValue(k.replace(".path", ".type")) == false);
if (keystorePathSettings.isEmpty() == false) {
return BootstrapCheckResult.failure("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
"revisit [" + keystorePathSettings.toDelimitedString(',') + "] settings");
}

}
return BootstrapCheckResult.success();
}

@Override
public boolean alwaysEnforce() {
return fipsModeEnabled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.common.settings.Settings;
import org.elasticsearch.xpack.core.XPackSettings;

import java.util.Locale;

public class FIPS140PasswordHashingAlgorithmBootstrapCheck implements BootstrapCheck {

private final boolean fipsModeEnabled;

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

/**
* Test if the node fails the check.
*
* @param context the bootstrap context
* @return the result of the bootstrap check
*/
@Override
public BootstrapCheckResult check(BootstrapContext context) {
final String selectedAlgorithm = XPackSettings.PASSWORD_HASHING_ALGORITHM.get(context.settings);
if (selectedAlgorithm.toLowerCase(Locale.ROOT).startsWith("pbkdf2") == false) {
return BootstrapCheckResult.failure("Only PBKDF2 is allowed for password hashing in a FIPS-140 JVM. Please set the " +
"appropriate value for [ " + XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey() + " ] setting.");
}
return BootstrapCheckResult.success();
}

@Override
public boolean alwaysEnforce() {
return fipsModeEnabled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;

import java.io.IOException;
import java.io.UncheckedIOException;

public class FIPS140SecureSettingsBootstrapCheck implements BootstrapCheck {

private final boolean fipsModeEnabled;
private final Environment environment;

FIPS140SecureSettingsBootstrapCheck(Settings settings, Environment environment) {
this.fipsModeEnabled = Security.FIPS_MODE_ENABLED.get(settings);
this.environment = environment;
}

/**
* Test if the node fails the check.
*
* @param context the bootstrap context
* @return the result of the bootstrap check
*/
@Override
public BootstrapCheckResult check(BootstrapContext context) {
if (fipsModeEnabled) {
try (KeyStoreWrapper secureSettings = KeyStoreWrapper.load(environment.configFile())) {
if (secureSettings != null && secureSettings.getFormatVersion() < 3) {
return BootstrapCheckResult.failure("Secure settings store is not of the latest version. Please use " +
"bin/elasticsearch-keystore create to generate a new secure settings store and migrate the secure settings there.");
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return BootstrapCheckResult.success();
}

@Override
public boolean alwaysEnforce() {
return fipsModeEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ 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 @@ -311,6 +313,9 @@ public Security(Settings settings, final Path configPath) {
new PkiRealmBootstrapCheck(getSslService()),
new TLSLicenseBootstrapCheck(),
new PasswordHashingAlgorithmBootstrapCheck(),
new FIPS140SecureSettingsBootstrapCheck(settings, env),
new FIPS140JKSKeystoreBootstrapCheck(settings),
new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings),
new KerberosRealmBootstrapCheck(env)));
checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
this.bootstrapChecks = Collections.unmodifiableList(checks);
Expand Down Expand Up @@ -613,6 +618,7 @@ public static List<Setting<?>> getSettings(boolean transportClientMode, List<Sec
}

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

// IP Filter settings
IPFilter.addSettings(settingsList);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;

public class FIPS140JKSKeystoreBootstrapCheckTests extends ESTestCase {

public void testNoKeystoreIsAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true");
assertFalse(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testSSLKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.ssl.keystore.path", "/this/is/the/path")
.put("xpack.ssl.keystore.type", "JKS");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testSSLImplicitKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.ssl.keystore.path", "/this/is/the/path")
.put("xpack.ssl.keystore.type", "JKS");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testTransportSSLKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.security.transport.ssl.keystore.path", "/this/is/the/path")
.put("xpack.security.transport.ssl.keystore.type", "JKS");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testHttpSSLKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.security.http.ssl.keystore.path", "/this/is/the/path")
.put("xpack.security.http.ssl.keystore.type", "JKS");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testRealmKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.security.authc.realms.ldap.ssl.keystore.path", "/this/is/the/path")
.put("xpack.security.authc.realms.ldap.ssl.keystore.type", "JKS");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}

public void testImplicitRealmKeystoreTypeIsNotAllowed() {
final Settings.Builder settings = Settings.builder()
.put("xpack.security.fips_mode.enabled", "true")
.put("xpack.security.authc.realms.ldap.ssl.keystore.path", "/this/is/the/path");
assertTrue(new FIPS140JKSKeystoreBootstrapCheck(settings.build()).check(new BootstrapContext(settings.build(), null)).isFailure());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.XPackSettings;

public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends ESTestCase {

public void testPBKDF2AlgorithmIsAllowed() {
Settings settings = Settings.builder().put("xpack.security.fips_mode.enabled", "true").build();

settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000").build();
assertFalse(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());

settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2").build();
assertFalse(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
}

public void testBCRYPTAlgorithmIsNotAllowed() {
Settings settings = Settings.builder().put("xpack.security.fips_mode.enabled", "true").build();
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT").build();
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());

settings = Settings.builder().put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "BCRYPT11").build();
assertTrue(new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings).check(new BootstrapContext(settings, null)).isFailure());
}
}
Loading

0 comments on commit b687a50

Please sign in to comment.