Skip to content

Commit

Permalink
Merge pull request #50 from jmdesprez/JENKINS-73525
Browse files Browse the repository at this point in the history
  • Loading branch information
Vlatombe committed Aug 13, 2024
2 parents 8ea0029 + 3f7ec97 commit 71b1a82
Show file tree
Hide file tree
Showing 18 changed files with 581 additions and 5 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>

<!-- jenkins versions -->
<jenkins.version>2.401.3</jenkins.version>
<bom.artifactId>bom-2.401.x</bom.artifactId>
<bom.version>2745.vc7b_fe4c876fa_</bom.version>
<jenkins.version>2.426.3</jenkins.version>
<bom.artifactId>bom-2.426.x</bom.artifactId>
<bom.version>3208.vb_21177d4b_cd9</bom.version>

<!-- maven plugins versions -->
<maven-coveralls.version>4.3.0</maven-coveralls.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jenkinsci.plugins.kubernetes.auth;

import org.jenkinsci.plugins.kubernetes.credentials.Utils;

/**
* Configuration object for {@link KubernetesAuth} operations.
*/
Expand All @@ -18,6 +20,7 @@ public class KubernetesAuthConfig {
private final boolean skipTlsVerify;

public KubernetesAuthConfig(String serverUrl, String caCertificate, boolean skipTlsVerify) {
Utils.ensureFIPSCompliantRequest(serverUrl, skipTlsVerify);
this.serverUrl = serverUrl;
this.caCertificate = caCertificate;
this.skipTlsVerify = skipTlsVerify;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private static SSLConnectionSocketFactory getVerifyCertSSLFactory(String hostnam
}

public static HttpClientBuilder getBuilder(URI uri, String caCertData, boolean skipTLSVerify) throws TLSConfigurationError {
Utils.ensureFIPSCompliantURIRequest(uri, skipTLSVerify);
final HttpClientBuilder builder = HttpClients.custom().setRedirectStrategy(NO_HTTP_REDIRECT);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
Expand Down Expand Up @@ -135,7 +136,9 @@ private synchronized Token refreshToken(String apiServerURL, String caCertData,

final HttpClientBuilder builder = HttpClientWithTLSOptionsFactory.getBuilder(uri, caCertData, skipTLSVerify);
HttpGet authorize = new HttpGet(oauthServerURL + "?client_id=openshift-challenging-client&response_type=token");
authorize.setHeader("Authorization", getBasicAuthenticationHeader(getUsername(), getPassword()));
authorize.setHeader(HttpHeaders.AUTHORIZATION, getBasicAuthenticationHeader(getUsername(), getPassword()));

Utils.ensureFIPSCompliantURIRequest(authorize.getURI(), skipTLSVerify);
final CloseableHttpResponse response = builder.build().execute(authorize);

if (response.getStatusLine().getStatusCode() != 302) {
Expand All @@ -154,6 +157,7 @@ private String getOauthServerUrl(String apiServerURL, String caCertData, boolean
URI uri = new URI(apiServerURL);
final HttpClientBuilder builder = HttpClientWithTLSOptionsFactory.getBuilder(uri, caCertData, skipTLSVerify);
HttpGet discover = new HttpGet(apiServerURL + "/.well-known/oauth-authorization-server");
Utils.ensureFIPSCompliantURIRequest(discover.getURI(), skipTLSVerify);
final CloseableHttpResponse response = builder.build().execute(discover);
return JSONObject.fromObject(EntityUtils.toString(response.getEntity())).getString("authorization_endpoint");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package org.jenkinsci.plugins.kubernetes.credentials;

import jenkins.security.FIPS140;
import org.apache.commons.codec.binary.Base64;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;

public abstract class Utils {

/**
* Error message used to indicate that skipping TLS verification is not accepted in FIPS mode.
*/
public static final String FIPS140_ERROR_MESSAGE =
"Using an insecure connection and/or skipping TLS verification is not accepted in FIPS mode.";

public static String wrapWithMarker(String begin, String end, String encodedBody) {
return new StringBuilder(begin).append("\n")
.append(encodedBody).append("\n")
Expand Down Expand Up @@ -46,4 +53,55 @@ public static String encodeCertificate(Certificate certificate) throws Certifica
public static String encodeKey(Key key) {
return encodeBase64(wrapPrivateKey(Base64.encodeBase64String(key.getEncoded())));
}

/**
* Ensure that the URI request is FIPS compliant for the given HttpUriRequest object and skipTLSVerify option.
* Throw an exception if the request is invalid.
* A request is considered valid if the connection is either using TLS or a local pipe
* and if the TLS verification is not skipped.
* If FIPS mode is not enabled, this method does nothing.
*
* @param uri The request to validate
* @param skipTLSVerify A flag indicating whether to skip TLS verification or not
* @throws IllegalArgumentException If the request is invalid
*/
public static void ensureFIPSCompliantURIRequest(URI uri, boolean skipTLSVerify) {
boolean isInsecure = uri.getScheme().equals("http");
ensureFIPSCompliant(isInsecure, skipTLSVerify);
}

/**
* Ensure that the request is FIPS compliant for the given URL and skipTLSVerify option.
* Throw an exception if the request is invalid.
* A request is considered valid if the connection is either using TLS or a local pipe
* and if the TLS verification is not skipped.
* If FIPS mode is not enabled, this method does nothing.
*
* @param stringRequest The request to validate
* @param skipTLSVerify A flag indicating whether to skip TLS verification or not
* @throws IllegalArgumentException If the request is invalid
*/
public static void ensureFIPSCompliantRequest(String stringRequest, boolean skipTLSVerify) {
boolean isInsecure = stringRequest.startsWith("http://");
ensureFIPSCompliant(isInsecure, skipTLSVerify);
}

/**
* Ensure FIPS compliance based on the following rules:
* <ul>
* <li>Must use a secure connection</li>
* <li>TLS verification is mandatory</li>
* </ul>
* Throw an exception if not compliant.
* If FIPS mode is not enabled, this method does nothing.
*
* @param insecureConnection If the connection is insecure
* @param skipTLSVerify A flag indicating whether to skip TLS verification or not
* @throws IllegalArgumentException If not FIPS compliant
*/
private static void ensureFIPSCompliant(boolean insecureConnection, boolean skipTLSVerify) {
if (FIPS140.useCompliantAlgorithms() && (insecureConnection || skipTLSVerify)) {
throw new IllegalArgumentException(Utils.FIPS140_ERROR_MESSAGE);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.jenkinsci.plugins.kubernetes.auth;

import static org.junit.Assert.fail;

import org.junit.Test;

public abstract class AbstractKubernetesAuthConfigFIPSTest {
protected String scheme;

protected boolean skipTLSVerify;

protected boolean shouldPass;

protected String motivation;

public AbstractKubernetesAuthConfigFIPSTest(
String scheme, boolean skipTLSVerify, boolean shouldPass, String motivation) {
this.scheme = scheme;
this.skipTLSVerify = skipTLSVerify;
this.shouldPass = shouldPass;
this.motivation = motivation;
}

@Test
public void testCreateKubernetesAuthConfig() {
try {
new KubernetesAuthConfig(scheme + "://server", null, skipTLSVerify);
if (!shouldPass) {
fail("This test was expected to fail, reason: " + motivation);
}
} catch (IllegalArgumentException e) {
if (shouldPass) {
fail("This test was expected to pass, reason: " + motivation);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.jenkinsci.plugins.kubernetes.auth;

import java.util.Arrays;
import java.util.Collection;
import jenkins.security.FIPS140;
import org.junit.ClassRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jvnet.hudson.test.FlagRule;

@RunWith(Parameterized.class)
public class KubernetesAuthConfigWithFIPSTest extends AbstractKubernetesAuthConfigFIPSTest {
@ClassRule
public static FlagRule<String> fipsFlag = FlagRule.systemProperty(FIPS140.class.getName() + ".COMPLIANCE", "true");

public KubernetesAuthConfigWithFIPSTest(
String scheme, boolean skipTLSVerify, boolean shouldPass, String motivation) {
super(scheme, skipTLSVerify, shouldPass, motivation);
}

@Parameterized.Parameters
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] {
// Valid use cases
{"https", false, true, "TLS is used and the TLS verification is not skipped, this should be accepted"},
// Invalid use cases
{"https", true, false, "Skip TLS check is not accepted in FIPS mode"},
{"http", false, false, "TLS is mandatory when in FIPS mode"},
{"http", true, false, "TLS and TLS check are mandatory when in FIPS mode"},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.jenkinsci.plugins.kubernetes.auth;

import java.util.Arrays;
import java.util.Collection;
import jenkins.security.FIPS140;
import org.junit.ClassRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jvnet.hudson.test.FlagRule;

@RunWith(Parameterized.class)
public class KubernetesAuthConfigWithoutFIPSTest extends AbstractKubernetesAuthConfigFIPSTest {
@ClassRule
public static FlagRule<String> fipsFlag = FlagRule.systemProperty(FIPS140.class.getName() + ".COMPLIANCE", "false");

public KubernetesAuthConfigWithoutFIPSTest(
String scheme, boolean skipTLSVerify, boolean shouldPass, String motivation) {
super(scheme, skipTLSVerify, shouldPass, motivation);
}

@Parameterized.Parameters
public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] {
// Valid use cases
{"https", false, true, "Not in FIPS mode, any combination should be valid"},
{"http", false, true, "Not in FIPS mode, any combination should be valid"},
{"http", true, true, "Not in FIPS mode, any combination should be valid"},
{"https", true, true, "Not in FIPS mode, any combination should be valid"},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.jenkinsci.plugins.kubernetes.credentials;

import static org.junit.Assert.fail;

import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Test;

public abstract class AbstractHttpClientWithTLSOptionsFactoryFIPSTest {
protected String scheme;

protected boolean skipTLSVerify;

protected boolean shouldPass;

protected String motivation;

public AbstractHttpClientWithTLSOptionsFactoryFIPSTest(String scheme, boolean skipTLSVerify, boolean shouldPass, String motivation) {
this.scheme = scheme;
this.skipTLSVerify = skipTLSVerify;
this.shouldPass = shouldPass;
this.motivation = motivation;
}

@Test
public void testCreateKubernetesAuthConfig() throws URISyntaxException {
try {
HttpClientWithTLSOptionsFactory.getBuilder(new URI(scheme, "localhost", null, null), null, skipTLSVerify);
if (!shouldPass) {
fail("This test was expected to fail, reason: " + motivation);
}
} catch (IllegalArgumentException e) {
if (shouldPass) {
fail("This test was expected to pass, reason: " + motivation);
}
} catch (HttpClientWithTLSOptionsFactory.TLSConfigurationError e) {
fail("This test should not cause a TLSConfigurationError");
}
}
}
Loading

0 comments on commit 71b1a82

Please sign in to comment.