Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for FIPS compliance mode #14912

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

beanuwave
Copy link

@beanuwave beanuwave commented Jul 23, 2024

Description

This PR makes FIPS mode available through the OPENSEARCH_CRYPTO_STANDARD=FIPS-140-3 environmental parameter instead of the tests.fips.enabled setting. It provides FIPS 140-3 support by replacing all BC dependencies with BCFIPS dependencies and making FIPS approved-only mode configurable at launch. Running this mode restricts the BCFIPS provider to rely solely on FIPS-certified ciphers.

  • The fips.gradle build script is removed in order to support a single-build solution.
  • All BC dependencies are replaced by BCFIPS.
  • Fixed creation of password protected internal keystore for valuable settings at node start. The add-string command was made with additional --stdin option, which interferes with password input.
  • The Password Matcher inside Identity-Shiro that relies on BC to check if hashed passwords match with OpenBSDBCrypt is replaced by the password4j implementation.
  • Adds full support for the BCFKS format (*.bcfks) for key and trust stores, also making it the default.
  • KeyStore instantiation was added to forbidden-apis in favour of KeyStoreFactory.
  • Google's truststore is converted to the BCFKS format.
  • Makes the best guess of which store type is provided based on the filename extension.
  • Store types are strictly limited to JKS, PKCS12, PKCS11, and BCFKS.
  • Refactors PemUtils to parse private keys in formats EC, PKCS8, PKCS1, and DSA, with or without encryption, and with or without parameters.
  • dependency ':libs:opensearch-common' was added to rest-client build, to support strict keystore types. It's also the reason for JVM bump JAVA8 to JAVA11.
  • allow ingest-attachment plugin to run in FIPS mode, since BC dependencies find no use.
  • The java.security file is added to the build to distinguish between FIPS and non-FIPS environments.
  • The fips_java.security file is altered due to evolving security standards.
  • The security.policy file is altered to grant necessary security permissions.
  • Increased security standards in KeyTabs and algorithms for Kerberos.
  • SecureRandom gets instantiated in to different way, depending on if it runs with FIPS or not.
  • Uninstalls SunJCE provider from security providers list at runtime when FIPS mode is enabled.

Runtime limitations (known so far) that come with enabling FIPS mode:

Admins can continue to manage their systems without being impacted by this change. However, for those keen on FIPS compliance, the most common obstacle will likely be the requirement to set a stronger password for the internal keystore and also convert key and truststores to *.bcfks format.

  • Does not allow empty passwords for keystores or private keys (they need to be at least 112 bits in strength).
  • The ssl.verification_mode=NONE setting is not permitted.
  • JKS and PKCS12 key and trust store types are not supported at all.
  • The internal keystore cannot be auto-migrated from versions 1 or 2.
  • Azure Classic Discovery plugin -> deprecated.
  • SQL-CLI client.
  • HDFS plugin won't connect since it's using RC4 cipher for token authentication.

Reasons for refactoring PemUtils, which is used by the Reindex API in cases of migrating data from a remote cluster that is TLS protected:

  • Lack of support for evolving standards like PKCS#8.
  • Password-Based Key Derivation Functions such as PBKDF-OPENSSL are not supported in FIPS mode in favor of the PBKDF2 standard.
  • Java type safety.
  • It is generally a good idea to let ASN1 annotation parsing be done by external security libraries.

Related Issues

opensearch-project/security#3420

Check List

  • Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Copy link
Contributor

❌ Gradle check result for 6016d5d: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@beanuwave beanuwave changed the title Draft to allow run in FIPS compliace mode Draft to allow run in FIPS compliance mode Jul 24, 2024
Copy link
Contributor

❌ Gradle check result for 8e8ed47: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Contributor

❌ Gradle check result for 6016d5d: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@dblock
Copy link
Member

dblock commented Jul 24, 2024

Could use some help maybe from @cwperks or @peternied reviewing this, please.

Signed-off-by: Iwan Igonin <[email protected]>
Summery:
- replace unsecure kerberos crypto algorithms
- add 'java.security.KeyStore' to forbidden-apis
- instantiate and use SecureRandom from BCFIPS library
- exclude SunJCE from security providers list at runtime, when running in FIPS JVM
- exclude Azure tests when running in FIPS JVM

Signed-off-by: Iwan Igonin <[email protected]>
Copy link
Contributor

❌ Gradle check result for 5698702: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Contributor

❌ Gradle check result for e98bfce: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Copy link
Contributor

❕ Gradle check result for f775677: UNSTABLE

Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure.

@beanuwave
Copy link
Author

Thanks everyone for taking the time to review this PR. The last commit includes following changes:

  • Move KeyStoreFactory and KeyStoreType from the :libs:opensearch-common package to :libs:opensearch-ssl-config.
  • Always run keystore tests with a password if FIPS mode is on.
  • Shorten cryptoStandard != null && cryptoStandard.equals("FIPS-140-3") statements.
  • Revert changes to SecureRandomHolder.

Next I will focus on the two unresolved comments from above.

// test with FIPS-140-3 enabled
plugins.withType(JavaPlugin).configureEach {
tasks.withType(Test).configureEach { testTask ->
if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use BuildParams consistently please:

Suggested change
if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') {
if (BuildParams.inFipsJvm == true) {

@@ -239,12 +240,17 @@ private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOExce
return store;
}

@SuppressForbiddenApi("runs exclusively in test-context without KeyStoreFactory on classpath.")
Copy link
Collaborator

@reta reta Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@SuppressForbiddenApi("runs exclusively in test-context without KeyStoreFactory on classpath.")
@SuppressForbidden

import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.impldep.com.jcraft.jsch.annotations.SuppressForbiddenApi;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import org.gradle.internal.impldep.com.jcraft.jsch.annotations.SuppressForbiddenApi;
import de.thetaphi.forbiddenapis.SuppressForbidden;

@@ -123,6 +123,8 @@ dependencies {
api 'org.jruby.joni:joni:2.2.1'
api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}"
api "org.ajoberstar.grgit:grgit-core:5.2.1"
api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}"
Copy link
Collaborator

@reta reta Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is optional dependency and should not be included by default (this is build tooling which mostly every single plugin will depend upon:

Suggested change
api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}"
if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') {
api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}"
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we sadly cannot use BuildParams here)

@@ -112,6 +113,8 @@ public void apply(Project project) {
BuildParams.init(params -> {
// Initialize global build parameters
boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null;
var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be useful to extract this into FipsBuild helper class (or similar), so we could consolidate OPENSEARCH_CRYPTO_STANDARD usage in 1 place:

public final class FipsBuildParams {
    private static final String fipsMode = System.getenv("OPENSEARCH_CRYPTO_STANDARD");
    
    private FipsBuildParams() {
    }
    
    public static boolean isInFipsMode() {
        return "FIPS-140-3".equals(fipsMode);
    }
    
    public static String getFipsMode() {
        return fipsMode;
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private SSLContext createSslContext(KeyStore trustStore) throws GeneralSecurityException {
checkForTrustEntry(trustStore);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(new KeyManager[0], tmf.getTrustManagers(), new SecureRandom());
sslContext.init(new KeyManager[0], tmf.getTrustManagers(), CryptoServicesRegistrar.getSecureRandom());
Copy link
Collaborator

@reta reta Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since FIPS dependency has to be optional, we need to make its usage so as well:

Suggested change
sslContext.init(new KeyManager[0], tmf.getTrustManagers(), CryptoServicesRegistrar.getSecureRandom());
private static final class SecureRandomProvider {
private final static MethodHandle MH_BC_SECURE_RANDOM;
static {
MethodHandle mh = null;
if (FipsBuildParams.isInFipsMode()) {
try {
final Class<?> cryptoServicesRegistrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar");
mh = MethodHandles.publicLookup()
.findStatic(cryptoServicesRegistrarClass, "getSecureRandom", MethodType.methodType(SecureRandom.class));
} catch (final Throwable ex) {
throw new SecurityException("Unable to find org.bouncycastle.crypto.CryptoServicesRegistrar class", ex);
}
}
MH_BC_SECURE_RANDOM = mh;
}
public static SecureRandom getSecureRandom() throws GeneralSecurityException {
if (MH_BC_SECURE_RANDOM == null) {
return new SecureRandom();
} else try {
return (SecureRandom) MH_BC_SECURE_RANDOM.invoke();
} catch (final Throwable ex) {
throw new GeneralSecurityException(ex);
}
}
}
...
sslContext.init(new KeyManager[0], tmf.getTrustManagers(), SecureRandomProvider.getSecureRandom());

@@ -43,6 +43,12 @@ dependencies {
api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
api "junit:junit:${versions.junit}"
api "org.hamcrest:hamcrest:${versions.hamcrest}"
api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dependencies should be conditional on FIPS mode:

import org.opensearch.gradle.info.BuildParams



if (BuildParams.inFipsJvm == true) {
    api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}"
    api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}"

}

api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}"
}

tasks.named("dependencyLicenses").configure {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (BuildParams.inFipsJvm == true) {
  tasks.named("dependencyLicenses").configure {
    mapping from: /bc.*/, to: 'bouncycastle'
  }
}

@@ -51,12 +51,15 @@ dependencies {
api "commons-codec:commons-codec:${versions.commonscodec}"
api "commons-logging:commons-logging:${versions.commonslogging}"
api "org.slf4j:slf4j-api:${versions.slf4j}"
api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}"
import org.opensearch.gradle.info.BuildParams
...
if (BuildParams.inFipsJvm == true) {
api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}"
}

@@ -34,8 +34,8 @@ apply plugin: 'opensearch.build'
apply plugin: 'opensearch.publish'

java {
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_11
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this, Rest Client must use JDK 8 baseline

@reta
Copy link
Collaborator

reta commented Jan 13, 2025

@beanuwave thanks for working on this, I went through the large chunk of changes and there is one major issue that bothers me (or I do not understand something). The FIPS mode compliance is optional (at least at the moment) but its leaks everywhere as a mandatory prerequisite now:

  • through BC fips dependencies
  • through BC fips providers
  • through java security policy

We have to make it truly optional, otherwise it is becoming a major breaking change (in my opinion).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.