From 87a0eab54da043ce2adaee9b31d15723b40b0c53 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Mon, 16 Sep 2024 12:11:40 +1000 Subject: [PATCH] [JEP-237] Add new method to RealJenkinsRule to start in FIPS mode (#829) Signed-off-by: Olivier Lamy --- pom.xml | 5 ++ .../jvnet/hudson/test/RealJenkinsRule.java | 40 +++++++++- .../hudson/test/RealJenkinsRuleFIPSTest.java | 73 +++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/jvnet/hudson/test/RealJenkinsRuleFIPSTest.java diff --git a/pom.xml b/pom.xml index 54db78436..44d3c369d 100644 --- a/pom.xml +++ b/pom.xml @@ -223,6 +223,11 @@ THE SOFTWARE. + + io.jenkins.test.fips + fips-bundle-test + 23.v76d4fd57f5b_d + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java b/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java index 59bc76fd0..ea784989b 100644 --- a/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java +++ b/src/main/java/org/jvnet/hudson/test/RealJenkinsRule.java @@ -65,6 +65,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -90,6 +91,8 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import io.jenkins.test.fips.FIPSTestBundleProvider; import jenkins.model.Jenkins; import jenkins.model.JenkinsLocationConfiguration; import jenkins.util.Timer; @@ -200,6 +203,7 @@ public final class RealJenkinsRule implements TestRule { private boolean prepareHomeLazily; private boolean provisioned; + private final List bootClasspathFiles = new ArrayList<>(); // TODO may need to be relaxed for Gradle-based plugins private static final Pattern SNAPSHOT_INDEX_JELLY = Pattern.compile("(file:/.+/target)/classes/index.jelly"); @@ -457,6 +461,36 @@ public RealJenkinsRule prepareHomeLazily(boolean prepareHomeLazily) { return this; } + /** + * Use {@link #withFIPSEnabled(FIPSTestBundleProvider)} with default value of {@link FIPSTestBundleProvider#get()} + */ + public RealJenkinsRule withFIPSEnabled() { + return withFIPSEnabled(FIPSTestBundleProvider.get()); + } + + /** + + + * @param fipsTestBundleProvider the {@link FIPSTestBundleProvider} to use for testing + */ + public RealJenkinsRule withFIPSEnabled(FIPSTestBundleProvider fipsTestBundleProvider) { + Objects.requireNonNull(fipsTestBundleProvider, "fipsTestBundleProvider must not be null"); + try { + return withBootClasspath(fipsTestBundleProvider.getBootClasspathFiles().toArray(new File[0])) + .javaOptions(fipsTestBundleProvider.getJavaOptions().toArray(new String[0])); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * + * @param files add some {@link File} to bootclasspath + */ + public RealJenkinsRule withBootClasspath(File...files) { + this.bootClasspathFiles.addAll(List.of(files)); + return this; + } + public static List getJacocoAgentOptions() { RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); List arguments = runtimeMxBean.getInputArguments(); @@ -744,8 +778,12 @@ public void startJenkins() throws Throwable { + ",suspend=" + (debugSuspend ? "y" : "n") + (debugPort > 0 ? ",address=" + httpListenAddress + ":" + debugPort : "")); } - argv.addAll(javaOptions); + if(!bootClasspathFiles.isEmpty()) { + String fileList = bootClasspathFiles.stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); + argv.add("-Xbootclasspath/a:" + fileList); + } + argv.addAll(javaOptions); argv.addAll(List.of( "-jar", war.getAbsolutePath(), diff --git a/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleFIPSTest.java b/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleFIPSTest.java new file mode 100644 index 000000000..336dcabbd --- /dev/null +++ b/src/test/java/org/jvnet/hudson/test/RealJenkinsRuleFIPSTest.java @@ -0,0 +1,73 @@ +/* + * The MIT License + * + * Copyright 2024 Olivier Lamy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jvnet.hudson.test; + +import io.jenkins.test.fips.FIPSTestBundleProvider; +import jenkins.security.FIPS140; +import org.junit.Rule; +import org.junit.Test; + +import javax.net.ssl.KeyManagerFactory; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class RealJenkinsRuleFIPSTest { + + @Rule public RealJenkinsRule rr = new RealJenkinsRule().prepareHomeLazily(true) + .withDebugPort(4001).withDebugServer(false) + .withFIPSEnabled(FIPSTestBundleProvider.get()) + .javaOptions("-Djava.security.debug=properties"); + + @Test + public void fipsMode() throws Throwable { + rr.then(r -> { + Provider[] providers = Security.getProviders(); + System.out.println("fipsMode providers:" + Arrays.asList(providers)); + + Class clazz = Thread.currentThread().getContextClassLoader().loadClass("org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"); + System.out.println("BouncyCastleFipsProvider class:" + clazz); + + Provider provider = Security.getProvider("BCFIPS"); + assertThat(provider, notNullValue()); + assertThat(provider.getClass().getName(), is("org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider")); + assertThat(providers[0].getClass().getName(), is("org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider")); + assertThat(providers[1].getClass().getName(), is("org.bouncycastle.jsse.provider.BouncyCastleJsseProvider")); + assertThat(providers[2].getClass().getName(), is("sun.security.provider.Sun")); + assertThat(KeyStore.getDefaultType(), is("BCFKS")); + assertThat(KeyManagerFactory.getDefaultAlgorithm(), is("PKIX")); + + assertThat(providers.length, is(3)); + + assertThat(FIPS140.useCompliantAlgorithms(), is(true)); + }); + } + +}