Skip to content

Commit

Permalink
Use TLS + truststore with Keycloak for API OIDC tests
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar committed Jan 8, 2025
1 parent 56b1053 commit 4831808
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.security.KeyStore;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -37,7 +39,7 @@ public class OidcTenantConfigResolver implements TenantConfigResolver {
Logger logger;

@Inject
@ConfigProperty(name = "console.work-path", defaultValue = "${java.io.tmpdir}")
@ConfigProperty(name = "console.work-path")
String workPath;

@Inject
Expand Down Expand Up @@ -75,24 +77,27 @@ Optional<TlsConfiguration> getTlsConfiguration() {
}

void configureTruststore(KeyStore truststore) {
String filename = "%s%s%s-truststore.%s".formatted(
workPath,
File.separator,
UUID.randomUUID().toString(),
truststore.getType()
);
File file = new File(filename);
File workDir = new File(workPath);
File truststoreFile;

try {
truststoreFile = File.createTempFile("oidc-provider-trust", "." + truststore.getType(), workDir);
truststoreFile.deleteOnExit();
} catch (IOException e) {
throw new UncheckedIOException(e);
}

String secret = UUID.randomUUID().toString();

try (OutputStream out = new FileOutputStream(file)) {
try (OutputStream out = new FileOutputStream(truststoreFile)) {
truststore.store(out, secret.toCharArray());
} catch (Exception e) {
throw new RuntimeException(e);
}

// No default provided, set to empty to avoid NPE
oidcConfig.tls.trustStoreProvider = Optional.empty();
oidcConfig.tls.setTrustStoreFile(file.toPath());
oidcConfig.tls.setTrustStoreFile(truststoreFile.toPath());
oidcConfig.tls.setTrustStorePassword(secret);
// Future: map the certificate alias if provided
// oidcConfig.tls.setTrustStoreCertAlias(null);
Expand Down
1 change: 1 addition & 0 deletions api/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ quarkus.arc.exclude-types=io.apicurio.registry.rest.JacksonDateTimeCustomizer
quarkus.index-dependency.strimzi-api.group-id=io.strimzi
quarkus.index-dependency.strimzi-api.artifact-id=api

console.work-path=${java.io.tmpdir}
console.kafka.admin.request.timeout.ms=10000
console.kafka.admin.default.api.timeout.ms=10000

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.github.streamshub.console.kafka.systemtest.deployment;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.time.Duration;
import java.util.Map;

Expand All @@ -12,6 +14,8 @@
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.Transferable;

import com.github.streamshub.console.test.TlsHelper;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

public class KeycloakResourceManager implements QuarkusTestResourceLifecycleManager {
Expand All @@ -29,26 +33,56 @@ public Map<String, String> start() {
throw new UncheckedIOException(ioe);
}

int port = 8443;
TlsHelper tls = TlsHelper.newInstance();
String keystorePath = "/opt/keycloak/keystore.p12";

keycloak = new GenericContainer<>("quay.io/keycloak/keycloak:26.0")
.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("systemtests.keycloak"), true))
.withExposedPorts(8080)
.withExposedPorts(port)
.withEnv(Map.of(
"KC_BOOTSTRAP_ADMIN_USERNAME", "admin",
"KC_BOOTSTRAP_ADMIN_PASSWORD", "admin",
"PROXY_ADDRESS_FORWARDING", "true"))
.withCopyToContainer(
Transferable.of(tls.getKeyStoreBytes()),
keystorePath)
.withCopyToContainer(
Transferable.of(realmConfig),
"/opt/keycloak/data/import/console-realm.json")
.withCommand("start", "--hostname=localhost", "--http-enabled=true", "--import-realm")
.waitingFor(Wait.forHttp("/realms/console-authz").withStartupTimeout(Duration.ofMinutes(1)));
.withCommand(
"start",
"--hostname=localhost",
"--http-enabled=false",
"--https-key-store-file=%s".formatted(keystorePath),
"--https-key-store-password=%s".formatted(String.copyValueOf(tls.getPassphrase())),
"--import-realm"
)
.waitingFor(Wait.forHttps("/realms/console-authz")
.allowInsecure()
.withStartupTimeout(Duration.ofMinutes(1)));

File truststoreFile;

try {
truststoreFile = File.createTempFile("oidc-provider-trust", "." + tls.getTrustStore().getType());
//truststoreFile.deleteOnExit();
Files.write(truststoreFile.toPath(), tls.getTrustStoreBytes());
} catch (IOException e) {
throw new UncheckedIOException(e);
}

keycloak.start();

String urlTemplate = "http://localhost:%d/realms/console-authz";
var oidcUrl = urlTemplate.formatted(keycloak.getMappedPort(8080));
String urlTemplate = "https://localhost:%d/realms/console-authz";
var oidcUrl = urlTemplate.formatted(keycloak.getMappedPort(port));
return Map.of(
"console.test.oidc-url", oidcUrl,
"console.test.oidc-issuer", urlTemplate.formatted(8080));
"console.test.oidc-host", "localhost:%d".formatted(port),
"console.test.oidc-issuer", urlTemplate.formatted(port),
"quarkus.tls.\"oidc-provider-trust\".trust-store.jks.path", truststoreFile.getAbsolutePath(),
"quarkus.tls.\"oidc-provider-trust\".trust-store.jks.password", String.copyValueOf(tls.getPassphrase())
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,41 @@
import java.io.StringReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

import javax.net.ssl.SSLContext;

import jakarta.enterprise.inject.spi.CDI;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.ws.rs.core.HttpHeaders;

import org.eclipse.microprofile.config.Config;

import io.quarkus.tls.TlsConfigurationRegistry;
import io.restassured.http.Header;

public class TokenUtils {

final String tokenEndpoint;
final String tokenEndpointHost;
final SSLContext tls;

public TokenUtils(Config config) {
this.tokenEndpoint = config.getValue("console.test.oidc-url", String.class) + "/protocol/openid-connect/token";
this.tokenEndpointHost = config.getValue("console.test.oidc-host", String.class);

var tlsRegistry = CDI.current().select(TlsConfigurationRegistry.class).get();

try {
tls = tlsRegistry.get("oidc-provider-trust").get().createSSLContext();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public Header authorizationHeader(String username) {
Expand All @@ -47,11 +63,14 @@ public JsonObject getTokenObject(String username) {
+ "password=%1$s-password&"
+ "client_id=console-client", username);

HttpClient client = HttpClient.newBuilder().build();
HttpClient client = HttpClient.newBuilder()
.sslContext(tls)
.version(Version.HTTP_1_1)
.build();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenEndpoint))
.header("Host", "localhost:8080")
.header("Host", tokenEndpointHost)
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
Expand Down
Loading

0 comments on commit 4831808

Please sign in to comment.