[KEYCLOAK-11330] - SSL Support
This commit is contained in:
parent
0e952a5a9f
commit
6ccde288a3
13 changed files with 184 additions and 22 deletions
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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.
|
@ -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("\">")
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue