[7.x] Refactor FIPS BootstrapChecks to simple checks (#47499) (#48333)

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:
Ioannis Kakavas 2019-10-22 12:49:01 +03:00 committed by GitHub
parent aa29567e11
commit 24e43dfa34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 135 additions and 445 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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.");

View File

@ -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());
}
}

View File

@ -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());
}
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}

View File

@ -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);
// no exception thrown
new Security.ValidateLicenseForFIPS(true).accept(node, state);
// 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"));
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 {
@ -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
}
}