Skip to content

Commit c7689ce

Browse files
authored
Enable tests in FIPS 140 in JDK 11 (#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: #37250 Supersedes: #41024
1 parent 5cf112e commit c7689ce

File tree

30 files changed

+554
-223
lines changed

30 files changed

+554
-223
lines changed

buildSrc/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ if (project != rootProject) {
200200
exclude '**/*.p12'
201201
// the file that actually defines nocommit
202202
exclude '**/ForbiddenPatternsTask.java'
203+
exclude '**/*.bcfks'
203204
}
204205

205206
testingConventions {

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

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

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

@@ -623,15 +641,13 @@ class BuildPlugin implements Plugin<Project> {
623641
project.mkdir(heapdumpDir)
624642
project.mkdir(test.workingDir)
625643

626-
if (BuildParams.inFipsJvm) {
627-
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}FIPS")
628-
} else {
629-
nonInputProperties.systemProperty('runtime.java', "${-> BuildParams.runtimeJavaVersion.majorVersion}")
630-
}
631644
//TODO remove once jvm.options are added to test system properties
632645
test.systemProperty ('java.locale.providers','SPI,COMPAT')
633646
}
634-
647+
if (inFipsJvm()) {
648+
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bc-fips:1.0.1")
649+
project.dependencies.add('testRuntimeOnly', "org.bouncycastle:bctls-fips:1.0.9")
650+
}
635651
test.jvmArgumentProviders.add(nonInputProperties)
636652
test.extensions.add('nonInputProperties', nonInputProperties)
637653

@@ -775,4 +791,8 @@ class BuildPlugin implements Plugin<Project> {
775791
})
776792
}
777793
}
794+
795+
private static inFipsJvm(){
796+
return Boolean.parseBoolean(System.getProperty("tests.fips.enabled"));
797+
}
778798
}

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
@@ -334,6 +334,11 @@ public void extraConfigFile(String destination, File from, PropertyNormalization
334334
nodes.all(node -> node.extraConfigFile(destination, from, normalization));
335335
}
336336

337+
@Override
338+
public void extraJarFile(File from) {
339+
nodes.all(node -> node.extraJarFile(from));
340+
}
341+
337342
@Override
338343
public void user(Map<String, String> userSpec) {
339344
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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import java.nio.file.Paths;
3232
import java.util.ArrayList;
3333
import java.util.Arrays;
34+
import java.util.Collections;
35+
import java.util.HashMap;
3436
import java.util.Iterator;
3537
import java.util.List;
3638
import java.util.Locale;
@@ -87,8 +89,13 @@ public void accept(final int lineNumber, final String line) {
8789
.filter(Predicate.not(String::isBlank))
8890
.collect(Collectors.toUnmodifiableList()));
8991
}
92+
final Map<String, String> substitutions = new HashMap<>();
93+
substitutions.put("ES_TMPDIR", System.getenv("ES_TMPDIR"));
94+
if (null != System.getenv("ES_PATH_CONF")){
95+
substitutions.put("ES_PATH_CONF", System.getenv("ES_PATH_CONF"));
96+
}
9097
final List<String> substitutedJvmOptions =
91-
substitutePlaceholders(jvmOptions, Map.of("ES_TMPDIR", System.getenv("ES_TMPDIR")));
98+
substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions));
9299
final List<String> ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions);
93100
final List<String> systemJvmOptions = SystemJvmOptions.systemJvmOptions();
94101
final List<String> finalJvmOptions =

0 commit comments

Comments
 (0)