Skip to content

Commit

Permalink
Merge pull request #36698 from ozangunalp/strimzi_oauth_native_fix
Browse files Browse the repository at this point in the history
Bump strimzi kafka-oauth-client to 0.14.0 with fix for native
  • Loading branch information
cescoffier authored Oct 30, 2023
2 parents 1e15259 + abdfa22 commit 7b0f197
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 138 deletions.
7 changes: 6 additions & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
<jansi.version>2.4.0</jansi.version> <!-- Keep in sync with aesh-readline and dekorate -->
<jgit.version>6.6.1.202309021850-r</jgit.version>
<!-- these two artifacts needs to be compatible together -->
<strimzi-oauth.version>0.12.0</strimzi-oauth.version>
<strimzi-oauth.version>0.14.0</strimzi-oauth.version>
<strimzi-oauth.nimbus.version>9.34</strimzi-oauth.nimbus.version>
<java-buildpack-client.version>0.0.6</java-buildpack-client.version>
<org-crac.version>0.1.3</org-crac.version>
Expand Down Expand Up @@ -4256,6 +4256,11 @@
<artifactId>kafka-oauth-client</artifactId>
<version>${strimzi-oauth.version}</version>
</dependency>
<dependency>
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-common</artifactId>
<version>${strimzi-oauth.version}</version>
</dependency>
<dependency>
<groupId>io.strimzi</groupId>
<artifactId>strimzi-test-container</artifactId>
Expand Down
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/kafka.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1971,12 +1971,19 @@ First, add the following dependency to your application:
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-client</artifactId>
</dependency>
<!-- if compiling to native you'd need also the following dependency -->
<dependency>
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-common</artifactId>
</dependency>
----

[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
.build.gradle
----
implementation("io.strimzi:kafka-oauth-client")
// if compiling to native you'd need also the following dependency
implementation("io.strimzi:kafka-oauth-common")
----

This dependency provides the callback handler required to handle the OAuth workflow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ public void build(

handleAvro(reflectiveClass, proxies, serviceProviders, sslNativeSupport, capabilities);
handleOpenTracing(reflectiveClass, capabilities);
handleStrimziOAuth(curateOutcomeBuildItem, reflectiveClass);

}

Expand Down Expand Up @@ -330,31 +329,6 @@ private void handleOpenTracing(BuildProducer<ReflectiveClassBuildItem> reflectiv
.build());
}

private void handleStrimziOAuth(CurateOutcomeBuildItem curateOutcomeBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
if (!QuarkusClassLoader.isClassPresentAtRuntime("io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler")) {
return;
}

reflectiveClass
.produce(ReflectiveClassBuildItem.builder("io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler")
.methods().fields().build());

if (curateOutcomeBuildItem.getApplicationModel().getDependencies().stream().anyMatch(
x -> x.getGroupId().equals("org.keycloak") && x.getArtifactId().equals("keycloak-core"))) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder("org.keycloak.jose.jws.JWSHeader",
"org.keycloak.representations.AccessToken",
"org.keycloak.representations.AccessToken$Access",
"org.keycloak.representations.AccessTokenResponse",
"org.keycloak.representations.IDToken",
"org.keycloak.representations.JsonWebToken",
"org.keycloak.jose.jwk.JSONWebKeySet",
"org.keycloak.jose.jwk.JWK",
"org.keycloak.json.StringOrArrayDeserializer",
"org.keycloak.json.StringListMapDeserializer").methods().fields().build());
}
}

private void handleAvro(BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<NativeImageProxyDefinitionBuildItem> proxies,
BuildProducer<ServiceProviderBuildItem> serviceProviders,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.kafka.client.deployment;

import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;

public class StrimziOAuthProcessor {

@BuildStep
public void handleStrimziOAuth(CurateOutcomeBuildItem curateOutcomeBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
if (!QuarkusClassLoader.isClassPresentAtRuntime("io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler")) {
return;
}

reflectiveClass
.produce(ReflectiveClassBuildItem.builder(
"io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler")
.methods().fields().build());

if (curateOutcomeBuildItem.getApplicationModel().getDependencies().stream().anyMatch(
x -> x.getGroupId().equals("org.keycloak") && x.getArtifactId().equals("keycloak-core"))) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder("org.keycloak.jose.jws.JWSHeader",
"org.keycloak.representations.AccessToken",
"org.keycloak.representations.AccessToken$Access",
"org.keycloak.representations.AccessTokenResponse",
"org.keycloak.representations.IDToken",
"org.keycloak.representations.JsonWebToken",
"org.keycloak.jose.jwk.JSONWebKeySet",
"org.keycloak.jose.jwk.JWK",
"org.keycloak.json.StringOrArrayDeserializer",
"org.keycloak.json.StringListMapDeserializer").methods().fields().build());
}
}

}
5 changes: 5 additions & 0 deletions extensions/kafka-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<artifactId>quarkus-kubernetes-service-binding</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-common</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.smallrye.reactive.kafka.graal;

