Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-73139] Converting tests to com.sun.net.httpserver #54

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>

<!-- jenkins versions -->
<jenkins.version>2.426.3</jenkins.version>
<jenkins.version>2.472</jenkins.version>
basil marked this conversation as resolved.
Show resolved Hide resolved
<bom.artifactId>bom-2.426.x</bom.artifactId>
<bom.version>3208.vb_21177d4b_cd9</bom.version>

This comment was marked as outdated.

This comment was marked as resolved.


Expand All @@ -51,6 +51,9 @@

<spotbugs.effort>Max</spotbugs.effort>
<spotbugs.threshold>Low</spotbugs.threshold>
<!--TODO Until is included in parent pom -->
<maven.compiler.release>17</maven.compiler.release>
<jenkins-test-harness.version>2250.v03a_1295b_0a_30</jenkins-test-harness.version>
basil marked this conversation as resolved.
Show resolved Hide resolved
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package org.jenkinsci.plugins.kubernetes.credentials;

import com.cloudbees.plugins.credentials.CredentialsScope;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.jvnet.hudson.test.FlagRule;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;

import static org.junit.Assert.fail;

Expand Down Expand Up @@ -48,11 +48,7 @@ public abstract class AbstractOpenShiftBearerTokenCredentialFIPSTest {

protected String motivation;

private Server server;

private ServerConnector sslConnector;

private ServerConnector serverConnector;
private HttpServer server;


public AbstractOpenShiftBearerTokenCredentialFIPSTest(
Expand All @@ -68,44 +64,42 @@ public void prepareFakeOAuthServer() throws Exception {
if (keystore == null) {
fail("Unable to find keystore.jks");
}
server = new Server();

HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.addCustomizer(new SecureRequestCustomizer());
if ("https".equals(scheme)) {
server = HttpsServer.create(new InetSocketAddress("localhost", 0), 0);
setupHttps((HttpsServer) server);
OpenShiftBearerTokenCredentialMockServer.registerHttpHandlers(server);
} else {
server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
OpenShiftBearerTokenCredentialMockServer.registerHttpHandlers(server);
}

SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore.toExternalForm());
sslContextFactory.setKeyManagerPassword("unittest");
sslContextFactory.setKeyStorePassword("unittest");
server.setExecutor(null); // Creates a default executor
server.start();
}

sslConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig));
serverConnector = new ServerConnector(server);
server.setConnectors(new Connector[]{serverConnector, sslConnector});
private void setupHttps(HttpsServer httpsServer) throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
Copy link

@raul-arabaolaza raul-arabaolaza Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the original behaviour in some cases, see https://android.googlesource.com/platform/external/jetty/+/refs/heads/marshmallow-dev/src/java/org/eclipse/jetty/util/ssl/SslContextFactory.java#95 in some fips environments that property may be used.

And this can be applied to other parts, for example you are forcing a "tls" for SslContext but the original code may not be doing that or allow the algorithm to be overriden by a system property... This kind of things are important as we may be removing the ability for a fips env to override things.

Copy link
Member

@basil basil Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a relevant comment I think. The purpose of the test is to ensure that TLS is in use and that TLS verification is not disabled. There are no changes to production code, so the only question is whether the effectiveness of the test is reduced. The test previously didn't require a FIPS compliant machine but verified FIPS compliance as described above, and the same conditions are true after this refactoring, so there is no issue here.

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(keystore.openStream(), "unittest".toCharArray());
kmf.init(ks, "unittest".toCharArray());

ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(new ServletHolder(new MockHttpServlet()), "/*");
server.setHandler(context);
server.start();
sslContext.init(kmf.getKeyManagers(), null, null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
}

@After
public void unprepareFakeOAuthServer() throws Exception {
server.stop();
server.stop(0);
}

