8000 [KEYCLOAK-11330] - SSL Support by pedroigor · Pull Request #7143 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[KEYCLOAK-11330] - SSL Support #7143

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

Merged
merged 1 commit into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -47,11 +47,11 @@ private QuarkusKeycloakSessionFactory() {

@Override
public void init() {
spis = factories.keySet();
serverStartupTimestamp = System.currentTimeMillis();
ProviderLoader userProviderLoader = createUserProviderLoader();
spis = loadRuntimeSpis(userProviderLoader);

for (Spi spi : factories.keySet()) {
for (Spi spi : spis) {
loadUserProviders(spi, userProviderLoader);
for (Class<? extends ProviderFactory> factoryClazz : factories.get(spi)) {
ProviderFactory factory = lookupProviderFactory(factoryClazz);
Expand Down Expand Up @@ -83,6 +83,22 @@ public void init() {
AdminPermissions.registerListener(this);
}

private Set<Spi> loadRuntimeSpis(ProviderLoader runtimeLoader) {
// most of the time SPIs loaded at build time are enough but under certain circumstances (e.g.: testsuite) we may
// want to load additional SPIs at runtime only from the JARs deployed at the providers dir
List<Spi> loaded = runtimeLoader.loadSpis();

if (loaded.isEmpty()) {
return factories.keySet();
}

Set<Spi> spis = new HashSet<>(factories.keySet());

spis.addAll(loaded);

return spis;
}

@Override
public void deploy(ProviderManager pm) {
throw new RuntimeException("Not supported");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
Expand All @@ -29,7 +28,13 @@ private static ClassLoader createClassLoader(ClassLoader parent) {
String homeDir = System.getProperty("keycloak.home.dir");

if (homeDir == null) {
return parent;
// don't load resources from classpath
return new ClassLoader() {
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return Collections.emptyEnumeration();
}
};
}

try {
Expand All @@ -45,11 +50,6 @@ private static ClassLoader createClassLoader(ClassLoader parent) {
logger.debug("Loading providers from " + urls.toString());

return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent) {
@Override
public InputStream getResourceAsStream(String name) {
return super.getResourceAsStream(name);
}

@Override
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL> resources = findResources(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public liquibase.logging.Logger getLog() {
return logger;
}
});

// we set this property to avoid Liquibase to lookup resources from the classpath and access JAR files
// we already index the packages we want so Liquibase will still be able to load these services
// for uber-jar, this is not a problem because everything is inside the JAR, but once we move to fast-jar we'll have performance penalties
// it seems that v4 of liquibase provides a more smart way of initialization the ServiceLocator that may allow us to remove this
System.setProperty("liquibase.scan.packages", "org.liquibase.core");

ServiceLocator.setInstance(new FastServiceLocator(services));
}

Expand Down
11 changes: 11 additions & 0 deletions testsuite/integration-arquillian/HOW-TO-RUN.md
8000
Original file line number Diff line number Diff line change
Expand Up @@ -911,3 +911,14 @@ When running the test, add the following arguments to the command line:
## Java 11 support
Java 11 requires some arguments to be passed to JVM. Those can be activated using `-Pjava11-auth-server` and
`-Pjava11-app-server` profiles, respectively.

### Running tests using Quarkus distribution

Make sure you build the project using the `quarkus` profile as follows:

mvn -Pdistribution,quarkus clean install

Then, just run tests using the `auth-server-quarkus` profile:

mvn -f testsuite/integration-arquillian/tests/base/pom.xml clean install -Pauth-server-quarkus

Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
hostname.default.frontendUrl = ${keycloak.frontendUrl:}

# Datasource
datasource.jdbc.transactions=xa

# H2
datasource.dialect=org.hibernate.dialect.H2Dialect
datasource.driver=org.h2.jdbcx.JdbcDataSource
datasource.url = jdbc:h2:file:${keycloak.home.dir}/data/keycloakdb;AUTO_SERVER=TRUE
datasource.username = sa
datasource.password = keycloak

# SSL
http.ssl.certificate.key-store-file=${keycloak.home.dir}/conf/keycloak.jks
http.ssl.certificate.key-store-password=secret

# Truststore Provider
truststore.file.file=${keycloak.home.dir}/conf/keycloak.truststore
truststore.file.password=secret
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ public Response clearAdminActions() {
}

@POST
@Consumes(javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_HTML_UTF_8)
@Path("/{action}")
public String post(@PathParam("action") String action) {
public String post(@PathParam("action") String action, MultivaluedMap<String, String> formParams) {
String title = "APP_REQUEST";
if (action.equals("auth")) {
title = "AUTH_RESPONSE";
Expand All @@ -133,8 +134,6 @@ public String post(@PathParam("action") String action) {
sb.append("<html><head><title>" + title + "</title></head><body>");

sb.append("<b>Form parameters: </b><br>");
HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
MultivaluedMap<String, String> formParams = request.getDecodedFormParameters();
for (String paramName : formParams.keySet()) {
sb.append(paramName).append(": ").append("<span id=\"")
.append(paramName).append("\">")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.jboss.arquillian.container.spi.ConfigurationException;
import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration;
import org.jboss.logging.Logger;

import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -11,12 +12,58 @@
*/
public class KeycloakQuarkusConfiguration implements ContainerConfiguration {

protected static final Logger log = Logger.getLogger(KeycloakQuarkusConfiguration.class);

private int bindHttpPortOffset = 100;
private int bindHttpPort = 8080;
private int bindHttpsPortOffset = 0;
private int bindHttpsPort = Integer.valueOf(System.getProperty("auth.server.https.port", "8543"));
private Path providersPath = Paths.get(System.getProperty("auth.server.home"));
private int startupTimeoutInSeconds = 60;

@Override
public void validate() throws ConfigurationException {
int basePort = getBindHttpPort();
int newPort = basePort + bindHttpPortOffset;
setBindHttpPort(newPort);

int baseHttpsPort = getBindHttpsPort();
int newHttpsPort = baseHttpsPort + bindHttpsPortOffset;
setBindHttpsPort(newHttpsPort);

log.info("Keycloak will listen for http on port: " + newPort + " and for https on port: " + newHttpsPort);
}

public int getBindHttpPortOffset() {
return bindHttpPortOffset;
}

public void setBindHttpPortOffset(int bindHttpPortOffset) {
this.bindHttpPortOffset = bindHttpPortOffset;
}

public int getBindHttpsPortOffset() {
return bindHttpsPortOffset;
}

public void setBindHttpsPortOffset(int bindHttpsPortOffset) {
this.bindHttpsPortOffset = bindHttpsPortOffset;
}

public int getBindHttpsPort() {
return this.bindHttpsPort;
}

public void setBindHttpsPort(int bindHttpsPort) {
this.bindHttpsPort = bindHttpsPort;
}

public int getBindHttpPort() {
return bindHttpPort;
}

public void setBindHttpPort(int bindHttpPort) {
this.bindHttpPort = bindHttpPort;
}

public Path getProvidersPath() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package org.keycloak.testsuite.arquillian.containers;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.io.FileUtils;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
Expand All @@ -19,7 +31,6 @@
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.SuiteContext;

/**
Expand All @@ -31,6 +42,7 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta

private KeycloakQuarkusConfiguration configuration;
private Process container;
private AtomicBoolean restart = new AtomicBoolean();

@Inject
private Instance<SuiteContext> suiteContext;
Expand Down Expand Up @@ -58,6 +70,11 @@ public void start() throws LifecycleException {
@Override
public void stop() throws LifecycleException {
container.destroy();
try {
container.waitFor(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
container.destroyForcibly();
}
}

@Override
Expand Down Expand Up @@ -86,16 +103,16 @@ public void undeploy(Descriptor descriptor) throws DeploymentException {
}

private Process startContainer() throws IOException {
if (AuthServerTestEnricher.AUTH_SERVER_SSL_REQUIRED) {
throw new IllegalStateException("Quarkus server does not yet support SSL");
}

ProcessBuilder pb = new ProcessBuilder(getProcessCommands());
File wrkDir = configuration.getProvidersPath().resolve("bin").toAbsolutePath().toFile();
File wrkDir = configuration.getProvidersPath().resolve("bin").toFile();
ProcessBuilder builder = pb.directory(wrkDir).inheritIO();

builder.environment().put("KEYCLOAK_ADMIN", "admin");
builder.environment().put("KEYCLOAK_ADMIN_PASSWORD", "admin");

if (restart.compareAndSet(false, true)) {
FileUtils.deleteDirectory(configuration.getProvidersPath().resolve("data").toFile());
}

return builder.start();
}
Expand All @@ -110,7 +127,8 @@ private String[] getProcessCommands() {
commands.add(System.getProperty("auth.server.debug.port", "5005"));
}

commands.add("-Dquarkus.http.port=" + suiteContext.get().getAuthServerInfo().getContextRoot().getPort());
commands.add("-Dquarkus.http.port=" + configuration.getBindHttpPort());
commands.add("-Dquarkus.http.ssl-port=" + configuration.getBindHttpsPort());

return commands.toArray(new String[commands.size()]);
}
Expand All @@ -121,7 +139,6 @@ private void waitForReadiness() throws MalformedURLException, LifecycleException
// support for MP Health this should change
URL contextRoot = new URL(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/");
HttpURLConnection connection;

long startTime = System.currentTimeMillis();

while (true) {
Expand All @@ -133,7 +150,13 @@ private void waitForReadiness() throws MalformedURLException, LifecycleException
try {
// wait before checking for opening a new connection
Thread.sleep(1000);
connection = (HttpURLConnection) contextRoot.openConnection();
if ("https".equals(contextRoot.toURI().getScheme())) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) (connection = (HttpURLConnection) contextRoot.openConnection());
httpsConnection.setSSLSocketFactory(createInsecureSslSocketFactory());
httpsConnection.setHostnameVerifier(createInsecureHostnameVerifier());
} else {
connection = (HttpURLConnection) contextRoot.openConnection();
}

connection.setReadTimeout((int) getStartTimeout());
connection.setConnectTimeout((int) getStartTimeout());
Expand All @@ -151,6 +174,41 @@ private void waitForReadiness() throws MalformedURLException, LifecycleException
log.infof("Keycloak is ready at %s", this.suiteContext.get().getAuthServerInfo().getContextRoot());
}

private HostnameVerifier createInsecureHostnameVerifier() {
return new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
}

private SSLSocketFactory createInsecureSslSocketFactory() throws IOException {
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
}

public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
}};

SSLContext sslContext;
SSLSocketFactory socketFactory;

try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IOException("Can't create unsecure trust manager");
}
return socketFactory;
}

private long getStartTimeout() {
return TimeUnit.SECONDS.toMillis(configuration.getStartupTimeoutInSeconds());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import org.junit.Test;
import org.keycloak.common.Version;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.info.ProviderRepresentation;
import org.keycloak.representations.info.ServerInfoRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;

import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -62,7 +64,9 @@ public void testServerInfo() {
assertNotNull(info.getSystemInfo().getServerTime());
assertNotNull(info.getSystemInfo().getUptime());

log.infof("JPA Connections provider info: %s", info.getProviders().get("connectionsJpa").getProviders().get("default").getOperationalInfo().toString());
Map<String, ProviderRepresentation> jpaProviders = info.getProviders().get("connectionsJpa").getProviders();
ProviderRepresentation jpaProvider = jpaProviders.values().iterator().next();
log.infof("JPA Connections provider info: %s", jpaProvider.getOperationalInfo().toString());
}

@Override
Expand Down
1 change: 1 addition & 0 deletions testsuite/integration-arquillian/tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@
<auth.server.quarkus>true</auth.server.quarkus>
<auth.server.jboss>false</auth.server.jboss>
<auth.server.undertow>false</auth.server.undertow>
<auth.server.config.dir>${auth.server.home}/conf</auth.server.config.dir>
</properties>
</profile>

Expand Down
0