Skip to content

Commit

Permalink
Support for OIDC MTLS binding
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Sep 17, 2024
1 parent 435d0ce commit d90d2d9
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 10 deletions.
6 changes: 6 additions & 0 deletions integration-tests/oidc-mtls/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,18 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
<systemPropertyVariables>
<keycloak.version>${keycloak.version}</keycloak.version>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skip>false</skip>
<systemPropertyVariables>
<keycloak.version>${keycloak.version}</keycloak.version>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
quarkus.http.tls-configuration-name=oidc-mtls
quarkus.tls.oidc-mtls.key-store.jks.path=server-keystore.jks
quarkus.tls.oidc-mtls.key-store.jks.password=secret
quarkus.tls.oidc-mtls.trust-store.jks.path=server-truststore.jks
quarkus.tls.oidc-mtls.trust-store.jks.password=password
quarkus.http.tls-configuration-name=http-mtls
quarkus.tls.http-mtls.key-store.jks.path=server-keystore.jks
quarkus.tls.http-mtls.key-store.jks.password=secret
quarkus.tls.http-mtls.trust-store.jks.path=server-truststore.jks
quarkus.tls.http-mtls.trust-store.jks.password=password

quarkus.keycloak.devservices.enabled=false
quarkus.oidc.tls.tls-configuration-name=oidc-tls
quarkus.tls.oidc-tls.key-store.jks.path=oidc-client-keystore.jks
quarkus.tls.oidc-tls.key-store.jks.password=password
quarkus.tls.oidc-tls.trust-store.jks.path=oidc-client-truststore.jks
quarkus.tls.oidc-tls.trust-store.jks.password=password

quarkus.http.auth.inclusive=true

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.quarkus.it.oidc;

import java.io.InputStream;
import java.net.URL;
import java.util.Map;

import org.jboss.logging.Logger;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;

public class KeycloakTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
private static final Logger LOGGER = Logger.getLogger(KeycloakTestResourceLifecycleManager.class);
private GenericContainer<?> keycloak;

private static String KEYCLOAK_SERVER_URL;
private static String CLIENT_KEYSTORE = "oidc-client-keystore.jks";
private static String CLIENT_TRUSTSTORE = "oidc-client-truststore.jks";

private static String SERVER_KEYSTORE = "oidc-server-keystore.jks";
private static String SERVER_KEYSTORE_MOUNTED_PATH = "/etc/oidc-server-keystore.jks";
private static String SERVER_TRUSTSTORE = "oidc-server-truststore.jks";
private static String SERVER_TRUSTSTORE_MOUNTED_PATH = "/etc/oidc-server-truststore.jks";

@SuppressWarnings("resource")
@Override
public Map<String, String> start() {
keycloak = new GenericContainer<>("quay.io/keycloak/keycloak:" + System.getProperty("keycloak.version"))
.withExposedPorts(8080, 8443)
.withEnv("KEYCLOAK_ADMIN", "admin")
.withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin")
.waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1));

keycloak = keycloak
.withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
.withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
.withCommand("build --https-client-auth=required")
.withCommand(String.format(
"start --https-client-auth=required --hostname-strict=false"
+ " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password",
SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH));
keycloak.start();
LOGGER.info(keycloak.getLogs());

KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443);

createRealm();

return Map.of("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/quarkus");
}

private static void createRealm() {
try {
RealmRepresentation realm = loadRealm();
createRequestSpec().auth().oauth2(getAdminAccessToken())
.contentType("application/json")
.body(JsonSerialization.writeValueAsBytes(realm))
.when()
.post(KEYCLOAK_SERVER_URL + "/admin/realms").then()
.statusCode(201);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private static RealmRepresentation loadRealm() throws Exception {
URL realmPathUrl = Thread.currentThread().getContextClassLoader().getResource("quarkus-realm.json");
try (InputStream is = realmPathUrl.openStream()) {
return JsonSerialization.readValue(is, RealmRepresentation.class);
}
}

private static String getAdminAccessToken() {
return createRequestSpec()
.param("grant_type", "password")
.param("username", "admin")
.param("password", "admin")
.param("client_id", "admin-cli")
.when()
.post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token")
.as(AccessTokenResponse.class).getToken();
}

public static String getAccessToken(String userName) {
return createRequestSpec().param("grant_type", "password")
.param("username", userName)
.param("password", userName)
.param("client_id", "backend-service")
.when()
.post(KEYCLOAK_SERVER_URL + "/realms/quarkus/protocol/openid-connect/token")
.as(AccessTokenResponse.class).getToken();
}

@Override
public void stop() {
createRequestSpec().auth().oauth2(getAdminAccessToken())
.when()
.delete(KEYCLOAK_SERVER_URL + "/admin/realms/quarkus").then().statusCode(204);

keycloak.stop();
}

private static RequestSpecification createRequestSpec() {
return RestAssured.given().trustStore(CLIENT_TRUSTSTORE, "password")
.keyStore(CLIENT_KEYSTORE, "password");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.KeyStoreOptions;
Expand All @@ -25,13 +25,12 @@
import io.vertx.mutiny.ext.web.client.WebClient;

@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class OidcMtlsTest {

@TestHTTPResource(ssl = true)
@TestHTTPResource(tls = true)
URL url;

KeycloakTestClient keycloakClient = new KeycloakTestClient();

@Test
public void testGetIdentityNames() throws Exception {
Vertx vertx = Vertx.vertx();
Expand All @@ -41,7 +40,8 @@ public void testGetIdentityNames() throws Exception {

// HTTP 200
HttpResponse<io.vertx.mutiny.core.buffer.Buffer> resp = webClient.get("/service/name")
.putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + keycloakClient.getAccessToken("alice"))
.putHeader("Authorization",
OidcConstants.BEARER_SCHEME + " " + KeycloakTestResourceLifecycleManager.getAccessToken("alice"))
.send().await()
.indefinitely();
assertEquals(200, resp.statusCode());
Expand Down

0 comments on commit d90d2d9

Please sign in to comment.