diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index 59f4951143d..9b27dc0e71d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -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 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 EXPIRATION_MESSAGES; static { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java index ae31966a347..48aa1d4024b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java @@ -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; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java deleted file mode 100644 index 6961c377f55..00000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheck.java +++ /dev/null @@ -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; - } -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java deleted file mode 100644 index 4b0d9cd2f8c..00000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheck.java +++ /dev/null @@ -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 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(); - } -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java deleted file mode 100644 index 8a754a2f25b..00000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheck.java +++ /dev/null @@ -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(); - } - -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java deleted file mode 100644 index 82a58b94a83..00000000000 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheck.java +++ /dev/null @@ -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; - } -} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 7bd57c1b02a..6eccfb251b9 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -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 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 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."); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheckTests.java deleted file mode 100644 index b35b8009f12..00000000000 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140JKSKeystoreBootstrapCheckTests.java +++ /dev/null @@ -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()); - } -} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java deleted file mode 100644 index 9f3cc0ef951..00000000000 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140LicenseBootstrapCheckTests.java +++ /dev/null @@ -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()); - } - } -} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java deleted file mode 100644 index 0dcaf1128f9..00000000000 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140PasswordHashingAlgorithmBootstrapCheckTests.java +++ /dev/null @@ -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)); - } - -} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheckTests.java deleted file mode 100644 index 5497dcfe460..00000000000 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/FIPS140SecureSettingsBootstrapCheckTests.java +++ /dev/null @@ -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); - } - } -} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 4a1cf946676..c5b019b9670 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -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 + } }