import java.util.function.BooleanSupplier;

import com.jayway.jsonpath.Predicate;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;

final class HasStrimzi implements BooleanSupplier {

@Override
public boolean getAsBoolean() {
try {
KafkaSubstitutions.class.getClassLoader()
.loadClass("io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler");
return true;
} catch (Exception e) {
return false;
}
}
}

@TargetClass(className = "com.jayway.jsonpath.internal.filter.ValueNodes", innerClass = "JsonNode", onlyWith = HasStrimzi.class)
final class Target_com_jayway_jsonpath_internal_filter_ValueNodes_JsonNode {
@Alias
private Object json;
@Alias
private boolean parsed;

@Substitute
public Object parse(Predicate.PredicateContext ctx) {
try {
return parsed ? json : new JacksonJsonProvider().parse(json.toString());
} catch (Throwable e) {
throw new IllegalArgumentException(e);
}
}
}

@TargetClass(className = "com.jayway.jsonpath.internal.filter.ValueNode", onlyWith = HasStrimzi.class)
final class Target_com_jayway_jsonpath_internal_filter_ValueNode {

@Substitute
private static boolean isJson(Object o) {
if (o == null || !(o instanceof String)) {
return false;
}
String str = o.toString().trim();
if (str.length() <= 1) {
return false;
}
char c0 = str.charAt(0);
char c1 = str.charAt(str.length() - 1);
if ((c0 == '[' && c1 == ']') || (c0 == '{' && c1 == '}')) {
try {
new JacksonJsonProvider().parse(str);
return true;
} catch (Exception e) {
return false;
}
}
return false;
}
}

@TargetClass(className = "com.jayway.jsonpath.internal.DefaultsImpl", onlyWith = HasStrimzi.class)
final class Target_com_jayway_jsonpath_internal_DefaultsImpl {

@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
@Alias
public static Target_com_jayway_jsonpath_internal_DefaultsImpl INSTANCE = new Target_com_jayway_jsonpath_internal_DefaultsImpl();

@Substitute
public JsonProvider jsonProvider() {
return new JacksonJsonNodeJsonProvider();
}

@Substitute
public MappingProvider mappingProvider() {
return new JacksonMappingProvider();
}
}
16 changes: 14 additions & 2 deletions integration-tests/kafka-oauth-keycloak/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-client</artifactId>
</dependency>
<!-- required for native compilation as strimzi substitutions require these classes -->
<dependency>
<groupId>io.strimzi</groupId>
<artifactId>kafka-oauth-common</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down Expand Up @@ -71,6 +76,11 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
Expand Down Expand Up @@ -174,7 +184,8 @@
<skip>false</skip>
<systemPropertyVariables>
<!-- Configure the app to test to resolve "keycloak" hostname to the Docker hostname -->
<jdk.net.hosts.file>target/hosts</jdk.net.hosts.file>
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<keycloak.realm.json>${project.basedir}/src/test/resources/keycloak/realms/kafka-authz-realm.json</keycloak.realm.json>
</systemPropertyVariables>
</configuration>
</plugin>
Expand All @@ -183,7 +194,8 @@
<configuration>
<skip>false</skip>
<systemPropertyVariables>
<quarkus.test.arg-line>-Djdk.net.hosts.file=${basedir}/target/hosts</quarkus.test.arg-line>
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<keycloak.realm.json>${project.basedir}/src/test/resources/keycloak/realms/kafka-authz-realm.json</keycloak.realm.json>
</systemPropertyVariables>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ quarkus.log.category.\"org.apache.zookeeper\".level=WARN

mp.messaging.connector.smallrye-kafka.security.protocol=SASL_PLAINTEXT
mp.messaging.connector.smallrye-kafka.sasl.mechanism=OAUTHBEARER
mp.messaging.connector.smallrye-kafka.sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
oauth.client.id="kafka-client" \
oauth.client.secret="kafka-client-secret" \
oauth.token.endpoint.uri="http://keycloak:8080/auth/realms/kafka-authz/protocol/openid-connect/token" ;
mp.messaging.connector.smallrye-kafka.sasl.jaas.config=set_by_test
mp.messaging.connector.smallrye-kafka.sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler

