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 16 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
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package org.jenkinsci.plugins.kubernetes.credentials;

import com.cloudbees.plugins.credentials.CredentialsScope;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import edu.umd.cs.findbugs.annotations.NonNull;
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.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.KeyStore;

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

import static org.junit.Assert.fail;

Expand Down Expand Up @@ -49,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 @@ -69,44 +64,44 @@ 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());
InetSocketAddress address = new InetSocketAddress("localhost", 0);
if ("https".equals(scheme)) {
server = HttpsServer.create(address, 0);
setupHttps((HttpsServer) server);
OpenShiftBearerTokenCredentialMockServer.registerHttpHandlers(server);
} else {
server = HttpServer.create(address, 0);
OpenShiftBearerTokenCredentialMockServer.registerHttpHandlers(server);
}

SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(keystore.toExternalForm());
sslContextFactory.setKeyManagerPassword("unittest");
sslContextFactory.setKeyStorePassword("unittest");
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");
try (InputStream is = keystore.openStream()) {
ks.load(is, "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();
public void unprepareFakeOAuthServer() {
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,107 @@
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.HttpServer;
import com.sun.net.httpserver.HttpsServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author Nevin Sunny
*/
public class OpenShiftBearerTokenCredentialMockServer {

public static void registerHttpHandlers(HttpServer server) {
server.createContext(
"/bad-location/oauth/authorize",
OpenShiftBearerTokenCredentialMockServer::badLocationHandler);
server.createContext(
"/missing-location/oauth/authorize",
OpenShiftBearerTokenCredentialMockServer::missingLocationHandler);
server.createContext(
"/bad-response/oauth/authorize",
OpenShiftBearerTokenCredentialMockServer::badResponseHandler);
server.createContext(
"/valid-response/oauth/authorize",
OpenShiftBearerTokenCredentialMockServer::validResponseHandler1);
server.createContext(
"/valid-response2/oauth/authorize",
OpenShiftBearerTokenCredentialMockServer::validResponseHandler2);
server.createContext("/", OpenShiftBearerTokenCredentialMockServer::defaultHandler);
}

private static void badLocationHandler(HttpExchange he) throws IOException {
he.getResponseHeaders().set("Location", "bad");
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 {
he.getResponseHeaders().set("Location", "http://my-service/#access_token=1234&expires_in=86400");
he.sendResponseHeaders(302, -1);
}

private static void validResponseHandler2(HttpExchange he) throws IOException {
he.getResponseHeaders().set("Location", "http://my-service/#access_token=1235&expires_in=86400");
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 = he.getHttpContext().getServer() instanceof HttpsServer ? "https" : "http";
String rootURL = scheme + "://localhost:" + he.getLocalAddress().getPort() + "/";

if (matcher.find()) {
String responseToClient = "{\n" +
" \"issuer\": \"" + rootURL + "\",\n" +
" \"authorization_endpoint\": \"" + rootURL + "/" + matcher.group(1) + "/oauth/authorize\",\n" +
" \"token_endpoint\": \"" + rootURL + "/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" +
"}";
byte[] responseBytes = responseToClient.getBytes(StandardCharsets.UTF_8);
he.sendResponseHeaders(200, responseBytes.length);
try (OutputStream os = he.getResponseBody()) {
os.write(responseBytes);
}
} else {
String responseToClient = "Bad test: unknown path " + path;
byte[] responseBytes = responseToClient.getBytes(StandardCharsets.UTF_8);
he.sendResponseHeaders(500, responseBytes.length);
try (OutputStream os = he.getResponseBody()) {
os.write(responseBytes);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package org.jenkinsci.plugins.kubernetes.credentials;

import com.cloudbees.plugins.credentials.CredentialsScope;
import com.sun.net.httpserver.HttpServer;
import hudson.util.Secret;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
Expand Down Expand Up @@ -33,37 +31,39 @@ public class OpenShiftBearerTokenCredentialTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();

private Server server;
private HttpServer server;

@Before
public void prepareFakeOAuthServer() throws Exception {
InetSocketAddress addr = new InetSocketAddress("localhost", 0);
server = new Server(addr);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(new ServletHolder(new MockHttpServlet()), "/*");
server.setHandler(context);
InetSocketAddress address = new InetSocketAddress("localhost", 0);
server = HttpServer.create(address, 0);
OpenShiftBearerTokenCredentialMockServer.registerHttpHandlers(server);
server.start();
}

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

private String getURI() {
InetSocketAddress address = server.getAddress();
return "http://" + address.getHostString() + ":" + address.getPort() + "/";
}

@Test
public void testValidResponse() throws IOException {
OpenShiftBearerTokenCredentialImpl t = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, CREDENTIAL_ID, "sample", USERNAME, PASSWORD);
String token = t.getToken(server.getURI() + "valid-response", null, true);
String token = t.getToken(getURI() + "valid-response", null, true);
assertEquals("1234", token);
}

@Test
public void testMultipleCachedTokens() throws IOException {
OpenShiftBearerTokenCredentialImpl t = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, CREDENTIAL_ID, "sample", USERNAME, PASSWORD);
String token1 = t.getToken(server.getURI() + "valid-response", null, true);
String token2 = t.getToken(server.getURI() + "valid-response2", null, true);
String token3 = t.getToken(server.getURI() + "valid-response", null, true);
String token1 = t.getToken(getURI() + "valid-response", null, true);
String token2 = t.getToken(getURI() + "valid-response2", null, true);
String token3 = t.getToken(getURI() + "valid-response", null, true);
assertEquals("1234", token1);
assertEquals("1235", token2);
assertEquals("1234", token3);
Expand All @@ -75,7 +75,7 @@ public void testBadStatusCode() throws IOException {
expectedEx.expectMessage("The response from the OAuth server was invalid: The OAuth service didn't respond with a redirection but with '400: Bad Request'");

OpenShiftBearerTokenCredentialImpl t = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, CREDENTIAL_ID, "sample", USERNAME, PASSWORD);
t.getToken(server.getURI() + "bad-response", null, true);
t.getToken(getURI() + "bad-response", null, true);
}

@Test
Expand All @@ -84,7 +84,7 @@ public void testMissingLocation() throws IOException {
expectedEx.expectMessage("The response from the OAuth server was invalid: The OAuth service didn't respond with location header");

OpenShiftBearerTokenCredentialImpl t = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, CREDENTIAL_ID, "sample", USERNAME, PASSWORD);
t.getToken(server.getURI() + "missing-location", null, true);
t.getToken(getURI() + "missing-location", null, true);
}

@Test
Expand All @@ -93,7 +93,7 @@ public void testBadLocation() throws IOException {
expectedEx.expectMessage("The response from the OAuth server was invalid: The response contained no token");

OpenShiftBearerTokenCredentialImpl t = new OpenShiftBearerTokenCredentialImpl(CredentialsScope.GLOBAL, CREDENTIAL_ID, "sample", USERNAME, PASSWORD);
t.getToken(server.getURI() + "bad-location", null, true);
t.getToken(getURI() + "bad-location", null, true);
}

@Test
Expand Down