[KEYCLOAK-11330] - SSL Support

This commit is contained in:
Pedro Igor 2020-06-05 19:23:12 -03:00 committed by Stian Thorgersen
parent 0e952a5a9f
commit 6ccde288a3
13 changed files with 184 additions and 22 deletions

View file

@ -47,11 +47,11 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
@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);
@ -83,6 +83,22 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
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");

View file

@ -3,7 +3,6 @@ package org.keycloak;
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;
@ -29,7 +28,13 @@ class UserProviderLoader {
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 {
@ -45,11 +50,6 @@ class UserProviderLoader {
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);

View file

@ -47,6 +47,13 @@ public class KeycloakRecorder {
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));
}

View file

@ -914,3 +914,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

View file

@ -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

View file

@ -119,9 +119,10 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
}
@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";
@ -133,8 +134,6 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
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("\">")

View file

@ -2,6 +2,7 @@ package org.keycloak.testsuite.arquillian.containers;
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;
@ -11,12 +12,58 @@ import java.nio.file.Paths;
*/
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() {

View file

@ -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;
@ -19,7 +31,6 @@ import org.jboss.arquillian.core.api.annotation.Inject;
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;
/**
@ -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;
@ -58,6 +70,11 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
@Override
public void stop() throws LifecycleException {
container.destroy();
try {
container.waitFor(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
container.destroyForcibly();
}
}
@Override
@ -86,16 +103,16 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
}
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();
}
@ -110,7 +127,8 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
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()]);
}
@ -121,7 +139,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
// 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) {
@ -133,7 +150,13 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
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());
@ -151,6 +174,41 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
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());
}

View file

@ -20,10 +20,12 @@ package org.keycloak.testsuite.admin;
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;
@ -62,7 +64,9 @@ public class ServerInfoTest extends AbstractKeycloakTest {
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

View file

@ -698,6 +698,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>