mp.messaging.outgoing.out.connector=smallrye-kafka
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,76 @@
import java.util.HashMap;
import java.util.Map;

import org.jboss.logging.Logger;
import org.testcontainers.utility.MountableFile;

import io.quarkus.it.kafka.containers.KeycloakContainer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import io.quarkus.test.keycloak.server.KeycloakContainer;
import io.strimzi.test.container.StrimziKafkaContainer;

public class KafkaKeycloakTestResource implements QuarkusTestResourceLifecycleManager {

private static final Logger log = Logger.getLogger(KafkaKeycloakTestResource.class);
private StrimziKafkaContainer kafka;
private KeycloakContainer keycloak;
private static final String KEYCLOAK_REALM_JSON = System.getProperty("keycloak.realm.json");

@Override
public Map<String, String> start() {

Map<String, String> properties = new HashMap<>();

//Start keycloak container
keycloak = new KeycloakContainer();
keycloak.start();
log.info(keycloak.getLogs());
keycloak.createHostsFile();
KeycloakTestClient client = new KeycloakTestClient(keycloak.getServerUrl());
client.createRealmFromPath(KEYCLOAK_REALM_JSON);

//Start kafka container
this.kafka = new StrimziKafkaContainer("quay.io/strimzi/kafka:latest-kafka-3.0.0")
.withBrokerId(1)
.withKafkaConfigurationMap(Map.of("listener.security.protocol.map", "JWT:SASL_PLAINTEXT,BROKER1:PLAINTEXT"))
.withKafkaConfigurationMap(Map.of("listener.security.protocol.map",
"JWT:SASL_PLAINTEXT,BROKER1:PLAINTEXT",
"listener.name.jwt.oauthbearer.sasl.jaas.config",
getOauthSaslJaasConfig(keycloak.getInternalUrl(), keycloak.getServerUrl()),
"listener.name.jwt.plain.sasl.jaas.config",
getPlainSaslJaasConfig(keycloak.getInternalUrl(), keycloak.getServerUrl())))
.withNetworkAliases("kafka")
.withServerProperties(MountableFile.forClasspathResource("kafkaServer.properties"))
.withBootstrapServers(
c -> String.format("JWT://%s:%s", c.getHost(), c.getMappedPort(KAFKA_PORT)));
this.kafka.start();
log.info(this.kafka.getLogs());
properties.put("kafka.bootstrap.servers", this.kafka.getBootstrapServers());
properties.put("mp.messaging.connector.smallrye-kafka.sasl.jaas.config",
getClientSaslJaasConfig(keycloak.getServerUrl()));

return properties;
}

private String getClientSaslJaasConfig(String keycloakServerUrl) {
return "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required" +
" oauth.client.id=\"kafka-client\"" +
" oauth.client.secret=\"kafka-client-secret\"" +
" oauth.token.endpoint.uri=\"" + keycloakServerUrl + "/realms/kafka-authz/protocol/openid-connect/token\";";
}

private String getPlainSaslJaasConfig(String keycloakInternalUrl, String keycloakServerUrl) {
return "'org.apache.kafka.common.security.plain.PlainLoginModule required " +
"oauth.jwks.endpoint.uri=\"" + keycloakInternalUrl + "/realms/kafka-authz/protocol/openid-connect/certs\" " +
"oauth.valid.issuer.uri=\"" + keycloakServerUrl + "/realms/kafka-authz\" " +
"oauth.token.endpoint.uri=\"" + keycloakInternalUrl + "/realms/kafka-authz/protocol/openid-connect/token\" " +
"oauth.client.id=\"kafka\" " +
"oauth.client.secret=\"kafka-secret\" " +
"unsecuredLoginStringClaim_sub=\"admin\";'";
}

private String getOauthSaslJaasConfig(String keycloakInternalUrl, String keycloakServerUrl) {
return "'org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required " +
"oauth.jwks.endpoint.uri=\"" + keycloakInternalUrl + "/realms/kafka-authz/protocol/openid-connect/certs\" " +
"oauth.valid.issuer.uri=\"" + keycloakServerUrl + "/realms/kafka-authz\" " +
"oauth.token.endpoint.uri=\"" + keycloakInternalUrl + "/realms/kafka-authz/protocol/openid-connect/token\" " +
"oauth.client.id=\"kafka\" " +
"oauth.client.secret=\"kafka-secret\";'";
}

@Override
public void stop() {
if (kafka != null) {
Expand Down
Loading

0 comments on commit 7b0f197

Please sign in to comment.