Skip to content

Commit 1d7867d

Browse files
committed
Enable tests in FIPS 140 in JDK 11 (elastic#48378)
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
1 parent c239a6a commit 1d7867d

File tree

27 files changed

+513
-220
lines changed

27 files changed

+513
-220
lines changed

buildSrc/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ if (project != rootProject) {
203203
exclude '**/*.p12'
204204
// the file that actually defines nocommit
205205
exclude '**/ForbiddenPatternsTask.java'
206+
exclude '**/*.bcfks'
206207
}
207208

208209
testingConventions {

buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,12 @@ import groovy.transform.CompileStatic
2727
import org.apache.commons.io.IOUtils
2828
import org.elasticsearch.gradle.info.BuildParams
2929
import org.elasticsearch.gradle.info.GlobalBuildInfoPlugin
30-
import org.elasticsearch.gradle.info.GlobalInfoExtension
31-
import org.elasticsearch.gradle.info.JavaHome
3230
import org.elasticsearch.gradle.precommit.DependencyLicensesTask
3331
import org.elasticsearch.gradle.precommit.PrecommitTasks
3432
import org.elasticsearch.gradle.test.ErrorReportingTestListener
3533
import org.elasticsearch.gradle.testclusters.ElasticsearchCluster
3634
import org.elasticsearch.gradle.testclusters.TestClustersPlugin
35+
import org.elasticsearch.gradle.tool.Boilerplate
3736
import org.gradle.api.Action
3837
import org.gradle.api.GradleException
3938
import org.gradle.api.InvalidUserDataException
@@ -139,31 +138,50 @@ class BuildPlugin implements Plugin<Project> {
139138
configureTestTasks(project)
140139
configurePrecommit(project)
141140
configureDependenciesInfo(project)
142-
143141
configureFips140(project)
144142
}
145143

146-
public static void configureFips140(Project project) {
147-
// Need to do it here to support external plugins
148-
GlobalInfoExtension globalInfo = project.rootProject.extensions.getByType(GlobalInfoExtension)
149-
// wait until global info is populated because we don't know if we are running in a fips jvm until execution time
150-
globalInfo.ready {
151-
// Common config when running with a FIPS-140 runtime JVM
152-
if (BuildParams.inFipsJvm) {
153-
project.tasks.withType(Test).configureEach { Test task ->
154-
task.systemProperty 'javax.net.ssl.trustStorePassword', 'password'
155-
task.systemProperty 'javax.net.ssl.keyStorePassword', 'password'
156-
}
157-
project.pluginManager.withPlugin("elasticsearch.testclusters") {
158-
NamedDomainObjectContainer<ElasticsearchCluster> testClusters = project.extensions.findByName(TestClustersPlugin.EXTENSION_NAME) as NamedDomainObjectContainer<ElasticsearchCluster>
159-
if (testClusters != null) {
160-
testClusters.all { ElasticsearchCluster cluster ->
161-
cluster.systemProperty 'javax.net.ssl.trustStorePassword', 'password'
162-
cluster.systemProperty 'javax.net.ssl.keyStorePassword', 'password'
163-
}
164-
}
144+
static void configureFips140(Project project) {
145+
// Common config when running with a FIPS-140 runtime JVM
146+
if (inFipsJvm()) {
147+
ExportElasticsearchBuildResourcesTask buildResources = project.tasks.getByName('buildResources') as ExportElasticsearchBuildResourcesTask
148+
File securityProperties = buildResources.copy("fips_java.security")
149+
File securityPolicy = buildResources.copy("fips_java.policy")
150+
File bcfksKeystore = buildResources.copy("cacerts.bcfks")
151+
// This configuration can be removed once system modules are available
152+
Boilerplate.maybeCreate(project.configurations, 'extraJars') {
153+
project.dependencies.add('extraJars', "org.bouncycastle:bc-fips:1.0.1")
154+
project.dependencies.add('extraJars', "org.bouncycastle:bctls-fips:1.0.9")
155+
}
156+
project.pluginManager.withPlugin("elasticsearch.testclusters") {
157+
NamedDomainObjectContainer<ElasticsearchCluster> testClusters = project.extensions.findByName(TestClustersPlugin.EXTENSION_NAME) as NamedDomainObjectContainer<ElasticsearchCluster>
158+
testClusters.all { ElasticsearchCluster cluster ->
159+
for (File dep : project.getConfigurations().getByName("extraJars").getFiles()){
160+
cluster.extraJarFile(dep)
165161
}
162+
cluster.extraConfigFile("fips_java.security", securityProperties)
163+
cluster.extraConfigFile("fips_java.policy", securityPolicy)
164+
cluster.extraConfigFile("cacerts.bcfks", bcfksKeystore)
165+
cluster.systemProperty('java.security.properties', '=${ES_PATH_CONF}/fips_java.security')
166+
cluster.systemProperty('java.security.policy', '=${ES_PATH_CONF}/fips_java.policy')
167+
cluster.systemProperty('javax.net.ssl.trustStore', '${ES_PATH_CONF}/cacerts.bcfks')
168+
cluster.systemProperty('javax.net.ssl.trustStorePassword', 'password')
169+
cluster.systemProperty('javax.net.ssl.keyStorePassword', 'password')
170+
cluster.systemProperty('javax.net.ssl.keyStoreType', 'BCFKS')
166171
}
172+
}
173+
project.tasks.withType(Test).configureEach { Test task ->
174+
task.dependsOn(buildResources)
175+
task.systemProperty('javax.net.ssl.trustStorePassword', 'password')
176+
task.systemProperty('javax.net.ssl.keyStorePassword', 'password')
177+
task.systemProperty('javax.net.ssl.trustStoreType', 'BCFKS')
178+
// Using the key==value format to override default JVM security settings and policy
179+
// see also: https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html
180+
task.systemProperty('java.security.properties', String.format(Locale.ROOT, "=%s", securityProperties.toString()))
181+
task.systemProperty('java.security.policy', String.format(Locale.ROOT, "=%s", securityPolicy.toString()))
182+
task.systemProperty('javax.net.ssl.trustStore', bcfksKeystore.toString())
183+
}
184+
167185
}
168186
}
169187

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

658-
if (BuildParams.inFipsJvm) {
659-
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}FIPS")
660-
} else {
661-
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}")
662-
}
663-
664676
if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_1_9) {
665677
test.jvmArgs '--illegal-access=warn'
666678
}
@@ -671,7 +683,10 @@ class BuildPlugin implements Plugin<Project> {
671683
test.systemProperty ('java.locale.providers','SPI,COMPAT')
672684
}
673685
}
674-
686+
if (inFipsJvm()) {
687+
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bc-fips:1.0.1")
688+
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bctls-fips:1.0.9")
689+
}
675690
test.jvmArgumentProviders.add(nonInputProperties)
676691
test.extensions.add('nonInputProperties', nonInputProperties)
677692

@@ -813,4 +828,8 @@ class BuildPlugin implements Plugin<Project> {
813828
})
814829
}
815830
}
831+
832+
private static inFipsJvm(){
833+
return Boolean.parseBoolean(System.getProperty("tests.fips.enabled"));
834+
}
816835
}

