FIPS 140 bootstrap checks should not be bootstrap checks as they are always enforced. This commit moves the validation logic within the security plugin. The FIPS140SecureSettingsBootstrapCheck was not applicable as the keystore was being loaded on init, before the Bootstrap checks were checked, so an elasticsearch keystore of version < 3 would cause the node to fail in a FIPS 140 JVM before the bootstrap check kicked in, and as such hasn't been migrated. Resolves: #34772
This commit is contained in:
parent
aa29567e11
commit
24e43dfa34
|
@ -16,10 +16,12 @@ import org.elasticsearch.xpack.core.XPackSettings;
|
|||
import org.elasticsearch.xpack.core.monitoring.MonitoringField;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
@ -28,6 +30,9 @@ import java.util.function.BiFunction;
|
|||
*/
|
||||
public class XPackLicenseState {
|
||||
|
||||
public static final Set<OperationMode> FIPS_ALLOWED_LICENSE_OPERATION_MODES =
|
||||
EnumSet.of(License.OperationMode.PLATINUM, License.OperationMode.TRIAL);
|
||||
|
||||
/** Messages for each feature which are printed when the license expires. */
|
||||
static final Map<String, String[]> EXPIRATION_MESSAGES;
|
||||
static {
|
||||
|
|
|
@ -242,7 +242,7 @@ public class SSLConfigurationSettings {
|
|||
return setting.get(settings).orElseGet(() -> inferKeyStoreType(path));
|
||||
}
|
||||
|
||||
private static String inferKeyStoreType(String path) {
|
||||
public static String inferKeyStoreType(String path) {
|
||||
String name = path == null ? "" : path.toLowerCase(Locale.ROOT);
|
||||
if (name.endsWith(".p12") || name.endsWith(".pfx") || name.endsWith(".pkcs12")) {
|
||||
return PKCS12_KEYSTORE_TYPE;
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
public class FIPS140JKSKeystoreBootstrapCheck implements BootstrapCheck {
|
||||
|
||||
/**
|
||||
* 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 (XPackSettings.FIPS_MODE_ENABLED.get(context.settings())) {
|
||||
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 true;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.xpack.core.XPackSettings;
|
||||
|
||||
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);
|
||||
|
||||
@Override
|
||||
public BootstrapCheckResult check(BootstrapContext context) {
|
||||
if (XPackSettings.FIPS_MODE_ENABLED.get(context.settings())) {
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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.xpack.core.XPackSettings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class FIPS140PasswordHashingAlgorithmBootstrapCheck implements BootstrapCheck {
|
||||
|
||||
/**
|
||||
* Test if the node fails the check.
|
||||
*
|
||||
* @param context the bootstrap context
|
||||
* @return the result of the bootstrap check
|
||||
*/
|
||||
@Override
|
||||
public BootstrapCheckResult check(final BootstrapContext context) {
|
||||
if (XPackSettings.FIPS_MODE_ENABLED.get(context.settings())) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.xpack.core.XPackSettings;
|
||||
|
||||
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 = XPackSettings.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 appropriate 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;
|
||||
}
|
||||
}
|
|
@ -263,6 +263,7 @@ import java.util.stream.Collectors;
|
|||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
|
||||
import static org.elasticsearch.license.XPackLicenseState.FIPS_ALLOWED_LICENSE_OPERATION_MODES;
|
||||
import static org.elasticsearch.xpack.core.XPackSettings.API_KEY_SERVICE_ENABLED_SETTING;
|
||||
import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED;
|
||||
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
|
||||
|
@ -313,11 +314,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
new ApiKeySSLBootstrapCheck(),
|
||||
new TokenSSLBootstrapCheck(),
|
||||
new PkiRealmBootstrapCheck(getSslService()),
|
||||
new TLSLicenseBootstrapCheck(),
|
||||
new FIPS140SecureSettingsBootstrapCheck(settings, env),
|
||||
new FIPS140JKSKeystoreBootstrapCheck(),
|
||||
new FIPS140PasswordHashingAlgorithmBootstrapCheck(),
|
||||
new FIPS140LicenseBootstrapCheck()));
|
||||
new TLSLicenseBootstrapCheck()));
|
||||
checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
|
||||
this.bootstrapChecks = Collections.unmodifiableList(checks);
|
||||
Automatons.updateConfiguration(settings);
|
||||
|
@ -330,6 +327,9 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
|
||||
private static void runStartupChecks(Settings settings) {
|
||||
validateRealmSettings(settings);
|
||||
if (XPackSettings.FIPS_MODE_ENABLED.get(settings)) {
|
||||
validateForFips(settings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -882,6 +882,37 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
}
|
||||
}
|
||||
|
||||
static void validateForFips(Settings settings) {
|
||||
final List<String> validationErrors = new ArrayList<>();
|
||||
Settings keystoreTypeSettings = settings.filter(k -> k.endsWith("keystore.type"))
|
||||
.filter(k -> settings.get(k).equalsIgnoreCase("jks"));
|
||||
if (keystoreTypeSettings.isEmpty() == false) {
|
||||
validationErrors.add("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
|
||||
"revisit [" + keystoreTypeSettings.toDelimitedString(',') + "] settings");
|
||||
}
|
||||
Settings keystorePathSettings = settings.filter(k -> k.endsWith("keystore.path"))
|
||||
.filter(k -> settings.hasValue(k.replace(".path", ".type")) == false);
|
||||
if (keystorePathSettings.isEmpty() == false && SSLConfigurationSettings.inferKeyStoreType(null).equals("jks")) {
|
||||
validationErrors.add("JKS Keystores cannot be used in a FIPS 140 compliant JVM. Please " +
|
||||
"revisit [" + keystorePathSettings.toDelimitedString(',') + "] settings");
|
||||
}
|
||||
final String selectedAlgorithm = XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings);
|
||||
if (selectedAlgorithm.toLowerCase(Locale.ROOT).startsWith("pbkdf2") == false) {
|
||||
validationErrors.add("Only PBKDF2 is allowed for password hashing in a FIPS 140 JVM. Please set the " +
|
||||
"appropriate value for [ " + XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey() + " ] setting.");
|
||||
}
|
||||
|
||||
if (validationErrors.isEmpty() == false) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Validation for FIPS 140 mode failed: \n");
|
||||
int index = 0;
|
||||
for (String error : validationErrors) {
|
||||
sb.append(++index).append(": ").append(error).append(";\n");
|
||||
}
|
||||
throw new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TransportInterceptor> getTransportInterceptors(NamedWriteableRegistry namedWriteableRegistry, ThreadContext threadContext) {
|
||||
if (transportClientMode || enabled == false) { // don't register anything if we are not enabled
|
||||
|
@ -1044,7 +1075,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
if (inFipsMode) {
|
||||
License license = LicenseService.getLicense(state.metaData());
|
||||
if (license != null &&
|
||||
FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode()) == false) {
|
||||
FIPS_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.");
|
||||
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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.common.settings.Settings;
|
||||
import org.elasticsearch.test.AbstractBootstrapCheckTestCase;
|
||||
|
||||
public class FIPS140JKSKeystoreBootstrapCheckTests extends AbstractBootstrapCheckTestCase {
|
||||
|
||||
public void testNoKeystoreIsAllowed() {
|
||||
final Settings.Builder settings = Settings.builder()
|
||||
.put("xpack.security.fips_mode.enabled", "true");
|
||||
assertFalse(new FIPS140JKSKeystoreBootstrapCheck().check(createTestContext(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().check(createTestContext(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().check(createTestContext(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().check(createTestContext(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().check(createTestContext(settings.build(), null)).isFailure());
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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.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.AbstractBootstrapCheckTestCase;
|
||||
|
||||
public class FIPS140LicenseBootstrapCheckTests extends AbstractBootstrapCheckTestCase {
|
||||
|
||||
public void testBootstrapCheck() throws Exception {
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck()
|
||||
.check(emptyContext).isSuccess());
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck()
|
||||
.check(createTestContext(Settings.builder().put("xpack.security.fips_mode.enabled", randomBoolean()).build(), MetaData
|
||||
.EMPTY_META_DATA)).isSuccess());
|
||||
|
||||
MetaData.Builder builder = MetaData.builder();
|
||||
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
|
||||
TestUtils.putLicense(builder, license);
|
||||
MetaData metaData = builder.build();
|
||||
|
||||
if (FIPS140LicenseBootstrapCheck.ALLOWED_LICENSE_OPERATION_MODES.contains(license.operationMode())) {
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck().check(createTestContext(
|
||||
Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).isSuccess());
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck().check(createTestContext(
|
||||
Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
|
||||
} else {
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck().check(createTestContext(
|
||||
Settings.builder().put("xpack.security.fips_mode.enabled", false).build(), metaData)).isSuccess());
|
||||
assertTrue(new FIPS140LicenseBootstrapCheck().check(createTestContext(
|
||||
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().check(createTestContext(
|
||||
Settings.builder().put("xpack.security.fips_mode.enabled", true).build(), metaData)).getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* 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.common.settings.Settings;
|
||||
import org.elasticsearch.test.AbstractBootstrapCheckTestCase;
|
||||
import org.elasticsearch.xpack.core.XPackSettings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class FIPS140PasswordHashingAlgorithmBootstrapCheckTests extends AbstractBootstrapCheckTestCase {
|
||||
|
||||
public void testPBKDF2AlgorithmIsAllowed() {
|
||||
{
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2_10000")
|
||||
.build();
|
||||
final BootstrapCheck.BootstrapCheckResult result =
|
||||
new FIPS140PasswordHashingAlgorithmBootstrapCheck().check(createTestContext(settings, null));
|
||||
assertFalse(result.isFailure());
|
||||
}
|
||||
|
||||
{
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), "PBKDF2")
|
||||
.build();
|
||||
final BootstrapCheck.BootstrapCheckResult result =
|
||||
new FIPS140PasswordHashingAlgorithmBootstrapCheck().check(createTestContext(settings, null));
|
||||
assertFalse(result.isFailure());
|
||||
}
|
||||
}
|
||||
|
||||
public void testBCRYPTAlgorithmDependsOnFipsMode() {
|
||||
for (final Boolean fipsModeEnabled : Arrays.asList(true, false)) {
|
||||
for (final String passwordHashingAlgorithm : Arrays.asList(null, "BCRYPT", "BCRYPT11")) {
|
||||
runBCRYPTTest(fipsModeEnabled, passwordHashingAlgorithm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runBCRYPTTest(final boolean fipsModeEnabled, final String passwordHashingAlgorithm) {
|
||||
final Settings.Builder builder = Settings.builder().put(XPackSettings.FIPS_MODE_ENABLED.getKey(), fipsModeEnabled);
|
||||
if (passwordHashingAlgorithm != null) {
|
||||
builder.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(), passwordHashingAlgorithm);
|
||||
}
|
||||
final Settings settings = builder.build();
|
||||
final BootstrapCheck.BootstrapCheckResult result =
|
||||
new FIPS140PasswordHashingAlgorithmBootstrapCheck().check(createTestContext(settings, null));
|
||||
assertThat(result.isFailure(), equalTo(fipsModeEnabled));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* 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.apache.lucene.codecs.CodecUtil;
|
||||
import org.apache.lucene.store.IOContext;
|
||||
import org.apache.lucene.store.IndexOutput;
|
||||
import org.apache.lucene.store.SimpleFSDirectory;
|
||||
import org.elasticsearch.common.settings.KeyStoreWrapper;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.test.AbstractBootstrapCheckTestCase;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Base64;
|
||||
|
||||
public class FIPS140SecureSettingsBootstrapCheckTests extends AbstractBootstrapCheckTestCase {
|
||||
|
||||
public void testLegacySecureSettingsIsNotAllowed() throws Exception {
|
||||
assumeFalse("Can't run in a FIPS JVM, PBE is not available", inFipsJvm());
|
||||
final Settings.Builder builder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.security.fips_mode.enabled", "true");
|
||||
Environment env = TestEnvironment.newEnvironment(builder.build());
|
||||
generateV2Keystore(env);
|
||||
assertTrue(new FIPS140SecureSettingsBootstrapCheck(builder.build(), env).check(createTestContext(builder.build(),
|
||||
null)).isFailure());
|
||||
}
|
||||
|
||||
public void testCorrectSecureSettingsVersionIsAllowed() throws Exception {
|
||||
final Settings.Builder builder = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.security.fips_mode.enabled", "true");
|
||||
Environment env = TestEnvironment.newEnvironment(builder.build());
|
||||
final KeyStoreWrapper keyStoreWrapper = KeyStoreWrapper.create();
|
||||
try {
|
||||
keyStoreWrapper.save(env.configFile(), "password".toCharArray());
|
||||
} catch (final AccessControlException e) {
|
||||
if (e.getPermission() instanceof RuntimePermission && e.getPermission().getName().equals("accessUserInformation")) {
|
||||
// this is expected:but we don't care in tests
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
assertFalse(new FIPS140SecureSettingsBootstrapCheck(builder.build(), env).check(createTestContext(builder.build(),
|
||||
null)).isFailure());
|
||||
}
|
||||
|
||||
private void generateV2Keystore(Environment env) throws Exception {
|
||||
Path configDir = env.configFile();
|
||||
SimpleFSDirectory directory = new SimpleFSDirectory(configDir);
|
||||
byte[] fileBytes = new byte[20];
|
||||
random().nextBytes(fileBytes);
|
||||
try (IndexOutput output = directory.createOutput("elasticsearch.keystore", IOContext.DEFAULT)) {
|
||||
|
||||
CodecUtil.writeHeader(output, "elasticsearch.keystore", 2);
|
||||
output.writeByte((byte) 0); // hasPassword = false
|
||||
output.writeString("PKCS12");
|
||||
output.writeString("PBE"); // string algo
|
||||
output.writeString("PBE"); // file algo
|
||||
|
||||
output.writeVInt(2); // num settings
|
||||
output.writeString("string_setting");
|
||||
output.writeString("STRING");
|
||||
output.writeString("file_setting");
|
||||
output.writeString("FILE");
|
||||
|
||||
SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE");
|
||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||
keystore.load(null, null);
|
||||
SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray()));
|
||||
KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]);
|
||||
keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
|
||||
|
||||
byte[] base64Bytes = Base64.getEncoder().encode(fileBytes);
|
||||
char[] chars = new char[base64Bytes.length];
|
||||
for (int i = 0; i < chars.length; ++i) {
|
||||
chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok
|
||||
}
|
||||
secretKey = secretFactory.generateSecret(new PBEKeySpec(chars));
|
||||
keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter);
|
||||
|
||||
ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream();
|
||||
keystore.store(keystoreBytesStream, new char[0]);
|
||||
byte[] keystoreBytes = keystoreBytesStream.toByteArray();
|
||||
output.writeInt(keystoreBytes.length);
|
||||
output.writeBytes(keystoreBytes, keystoreBytes.length);
|
||||
CodecUtil.writeFooter(output);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import org.elasticsearch.xpack.core.security.SecurityField;
|
|||
import org.elasticsearch.xpack.core.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
|
||||
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions;
|
||||
|
@ -65,6 +66,7 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
|
||||
import static org.elasticsearch.license.XPackLicenseState.FIPS_ALLOWED_LICENSE_OPERATION_MODES;
|
||||
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
|
||||
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
@ -256,24 +258,36 @@ public class SecurityTests extends ESTestCase {
|
|||
assertThat(e.getMessage(), containsString("cannot deserialize the license format"));
|
||||
}
|
||||
|
||||
public void testJoinValidatorForFIPSLicense() throws Exception {
|
||||
public void testJoinValidatorForFIPSOnAllowedLicense() 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));
|
||||
License license =
|
||||
TestUtils.generateSignedLicense(randomFrom(FIPS_ALLOWED_LICENSE_OPERATION_MODES).toString(), 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) {
|
||||
// no exception thrown
|
||||
new Security.ValidateLicenseForFIPS(true).accept(node, state);
|
||||
} else {
|
||||
// no exception thrown
|
||||
}
|
||||
|
||||
public void testJoinValidatorForFIPSOnForbiddenLicense() throws Exception {
|
||||
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(),
|
||||
VersionUtils.randomVersionBetween(random(), null, Version.CURRENT));
|
||||
MetaData.Builder builder = MetaData.builder();
|
||||
final String forbiddenLicenseType =
|
||||
randomFrom(Arrays.stream(License.OperationMode.values())
|
||||
.filter(l -> FIPS_ALLOWED_LICENSE_OPERATION_MODES.contains(l) == false).collect(Collectors.toList())).toString();
|
||||
License license = TestUtils.generateSignedLicense(forbiddenLicenseType, TimeValue.timeValueHours(24));
|
||||
TestUtils.putLicense(builder, license);
|
||||
ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(builder.build()).build();
|
||||
new Security.ValidateLicenseForFIPS(false).accept(node, state);
|
||||
// no exception thrown
|
||||
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 {
|
||||
|
@ -409,4 +423,71 @@ public class SecurityTests extends ESTestCase {
|
|||
Security.validateRealmSettings(settings);
|
||||
// no-exception
|
||||
}
|
||||
|
||||
public void testValidateForFipsKeystoreWithImplicitJksType() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
|
||||
randomFrom(Hasher.getAvailableAlgoStoredHash().stream()
|
||||
.filter(alg -> alg.startsWith("pbkdf2") == false).collect(Collectors.toList())))
|
||||
.build();
|
||||
final IllegalArgumentException iae =
|
||||
expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
|
||||
assertThat(iae.getMessage(), containsString("JKS Keystores cannot be used in a FIPS 140 compliant JVM"));
|
||||
}
|
||||
|
||||
public void testValidateForFipsKeystoreWithExplicitJksType() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
|
||||
.put("xpack.security.transport.ssl.keystore.type", "JKS")
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
|
||||
randomFrom(Hasher.getAvailableAlgoStoredHash().stream()
|
||||
.filter(alg -> alg.startsWith("pbkdf2")).collect(Collectors.toList())))
|
||||
.build();
|
||||
final IllegalArgumentException iae =
|
||||
expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
|
||||
assertThat(iae.getMessage(), containsString("JKS Keystores cannot be used in a FIPS 140 compliant JVM"));
|
||||
}
|
||||
|
||||
public void testValidateForFipsInvalidPasswordHashingAlgorithm() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
|
||||
randomFrom(Hasher.getAvailableAlgoStoredHash().stream()
|
||||
.filter(alg -> alg.startsWith("pbkdf2") == false).collect(Collectors.toList())))
|
||||
.build();
|
||||
final IllegalArgumentException iae =
|
||||
expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
|
||||
assertThat(iae.getMessage(), containsString("Only PBKDF2 is allowed for password hashing in a FIPS 140 JVM."));
|
||||
}
|
||||
|
||||
public void testValidateForFipsMultipleValidationErrors() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
|
||||
.put("xpack.security.transport.ssl.keystore.type", "JKS")
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
|
||||
randomFrom(Hasher.getAvailableAlgoStoredHash().stream()
|
||||
.filter(alg -> alg.startsWith("pbkdf2") == false).collect(Collectors.toList())))
|
||||
.build();
|
||||
final IllegalArgumentException iae =
|
||||
expectThrows(IllegalArgumentException.class, () -> Security.validateForFips(settings));
|
||||
assertThat(iae.getMessage(), containsString("JKS Keystores cannot be used in a FIPS 140 compliant JVM"));
|
||||
assertThat(iae.getMessage(), containsString("Only PBKDF2 is allowed for password hashing in a FIPS 140 JVM."));
|
||||
}
|
||||
|
||||
public void testValidateForFipsNoErrors() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(XPackSettings.FIPS_MODE_ENABLED.getKey(), true)
|
||||
.put("xpack.security.transport.ssl.keystore.path", "path/to/keystore")
|
||||
.put("xpack.security.transport.ssl.keystore.type", "BCFKS")
|
||||
.put(XPackSettings.PASSWORD_HASHING_ALGORITHM.getKey(),
|
||||
randomFrom(Hasher.getAvailableAlgoStoredHash().stream()
|
||||
.filter(alg -> alg.startsWith("pbkdf2")).collect(Collectors.toList())))
|
||||
.build();
|
||||
Security.validateForFips(settings);
|
||||
// no exception thrown
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue