Skip to content

Commit

Permalink
Enable tests in FIPS 140 in JDK 11 (elastic#48378)
Browse files Browse the repository at this point in the history
This change enables us to run our test suites in JVMs configured in
FIPS 140 approved mode. It does so by:

- Using BouncyCastle FIPS Cryptographic provider and BSJSSE in
FIPS mode. These are used as testRuntime dependencies for unit
tests and internal clusters, and copied (relevant jars)
explicitly to the lib directory for testclusters used in REST tests

- Configuring any given runtime Java in FIPS mode with the bundled
policy and security properties files, setting the system
properties java.security.properties and java.security.policy
with the == operator that overrides the default JVM properties
and policy.

Running the tests in FIPS 140 approved mode doesn't require an
additional configuration either in CI workers or locally and is
controlled by specifying -Dtests.fips.enabled=true

Closes: elastic#37250
Supersedes: elastic#41024
  • Loading branch information
jkakavas committed Nov 22, 2019
1 parent c239a6a commit 1d7867d
Show file tree
Hide file tree
Showing 27 changed files with 513 additions and 220 deletions.
1 change: 1 addition & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ if (project != rootProject) {
exclude '**/*.p12'
// the file that actually defines nocommit
exclude '**/ForbiddenPatternsTask.java'
exclude '**/*.bcfks'
}

testingConventions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,12 @@ import groovy.transform.CompileStatic
import org.apache.commons.io.IOUtils
import org.elasticsearch.gradle.info.BuildParams
import org.elasticsearch.gradle.info.GlobalBuildInfoPlugin
import org.elasticsearch.gradle.info.GlobalInfoExtension
import org.elasticsearch.gradle.info.JavaHome
import org.elasticsearch.gradle.precommit.DependencyLicensesTask
import org.elasticsearch.gradle.precommit.PrecommitTasks
import org.elasticsearch.gradle.test.ErrorReportingTestListener
import org.elasticsearch.gradle.testclusters.ElasticsearchCluster
import org.elasticsearch.gradle.testclusters.TestClustersPlugin
import org.elasticsearch.gradle.tool.Boilerplate
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.InvalidUserDataException
Expand Down Expand Up @@ -139,31 +138,50 @@ class BuildPlugin implements Plugin<Project> {
configureTestTasks(project)
configurePrecommit(project)
configureDependenciesInfo(project)

configureFips140(project)
}

public static void configureFips140(Project project) {
// Need to do it here to support external plugins
GlobalInfoExtension globalInfo = project.rootProject.extensions.getByType(GlobalInfoExtension)
// wait until global info is populated because we don't know if we are running in a fips jvm until execution time
globalInfo.ready {
// Common config when running with a FIPS-140 runtime JVM
if (BuildParams.inFipsJvm) {
project.tasks.withType(Test).configureEach { Test task ->
task.systemProperty 'javax.net.ssl.trustStorePassword', 'password'
task.systemProperty 'javax.net.ssl.keyStorePassword', 'password'
}
project.pluginManager.withPlugin("elasticsearch.testclusters") {
NamedDomainObjectContainer<ElasticsearchCluster> testClusters = project.extensions.findByName(TestClustersPlugin.EXTENSION_NAME) as NamedDomainObjectContainer<ElasticsearchCluster>
if (testClusters != null) {
testClusters.all { ElasticsearchCluster cluster ->
cluster.systemProperty 'javax.net.ssl.trustStorePassword', 'password'
cluster.systemProperty 'javax.net.ssl.keyStorePassword', 'password'
}
}
static void configureFips140(Project project) {
// Common config when running with a FIPS-140 runtime JVM
if (inFipsJvm()) {
ExportElasticsearchBuildResourcesTask buildResources = project.tasks.getByName('buildResources') as ExportElasticsearchBuildResourcesTask
File securityProperties = buildResources.copy("fips_java.security")
File securityPolicy = buildResources.copy("fips_java.policy")
File bcfksKeystore = buildResources.copy("cacerts.bcfks")
// This configuration can be removed once system modules are available
Boilerplate.maybeCreate(project.configurations, 'extraJars') {
project.dependencies.add('extraJars', "org.bouncycastle:bc-fips:1.0.1")
project.dependencies.add('extraJars', "org.bouncycastle:bctls-fips:1.0.9")
}
project.pluginManager.withPlugin("elasticsearch.testclusters") {
NamedDomainObjectContainer<ElasticsearchCluster> testClusters = project.extensions.findByName(TestClustersPlugin.EXTENSION_NAME) as NamedDomainObjectContainer<ElasticsearchCluster>
testClusters.all { ElasticsearchCluster cluster ->
for (File dep : project.getConfigurations().getByName("extraJars").getFiles()){
cluster.extraJarFile(dep)
}
cluster.extraConfigFile("fips_java.security", securityProperties)
cluster.extraConfigFile("fips_java.policy", securityPolicy)
cluster.extraConfigFile("cacerts.bcfks", bcfksKeystore)
cluster.systemProperty('java.security.properties', '=${ES_PATH_CONF}/fips_java.security')
cluster.systemProperty('java.security.policy', '=${ES_PATH_CONF}/fips_java.policy')
cluster.systemProperty('javax.net.ssl.trustStore', '${ES_PATH_CONF}/cacerts.bcfks')
cluster.systemProperty('javax.net.ssl.trustStorePassword', 'password')
cluster.systemProperty('javax.net.ssl.keyStorePassword', 'password')
cluster.systemProperty('javax.net.ssl.keyStoreType', 'BCFKS')
}
}
project.tasks.withType(Test).configureEach { Test task ->
task.dependsOn(buildResources)
task.systemProperty('javax.net.ssl.trustStorePassword', 'password')
task.systemProperty('javax.net.ssl.keyStorePassword', 'password')
task.systemProperty('javax.net.ssl.trustStoreType', 'BCFKS')
// Using the key==value format to override default JVM security settings and policy
// see also: https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html
task.systemProperty('java.security.properties', String.format(Locale.ROOT, "=%s", securityProperties.toString()))
task.systemProperty('java.security.policy', String.format(Locale.ROOT, "=%s", securityPolicy.toString()))
task.systemProperty('javax.net.ssl.trustStore', bcfksKeystore.toString())
}

}
}

Expand Down Expand Up @@ -655,12 +673,6 @@ class BuildPlugin implements Plugin<Project> {
project.mkdir(heapdumpDir)
project.mkdir(test.workingDir)

if (BuildParams.inFipsJvm) {
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}FIPS")
} else {
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}")
}

if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_1_9) {
test.jvmArgs '--illegal-access=warn'
}
Expand All @@ -671,7 +683,10 @@ class BuildPlugin implements Plugin<Project> {
test.systemProperty ('java.locale.providers','SPI,COMPAT')
}
}

if (inFipsJvm()) {
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bc-fips:1.0.1")
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bctls-fips:1.0.9")
}
test.jvmArgumentProviders.add(nonInputProperties)
test.extensions.add('nonInputProperties', nonInputProperties)

Expand Down Expand Up @@ -813,4 +828,8 @@ class BuildPlugin implements Plugin<Project> {
})
}
}

private static inFipsJvm(){
return Boolean.parseBoolean(System.getProperty("tests.fips.enabled"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,7 @@ public void generate() {
runtimeJavaVersionDetails = findJavaVersionDetails(runtimeJavaHome);
runtimeJavaVersionEnum = JavaVersion.toVersion(findJavaSpecificationVersion(runtimeJavaHome));

// We don't expect Gradle to be running in a FIPS JVM
String inFipsJvmScript = "print(java.security.Security.getProviders()[0].name.toLowerCase().contains(\"fips\"));";
inFipsJvm = Boolean.parseBoolean(runJavaAsScript(runtimeJavaHome, inFipsJvmScript));
inFipsJvm = Boolean.parseBoolean(System.getProperty("tests.fips.enabled"));
} else {
throw new RuntimeException("Runtime Java home path of '" + compilerJavaHome + "' does not exist");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@ public void extraConfigFile(String destination, File from, PropertyNormalization
nodes.all(node -> node.extraConfigFile(destination, from, normalization));
}

@Override
public void extraJarFile(File from) {
nodes.all(node -> node.extraJarFile(from));
}

@Override
public void user(Map<String, String> userSpec) {
nodes.all(node -> node.user(userSpec));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
private final LazyPropertyMap<String, CharSequence> environment = new LazyPropertyMap<>("Environment", this);
private final LazyPropertyList<CharSequence> jvmArgs = new LazyPropertyList<>("JVM arguments", this);
private final LazyPropertyMap<String, File> extraConfigFiles = new LazyPropertyMap<>("Extra config files", this, FileEntry::new);
private final LazyPropertyList<File> extraJarFiles = new LazyPropertyList<>("Extra jar files", this);
private final List<Map<String, String>> credentials = new ArrayList<>();
final LinkedHashMap<String, String> defaultConfig = new LinkedHashMap<>();

Expand Down Expand Up @@ -454,6 +455,8 @@ public synchronized void start() {

copyExtraConfigFiles();

copyExtraJars();

if (isSettingTrue("xpack.security.enabled")) {
if (credentials.isEmpty()) {
user(Collections.emptyMap());
Expand Down Expand Up @@ -530,6 +533,25 @@ private void copyExtraConfigFiles() {
});
}

/**
* Copies extra jars to the `/lib` directory.
* //TODO: Remove this when system modules are available
*/
private void copyExtraJars() {
if (extraJarFiles.isEmpty() == false){
logToProcessStdout("Setting up " + extraJarFiles.size() + " additional jar dependencies");
}
extraJarFiles.forEach(from -> {
Path destination = getDistroDir().resolve("lib").resolve(from.getName());
try {
Files.copy(from.toPath(), destination, StandardCopyOption.REPLACE_EXISTING);
LOGGER.info("Added extra jar {} to {}", from.getName(), destination);
} catch (IOException e) {
throw new UncheckedIOException("Can't copy extra jar dependency " + from.getName() + " to " + destination.toString(), e);
}
});
}

private void installModules() {
if (testDistribution == TestDistribution.INTEG_TEST) {
logToProcessStdout("Installing " + modules.size() + "modules");
Expand Down Expand Up @@ -576,6 +598,14 @@ public void extraConfigFile(String destination, File from, PropertyNormalization
extraConfigFiles.put(destination, from, normalization);
}

@Override
public void extraJarFile(File from) {
if (from.toString().endsWith(".jar") == false) {
throw new IllegalArgumentException("extra jar file " + from.toString() + " doesn't appear to be a JAR");
}
extraJarFiles.add(from);
}

@Override
public void user(Map<String, String> userSpec) {
Set<String> keys = new HashSet<>(userSpec.keySet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public interface TestClusterConfiguration {

void extraConfigFile(String destination, File from, PropertyNormalization normalization);

void extraJarFile(File from);

void user(Map<String, String> userSpec);

String getHttpSocketURI();
Expand Down
Binary file added buildSrc/src/main/resources/cacerts.bcfks
Binary file not shown.
16 changes: 16 additions & 0 deletions buildSrc/src/main/resources/fips_java.policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
grant {
permission java.security.SecurityPermission "putProviderProperty.BCFIPS";
permission java.security.SecurityPermission "putProviderProperty.BCJSSE";
permission java.lang.RuntimePermission "getProtectionDomain";
permission java.util.PropertyPermission "java.runtime.name", "read";
permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled";
//io.netty.handler.codec.DecoderException
permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec";
//java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.util.PropertyPermission "intellij.debug.agent", "read";
permission java.util.PropertyPermission "intellij.debug.agent", "write";
permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey";
permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey";
permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read";
};
50 changes: 50 additions & 0 deletions buildSrc/src/main/resources/fips_java.security
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
security.provider.3=SUN
securerandom.source=file:/dev/urandom
securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN
securerandom.drbg.config=
login.configuration.provider=sun.security.provider.ConfigFile
policy.provider=sun.security.provider.PolicyFile
policy.expandProperties=true
policy.allowSystemProperty=true
policy.ignoreIdentityScope=false
keystore.type=BCFKS
keystore.type.compat=true
package.access=sun.misc.,\
sun.reflect.
package.definition=sun.misc.,\
sun.reflect.
security.overridePropertiesFile=true
ssl.KeyManagerFactory.algorithm=PKIX
ssl.TrustManagerFactory.algorithm=PKIX
networkaddress.cache.negative.ttl=10
krb5.kdc.bad.policy = tryLast
jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \
RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224
jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \
DSA keySize < 1024
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \
EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC
jdk.tls.legacyAlgorithms= \
K_NULL, C_NULL, M_NULL, \
DH_anon, ECDH_anon, \
RC4_128, RC4_40, DES_CBC, DES40_CBC, \
3DES_EDE_CBC
jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37
crypto.policy=unlimited
jdk.xml.dsig.secureValidationPolicy=\
disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
maxTransforms 5,\
maxReferences 30,\
disallowReferenceUriSchemes file http https,\
minKeySize RSA 1024,\
minKeySize DSA 1024,\
minKeySize EC 224,\
noDuplicateIds,\
noRetrievalMethodLoops
jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\
java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!*
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -87,8 +88,13 @@ public void accept(final int lineNumber, final String line) {
.filter(s -> s.trim().isEmpty() == false)
.collect(Collectors.toList()));
}
final Map<String, String> substitutions = new HashMap<>();
substitutions.put("ES_TMPDIR", System.getenv("ES_TMPDIR"));
if (null != System.getenv("ES_PATH_CONF")){
substitutions.put("ES_PATH_CONF", System.getenv("ES_PATH_CONF"));
}
final List<String> substitutedJvmOptions =
substitutePlaceholders(jvmOptions, Collections.singletonMap("ES_TMPDIR", System.getenv("ES_TMPDIR")));
substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions));
final List<String> ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions);
final List<String> systemJvmOptions = SystemJvmOptions.systemJvmOptions();
final List<String> finalJvmOptions =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class SslConfiguration {
SSLContext.getInstance("TLSv1.3");
protocolAlgorithmMap.put("TLSv1.3", "TLSv1.3");
} catch (NoSuchAlgorithmException e) {
// ignore since we support JVMs that do not support TLSv1.3
// ignore since we support JVMs (and BC JSSE in FIPS mode) that do not support TLSv1.3
}
protocolAlgorithmMap.put("TLSv1.2", "TLSv1.2");
protocolAlgorithmMap.put("TLSv1.1", "TLSv1.1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -2253,6 +2252,6 @@ public static Index resolveIndex(String index) {
}

public static boolean inFipsJvm() {
return Security.getProviders()[0].getName().toLowerCase(Locale.ROOT).contains("fips");
return Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.security.Security;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -193,6 +192,8 @@ public static void resetPortCounter() {

public static final String DEFAULT_TEST_WORKER_ID = "--not-gradle--";

public static final String FIPS_SYSPROP = "tests.fips.enabled";

static {
TEST_WORKER_VM_ID = System.getProperty(TEST_WORKER_SYS_PROPERTY, DEFAULT_TEST_WORKER_ID);
setTestSysProps();
Expand Down Expand Up @@ -1353,7 +1354,7 @@ private static boolean isUnusableLocale() {
}

public static boolean inFipsJvm() {
return Security.getProviders()[0].getName().toLowerCase(Locale.ROOT).contains("fips");
return Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ private ReproduceErrorMessageBuilder appendESProperties() {
appendOpt("tests.distribution", System.getProperty("tests.distribution"));
appendOpt("compiler.java", System.getProperty("compiler.java"));
appendOpt("runtime.java", System.getProperty("runtime.java"));
appendOpt("javax.net.ssl.keyStorePassword", System.getProperty("javax.net.ssl.keyStorePassword"));
appendOpt("javax.net.ssl.trustStorePassword", System.getProperty("javax.net.ssl.trustStorePassword"));
appendOpt(ESTestCase.FIPS_SYSPROP, System.getProperty(ESTestCase.FIPS_SYSPROP));
return this;
}

Expand Down
Loading

0 comments on commit 1d7867d

Please sign in to comment.