@Test
public void ensureFIPSCompliantURIRequest() throws IOException {
OpenShiftBearerTokenCredentialImpl cred;
cred = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, "id", "description", "username", "password");
try {
int port;
if ("https".equals(scheme)) {
port = sslConnector.getLocalPort();
} else {
port = serverConnector.getLocalPort();
}
cred.getToken(scheme + "://localhost:" + port + "/valid-response", null, skipTLSVerify);
cred.getToken(scheme + "://localhost:" + server.getAddress().getPort() + "/valid-response", null, skipTLSVerify);
if (!shouldPass) {
fail("This test was expected to fail, reason: " + motivation);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.jenkinsci.plugins.kubernetes.credentials;
nevingeorgesunny marked this conversation as resolved.
Show resolved Hide resolved

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsServer;
import java.io.IOException;
import java.io.OutputStream;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Author: Nevin Sunny
* Date: 13/08/24
* Time: 11:24 am
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really necessary.

public class OpenShiftBearerTokenCredentialMockServer {

public static void registerHttpHandlers(HttpServer server) throws IOException {
BiConsumer<String, HttpHandler> register = server::createContext;

register.accept("/bad-location/oauth/authorize", OpenShiftBearerTokenCredentialMockServer::badLocationHandler);
register.accept(
"/missing-location/oauth/authorize", OpenShiftBearerTokenCredentialMockServer::missingLocationHandler);
register.accept("/bad-response/oauth/authorize", OpenShiftBearerTokenCredentialMockServer::badResponseHandler);
register.accept(
"/valid-response/oauth/authorize", OpenShiftBearerTokenCredentialMockServer::validResponseHandler1);
register.accept(
"/valid-response2/oauth/authorize", OpenShiftBearerTokenCredentialMockServer::validResponseHandler2);
register.accept("/", OpenShiftBearerTokenCredentialMockServer::defaultHandler);
}

private static void badLocationHandler(HttpExchange he) throws IOException {
String redirectURL = "bad";
he.getResponseHeaders().set("Location", redirectURL);
he.sendResponseHeaders(302, -1);
}

private static void missingLocationHandler(HttpExchange he) throws IOException {
he.sendResponseHeaders(302, -1); // No Location header
}

private static void badResponseHandler(HttpExchange he) throws IOException {
he.sendResponseHeaders(400, -1);
}

private static void validResponseHandler1(HttpExchange he) throws IOException {
String redirectURL = "http://my-service/#access_token=1234&expires_in=86400";
he.getResponseHeaders().set("Location", redirectURL);
he.sendResponseHeaders(302, -1);
}

private static void validResponseHandler2(HttpExchange he) throws IOException {
String redirectURL = "http://my-service/#access_token=1235&expires_in=86400";
he.getResponseHeaders().set("Location", redirectURL);
he.sendResponseHeaders(302, -1);
}

private static void defaultHandler(HttpExchange he) throws IOException {
String path = he.getRequestURI().getPath();
Pattern pattern = Pattern.compile("(.*)/.well-known/oauth-authorization-server");
Matcher matcher = pattern.matcher(path);

String scheme = "http";
if (he.getHttpContext().getServer() instanceof HttpsServer) {
scheme = "https";
}

String hostname = "localhost";
int port = he.getLocalAddress().getPort();

if (matcher.find()) {
String responseToClient = "{\n" + " \"issuer\": \""
+ scheme + "://" + hostname + ":" + port + "/\",\n" + " \"authorization_endpoint\": \""
+ scheme + "://" + hostname + ":" + port + matcher.group(1) + "/oauth/authorize\",\n"
+ " \"token_endpoint\": \""
+ scheme + "://" + hostname + ":" + port + "/oauth/token\",\n" + " \"scopes_supported\": [\n"
+ " \"user:check-access\",\n"
+ " \"user:full\",\n"
+ " \"user:info\",\n"
+ " \"user:list-projects\",\n"
+ " \"user:list-scoped-projects\"\n"
+ " ],\n"
+ " \"response_types_supported\": [\n"
+ " \"code\",\n"
+ " \"token\"\n"
+ " ],\n"
+ " \"grant_types_supported\": [\n"
+ " \"authorization_code\",\n"
+ " \"implicit\"\n"
+ " ],\n"
+ " \"code_challenge_methods_supported\": [\n"
+ " \"plain\",\n"
+ " \"S256\"\n"
+ " ]\n"
+ "}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be great to update the plugin to Java 17 so you could use String Blocks here.
That would require to update jenkins.version and build configuration but might worth the change imo. What do you think @MarkEWaite ?
Maybe for later ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried to mix general purpose Java 17 upgrades with Spring Security 6.x Upgrade work it did not end well for me. I tried to do that mixing with the git client plugin experiment with JGit 7.0.0 that requires Java 17. I was stacking two patches (one for JGit 7.0.0 and requiring Jenkins 2.463 minimum version and the other requiring the Jelly 12 EE 9 pre-release from the tip of the jakarta branch) and the addition of the Java 17 changes made me discard both of those changes and start with fresh branches.

I think that in general we should avoid making general Java 17 changes so that we don't delay these pull requests that are time critical.

In this case, the plugin does not need to require Java 17, so the Java 17 changes would actually break the plugin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally fine by me. This can be a "cleanup" task later on when we know everything is ok with the plugin.


byte[] responseBytes = responseToClient.getBytes();
he.sendResponseHeaders(200, responseBytes.length);
try (OutputStream os = he.getResponseBody()) {
os.write(responseBytes);
}
} else {
he.sendResponseHeaders(500, -1);
he.getResponseBody().write(("Bad test: unknown path " + path).getBytes());
}
}
}
Loading