Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump strimzi kafka-oauth-client to 0.14.0 with fix for native #36698

Merged
merged 3 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
<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"]

Check warning on line 1981 in docs/src/main/asciidoc/kafka.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.CaseSensitiveTerms] Use 'Gradle' rather than 'gradle'. Raw Output: {"message": "[Quarkus.CaseSensitiveTerms] Use 'Gradle' rather than 'gradle'.", "location": {"path": "docs/src/main/asciidoc/kafka.adoc", "range": {"start": {"line": 1981, "column": 41}}}, "severity": "INFO"}
.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>
cescoffier marked this conversation as resolved.
Show resolved Hide resolved
<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>
cescoffier marked this conversation as resolved.
Show resolved Hide resolved
<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
cescoffier marked this conversation as resolved.
Show resolved Hide resolved
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
Loading