buildSrc/src/main/java/org/elasticsearch/gradle/info/GenerateGlobalBuildInfoTask.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,7 @@ public void generate() {
147147
runtimeJavaVersionDetails = findJavaVersionDetails(runtimeJavaHome);
148148
runtimeJavaVersionEnum = JavaVersion.toVersion(findJavaSpecificationVersion(runtimeJavaHome));
149149

150-
// We don't expect Gradle to be running in a FIPS JVM
151-
String inFipsJvmScript = "print(java.security.Security.getProviders()[0].name.toLowerCase().contains(\"fips\"));";
152-
inFipsJvm = Boolean.parseBoolean(runJavaAsScript(runtimeJavaHome, inFipsJvmScript));
150+
inFipsJvm = Boolean.parseBoolean(System.getProperty("tests.fips.enabled"));
153151
} else {
154152
throw new RuntimeException("Runtime Java home path of '" + compilerJavaHome + "' does not exist");
155153
}

buildSrc/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ public void extraConfigFile(String destination, File from, PropertyNormalization
373373
nodes.all(node -> node.extraConfigFile(destination, from, normalization));
374374
}
375375

376+
@Override
377+
public void extraJarFile(File from) {
378+
nodes.all(node -> node.extraJarFile(from));
379+
}
380+
376381
@Override
377382
public void user(Map<String, String> userSpec) {
378383
nodes.all(node -> node.user(userSpec));

buildSrc/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public class ElasticsearchNode implements TestClusterConfiguration {
128128
private final LazyPropertyMap<String, CharSequence> environment = new LazyPropertyMap<>("Environment", this);
129129
private final LazyPropertyList<CharSequence> jvmArgs = new LazyPropertyList<>("JVM arguments", this);
130130
private final LazyPropertyMap<String, File> extraConfigFiles = new LazyPropertyMap<>("Extra config files", this, FileEntry::new);
131+
private final LazyPropertyList<File> extraJarFiles = new LazyPropertyList<>("Extra jar files", this);
131132
private final List<Map<String, String>> credentials = new ArrayList<>();
132133
final LinkedHashMap<String, String> defaultConfig = new LinkedHashMap<>();
133134

@@ -454,6 +455,8 @@ public synchronized void start() {
454455

455456
copyExtraConfigFiles();
456457

458+
copyExtraJars();
459+
457460
if (isSettingTrue("xpack.security.enabled")) {
458461
if (credentials.isEmpty()) {
459462
user(Collections.emptyMap());
@@ -530,6 +533,25 @@ private void copyExtraConfigFiles() {
530533
});
531534
}
532535

536+
/**
537+
* Copies extra jars to the `/lib` directory.
538+
* //TODO: Remove this when system modules are available
539+
*/
540+
private void copyExtraJars() {
541+
if (extraJarFiles.isEmpty() == false){
542+
logToProcessStdout("Setting up " + extraJarFiles.size() + " additional jar dependencies");
543+
}
544+
extraJarFiles.forEach(from -> {
545+
Path destination = getDistroDir().resolve("lib").resolve(from.getName());
546+
try {
547+
Files.copy(from.toPath(), destination, StandardCopyOption.REPLACE_EXISTING);
548+
LOGGER.info("Added extra jar {} to {}", from.getName(), destination);
549+
} catch (IOException e) {
550+
throw new UncheckedIOException("Can't copy extra jar dependency " + from.getName() + " to " + destination.toString(), e);
551+
}
552+
});
553+
}
554+
533555
private void installModules() {
534556
if (testDistribution == TestDistribution.INTEG_TEST) {
535557
logToProcessStdout("Installing " + modules.size() + "modules");
@@ -576,6 +598,14 @@ public void extraConfigFile(String destination, File from, PropertyNormalization
576598
extraConfigFiles.put(destination, from, normalization);
577599
}
578600

601+
@Override
602+
public void extraJarFile(File from) {
603+
if (from.toString().endsWith(".jar") == false) {
604+
throw new IllegalArgumentException("extra jar file " + from.toString() + " doesn't appear to be a JAR");
605+
}
606+
extraJarFiles.add(from);
607+
}
608+
579609
@Override
580610
public void user(Map<String, String> userSpec) {
581611
Set<String> keys = new HashSet<>(userSpec.keySet());

buildSrc/src/main/java/org/elasticsearch/gradle/testclusters/TestClusterConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public interface TestClusterConfiguration {
9292

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

95+
void extraJarFile(File from);
96+
9597
void user(Map<String, String> userSpec);
9698

9799
String getHttpSocketURI();
101 KB
Binary file not shown.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
grant {
2+
permission java.security.SecurityPermission "putProviderProperty.BCFIPS";
3+
permission java.security.SecurityPermission "putProviderProperty.BCJSSE";
4+
permission java.lang.RuntimePermission "getProtectionDomain";
5+
permission java.util.PropertyPermission "java.runtime.name", "read";
6+
permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled";
7+
//io.netty.handler.codec.DecoderException
8+
permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec";
9+
//java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec
10+
permission java.lang.RuntimePermission "accessDeclaredMembers";
11+
permission java.util.PropertyPermission "intellij.debug.agent", "read";
12+
permission java.util.PropertyPermission "intellij.debug.agent", "write";
13+
permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey";
14+
permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey";
15+
permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read";
16+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
2+
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
3+
security.provider.3=SUN
4+
securerandom.source=file:/dev/urandom
5+
securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN
6+
securerandom.drbg.config=
7+
login.configuration.provider=sun.security.provider.ConfigFile
8+
policy.provider=sun.security.provider.PolicyFile
9+
policy.expandProperties=true
10+
policy.allowSystemProperty=true
11+
policy.ignoreIdentityScope=false
12+
keystore.type=BCFKS
13+
keystore.type.compat=true
14+
package.access=sun.misc.,\
15+
sun.reflect.
16+
package.definition=sun.misc.,\
17+
sun.reflect.
18+
security.overridePropertiesFile=true
19+
ssl.KeyManagerFactory.algorithm=PKIX
20+
ssl.TrustManagerFactory.algorithm=PKIX
21+
networkaddress.cache.negative.ttl=10
22+
krb5.kdc.bad.policy = tryLast
23+
jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \
24+
RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224
25+
jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \
26+
DSA keySize < 1024
27+
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \
28+
EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC
29+
jdk.tls.legacyAlgorithms= \
30+
K_NULL, C_NULL, M_NULL, \
31+
DH_anon, ECDH_anon, \
32+
RC4_128, RC4_40, DES_CBC, DES40_CBC, \
33+
3DES_EDE_CBC
34+
jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37
35+
crypto.policy=unlimited
36+
jdk.xml.dsig.secureValidationPolicy=\
37+
disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
38+
disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
39+
disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
40+
disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
41+
maxTransforms 5,\
42+
maxReferences 30,\
43+
disallowReferenceUriSchemes file http https,\
44+
minKeySize RSA 1024,\
45+
minKeySize DSA 1024,\
46+
minKeySize EC 224,\
47+
noDuplicateIds,\
48+
noRetrievalMethodLoops
49+
jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\
50+
java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!*

distribution/tools/launchers/src/main/java/org/elasticsearch/tools/launchers/JvmOptionsParser.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.ArrayList;
3333
import java.util.Arrays;
3434
import java.util.Collections;
35+
import java.util.HashMap;
3536
import java.util.Iterator;
3637
import java.util.List;
3738
import java.util.Locale;
@@ -87,8 +88,13 @@ public void accept(final int lineNumber, final String line) {
8788
.filter(s -> s.trim().isEmpty() == false)
8889
.collect(Collectors.toList()));
8990
}
91+
final Map<String, String> substitutions = new HashMap<>();
92+
substitutions.put("ES_TMPDIR", System.getenv("ES_TMPDIR"));
93+
if (null != System.getenv("ES_PATH_CONF")){
94+
substitutions.put("ES_PATH_CONF", System.getenv("ES_PATH_CONF"));
95+
}
9096
final List<String> substitutedJvmOptions =
91-
substitutePlaceholders(jvmOptions, Collections.singletonMap("ES_TMPDIR", System.getenv("ES_TMPDIR")));
97+
substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions));
9298
final List<String> ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions);
9399
final List<String> systemJvmOptions = SystemJvmOptions.systemJvmOptions();
94100
final List<String> finalJvmOptions =

0 commit comments

Comments
 (0)