This is an automated email from the ASF dual-hosted git repository.

claudio4j pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot-examples.git


The following commit(s) were added to refs/heads/main by this push:
     new 3e3107a  Add camel kafka oauth example (#149)
3e3107a is described below

commit 3e3107a2af7382f947105733f09c826b40fc8a55
Author: Claudio Miranda <[email protected]>
AuthorDate: Thu Jan 16 07:13:30 2025 -0300

    Add camel kafka oauth example (#149)
---
 kafka-oauth/README.adoc                            | 126 ++++++++++++
 kafka-oauth/docker/certificates/ca-truststore.p12  | Bin 0 -> 1578 bytes
 kafka-oauth/docker/certificates/ca.crt             |  29 +++
 kafka-oauth/docker/certificates/ca.key             |  51 +++++
 kafka-oauth/docker/certificates/gen-ca.sh          |  18 ++
 kafka-oauth/docker/kafka-oauth-strimzi/compose.yml |  79 +++++++
 .../docker/kafka-oauth-strimzi/kafka/Dockerfile    |  12 ++
 .../kafka/certificates/cluster.keystore.p12        | Bin 0 -> 5598 bytes
 .../kafka/certificates/cluster.truststore.p12      | Bin 0 -> 1670 bytes
 .../kafka/certificates/gen-kafka-certs.sh          |  21 ++
 .../kafka/config/ca-truststore.p12                 | Bin 0 -> 1578 bytes
 .../kafka/config/log4j.properties                  |  94 +++++++++
 .../docker/kafka-oauth-strimzi/kafka/functions.sh  |  17 ++
 .../kafka/simple_kafka_config.sh                   | 103 ++++++++++
 .../docker/kafka-oauth-strimzi/kafka/start.sh      |  31 +++
 .../docker/keycloak/certificates/ca-truststore.p12 | Bin 0 -> 1578 bytes
 .../keycloak/certificates/gen-keycloak-certs.sh    |  20 ++
 .../certificates/keycloak.server.keystore.p12      | Bin 0 -> 5419 bytes
 kafka-oauth/docker/keycloak/compose.yml            |  19 ++
 kafka-oauth/docker/keycloak/realms/demo-realm.json | 219 ++++++++++++++++++++
 kafka-oauth/pom.xml                                | 228 +++++++++++++++++++++
 .../apache/camel/example/kafka/Application.java    |  35 ++++
 .../java/org/apache/camel/example/kafka/Route.java |  36 ++++
 .../src/main/resources/application.properties      |  37 ++++
 pom.xml                                            |   1 +
 25 files changed, 1176 insertions(+)

diff --git a/kafka-oauth/README.adoc b/kafka-oauth/README.adoc
new file mode 100644
index 0000000..f76aacf
--- /dev/null
+++ b/kafka-oauth/README.adoc
@@ -0,0 +1,126 @@
+== Camel Kafka with OAuth authentication example
+
+=== Introduction
+
+An example which shows how to integrate 
https://camel.apache.org/components/next/kafka-component.html[Camel with Kafka] 
with OAuth authentication using a client secret. The authentication is handled 
by Keycloak.
+
+This example requires docker-compose as it will build and run a keycloak and 
kafka broker (setup with kraft mode). 
+
+On the Kafka side it uses 
https://github.com/strimzi/strimzi-kafka-oauth[Strimzi Oauth for Apache Kafka], 
this library must also be set on the client side.
+
+The Kafka Oauth client side configuration is set in the 
`src/main/resources/application.properties`. You may want to learn from the 
Strimzi OAuth project the numerous configurations to have it working with your 
Kafka Broker, for example you may want to use OAuth Refresh tokens or use JWT 
tokens.
+
+=== The Kafka Oauth configuration
+
+The configuration is in `src/main/resources/application.properties`, you are 
welcome to learn more from the 
https://kafka.apache.org/documentation/#security[Kafka Security] and 
https://github.com/strimzi/strimzi-kafka-oauth[Strimzi OAuth] documentations.
+----
+camel.component.kafka.security-protocol = SASL_PLAINTEXT
+camel.component.kafka.sasl-mechanism = OAUTHBEARER
+camel.component.kafka.sasl-jaas-config = 
org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
+  oauth.client.id="kafka-producer-client" \
+  oauth.client.secret="kafka-producer-client-secret" \
+  oauth.username.claim="preferred_username" \
+  oauth.ssl.truststore.location="docker/certificates/ca-truststore.p12" \
+  oauth.ssl.truststore.type="pkcs12" \
+  oauth.ssl.truststore.password="changeit" \
+  
oauth.token.endpoint.uri="https://keycloak:8443/realms/demo/protocol/openid-connect/token";
 ;
+camel.component.kafka.additional-properties[sasl.login.callback.handler.class]=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
+----
+
+=== Building and running Keycloak and Kafka
+
+* Set the hosts in /etc/hosts
+
+We have to set the IP addresses in /etc/hosts (check your OS how to do it), 
verify the current IP address and correctly set it as the example shows.
+----
+192.168.0.104   keycloak
+192.168.0.104   kafka
+----
+
+* Build the project
+
+This command will download the required Strimzi OAuth libraries for Kafka and 
add it to the Kafka Broker directory, it will also build the camel spring boot 
example.
+----
+mvn package
+----
+
+* Launch the Keycloak server
+
+----
+cd docker
+docker-compose -f keycloak/compose.yml up 
+----
+
+It must show the `demo` realm was imported successfully.
+----
+[org.keycloak.exportimport.dir.DirImportProvider] (main) Importing from 
directory /opt/keycloak/bin/../data/import
+[org.keycloak.exportimport.util.ImportUtils] (main) Realm 'demo' imported
+[org.keycloak.services] (main) KC-SERVICES0032: Import finished successfully
+----
+
+It also shows the server started.
+----
+[io.quarkus] (main) Keycloak 26.0.8 on JVM (powered by Quarkus 3.15.1) started 
in 9.169s. Listening on: http://0.0.0.0:8080 and https://0.0.0.0:8443
+[io.quarkus] (main) Profile prod activated. 
+[io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, 
keycloak, narayana-jta, opentelemetry, reactive-routes, rest, rest-jackson, 
smallrye-context-propagation, vertx]
+----
+
+* Launch the Kafka broker
+
+Open another terminal console and go to the docker directory:
+----
+cd docker
+docker-compose -f kafka-oauth-strimzi/compose.yml up --build
+----
+
+It should show the kafka broker authenticated to the keycloak server using the 
kafka-broker client id.
+----
+loginWithClientSecret() - tokenEndpointUrl: 
http://keycloak:8080/realms/demo/protocol/openid-connect/token, clientId: 
kafka-broker, clientSecret: k*********, scope: null, audience: null, 
connectTimeout: 20, readTimeout
+: 60, retries: 0, retryPauseMillis: 0 
(io.strimzi.kafka.oauth.common.OAuthAuthenticator)
+----
+
+It should show the kafka broker started
+----
+Kafka version: 3.9.0 (org.apache.kafka.common.utils.AppInfoParser)
+
+[KafkaRaftServer nodeId=1] Kafka Server started (kafka.server.KafkaRaftServer)
+----
+
+=== Run the camel example
+
+As the project was already built, it's ready to run:
+
+----
+mvn spring-boot:run
+----
+
+It should display the kafka OAuth settings, example:
+----
+sasl.login.callback.handler.class = class 
io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
+sasl.mechanism = OAUTHBEARER
+security.protocol = SASL_PLAINTEXT
+----
+
+It should show the producer message and the consumer message.
+----
+[a_Topic1]] route1 : >> Message sent: Hi from Camel - Wed Jan 15 12:11:42 WET 
2025
+[a_Topic1]] route2 : << Message received: Hi from Camel - Wed Jan 15 12:11:42 
WET 2025
+----
+
+The kafka broker log should display the OAuth logging.
+----
+DEBUG Set validated token on callback: BearerTokenWithPayloadImpl 
(principalName: service-account-kafka-producer-client, groups: null, 
lifetimeMs: 1736978965000 [2025-01-15T22:09:25 UTC], startTimeMs: 1736942965000 
[2025-01-15T12:09:25 UTC], scope: [profile, email], payload: 
{"exp":1736978965,"iat":1736942965,"jti":"43781656-a432-47f5-b0ae-c44e3224bb2b","iss":"https://keycloak:8443/realms/demo","sub":"f288b7db-a3e4-4cf4-80d3-2e5118bb2c9c","typ":"Bearer","azp":"kafka-producer-client","acr
 [...]
+----
+
+Press `Ctrl-C` to exit.
+
+=== Help and contributions
+
+If you hit any problem using Camel or have some feedback, 
+then please https://camel.apache.org/community/support/[let us know].
+
+We also love contributors, 
+so https://camel.apache.org/community/contributing/[get involved] :-)
+
+The Camel riders!
+
diff --git a/kafka-oauth/docker/certificates/ca-truststore.p12 
b/kafka-oauth/docker/certificates/ca-truststore.p12
new file mode 100644
index 0000000..5899e5c
Binary files /dev/null and b/kafka-oauth/docker/certificates/ca-truststore.p12 
differ
diff --git a/kafka-oauth/docker/certificates/ca.crt 
b/kafka-oauth/docker/certificates/ca.crt
new file mode 100644
index 0000000..2158dc9
--- /dev/null
+++ b/kafka-oauth/docker/certificates/ca.crt
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIFCzCCAvOgAwIBAgIUZ0kDv3zox1niOA1aqt1mgw5waSkwDQYJKoZIhvcNAQEL
+BQAwFTETMBEGA1UEAwwKc3RyaW16aS5pbzAeFw0yMTAzMjYwOTE4MTBaFw0zMTAz
+MjQwOTE4MTBaMBUxEzARBgNVBAMMCnN0cmltemkuaW8wggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC7uFCvR4S6tSAFxAmzbjtGEV8XxA5iAZKL+TlMbs65
+riT1YYq0uU2JoXeWQUQRrwyPM5822klK0dVfTmrct0fVvPK7hQkAUatmmgczimxg
+ndmJPtnJaypG4v9C1Nn2Ahh02aCafKk1wjhfuELSC/pLwdF3r3qiQUZM04JZIC6y
+s9iqW7ehkTfaC5RV0nFXK0YfZI6rummXxIfrvv6oEzzcbmSkkB9vgKK1X7bJNTxR
+qfwto8/d3nV51ZAmMUi1tKsT7N51yNvBVv0/x7FRfx9VFLsGDNr4t6rWP41YG7xa
+yxlXDFSW5gJBBkt1lbrm2A+QfeaXKJGaApW3vJkCQdLo1JpNZP6qGnss+ctpMwTD
+Bxgigr9DAa9dCjkmoDq3Pew46svrmzDvFu/++f+OAcwFuqRYA1h8m5fgJ9pwhyfV
+r+KNR9yLs7ot1F9ntd4M2JaeiaFAePrQ0eCe3PlL1eGgrXQ54EwdPPt7lSbidCEO
+bWnkxLC95ErM+QJdx2nSYV98vRBrPyQwyaQHIXdHHC1jXAK8oxrmpzVDXo240emj
+1p/qxxMvfm97jrZbnOKs3LLEMT8iA2RrRClsWpm4MROZhwyi/tv9nBHEy/MsB8kt
++4TkZlBDbZVV3XOwCuNvBEAU3MlNOw5Nb6ppcxtBjVLy8ekK1F53k1q576ncv2TV
+kQIDAQABo1MwUTAdBgNVHQ4EFgQU9hhGe0vri3zDq+hsZmuqTZzatiwwHwYDVR0j
+BBgwFoAU9hhGe0vri3zDq+hsZmuqTZzatiwwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEALmrWaYSPjoVryvjxH9ANXqRGdM/6BkcI/uP668K2AiS+
+IJY2V4kVl908hs24l6Rd1/wCenKz2WoTZ5495JFIqjfZtOuMzQ2HGHZMDp+SaPYz
+ZlzMP/yv5mNJ5HtPuaLGs+7oNR6QKWpvQNnVFfsb2qCTwsSCcbLNvRwAhYOHo1/V
+HAB1ho29BBkUXaweLbBEijQIxufcK2XIXn/KksbQB0Cr8uuscrZx4UixVKIz2vx5
+8iJERwc1ox7SdKKrx8KYFy/pX7ppA1z/wRBQunD3JmSHHMl0ffWDkNrfCHmtjsB5
+Z9JfbPHT6laMa6WAjpD+X64O24JwnzodqMqK1DFJOLkylPq+S2daYc3AAVuOLxQ3
+SwqxZwQsFQFnldSI2+HqYJuEFsZ5AZSioW+JS+ogH+xsRzENJUwAh12P+8nEGzZf
+pGBEbdBoEuGJ4GG2EbrbOPcZ2jHnQlrh247NDeNeqTbugVOVui+dlANBOQmyd2xw
+sJMSUuwN4MJTNqcWC8jVsPfLprxEdYa35lFiWI/ut35QnIn8nO0CMrZ/jSRc5j3a
+sN+FnLcsqY1bbKutzeGu/AmLXMm8PYOCaEKbMuJVp9qslhthv8Qz0XQg6g6mnxlM
+xugFfjB++LglJ+zVKzEtL2BCc21QpI7fQXqb7HJd2H1H1IgoMG6eyCgzHUUiCFg=
+-----END CERTIFICATE-----
diff --git a/kafka-oauth/docker/certificates/ca.key 
b/kafka-oauth/docker/certificates/ca.key
new file mode 100644
index 0000000..874cd32
--- /dev/null
+++ b/kafka-oauth/docker/certificates/ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/kafka-oauth/docker/certificates/gen-ca.sh 
b/kafka-oauth/docker/certificates/gen-ca.sh
new file mode 100755
index 0000000..dd84237
--- /dev/null
+++ b/kafka-oauth/docker/certificates/gen-ca.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+set -e
+
+# create CA key
+openssl genrsa -out ca.key 4096
+
+# create CA certificate
+openssl req -x509 -new -nodes -sha256 -days 3650 -subj "/CN=strimzi.io" -key 
ca.key -out ca.crt
+
+
+PASSWORD=changeit
+
+# create p12 truststore
+keytool -keystore ca-truststore.p12 -storetype pkcs12 -alias ca -storepass 
$PASSWORD -keypass $PASSWORD -import -file ca.crt -noprompt
+
+# copy the certificate to client dirs
+cp ca-truststore.p12 ../kafka-oauth-strimzi/kafka/config/
+cp ca-truststore.p12 ../keycloak-import/config/
\ No newline at end of file
diff --git a/kafka-oauth/docker/kafka-oauth-strimzi/compose.yml 
b/kafka-oauth/docker/kafka-oauth-strimzi/compose.yml
new file mode 100644
index 0000000..46831ca
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/compose.yml
@@ -0,0 +1,79 @@
+services:
+
+  #################################### KAFKA BROKER 
####################################
+  kafka:
+    image: strimzi/example-kafka
+    build: kafka/target
+    container_name: kafka
+    ports:
+      - 9091:9091
+      - 9092:9092
+
+      # javaagent debug port
+      #- 5005:5005
+    command:
+      - /bin/bash
+      - -c
+      - cd /opt/kafka && ./start.sh --kraft
+
+    environment:
+
+      # Java Debug
+      #KAFKA_DEBUG: y
+      #DEBUG_SUSPEND_FLAG: y
+      #JAVA_DEBUG_PORT: 5005
+
+      #
+      # KAFKA Configuration
+      #
+      LOG_DIR: /home/kafka/logs
+      KAFKA_PROCESS_ROLES: "broker,controller"
+      KAFKA_NODE_ID: "1"
+      KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9091"
+      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+      KAFKA_SASL_MECHANISM_CONTROLLER_PROTOCOL: PLAIN
+
+      KAFKA_LISTENERS: "CONTROLLER://kafka:9091,CLIENT://kafka:9092"
+      KAFKA_ADVERTISED_LISTENERS: "CLIENT://kafka:9092"
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 
"CONTROLLER:SASL_PLAINTEXT,CLIENT:SASL_PLAINTEXT"
+
+      KAFKA_INTER_BROKER_LISTENER_NAME: CLIENT
+      KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: OAUTHBEARER
+
+      KAFKA_PRINCIPAL_BUILDER_CLASS: 
"io.strimzi.kafka.oauth.server.OAuthKafkaPrincipalBuilder"
+
+      KAFKA_LISTENER_NAME_CONTROLLER_SASL_ENABLED_MECHANISMS: PLAIN
+      KAFKA_LISTENER_NAME_CONTROLLER_PLAIN_SASL_JAAS_CONFIG: 
"org.apache.kafka.common.security.plain.PlainLoginModule required    
username=\"admin\"    password=\"admin-password\"    
user_admin=\"admin-password\"    user_bobby=\"bobby-secret\" ;"
+
+      KAFKA_LISTENER_NAME_CLIENT_SASL_ENABLED_MECHANISMS: OAUTHBEARER
+      KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_JAAS_CONFIG: 
"org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"
+      
KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_LOGIN_CALLBACK_HANDLER_CLASS: 
io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
+      
KAFKA_LISTENER_NAME_CLIENT_OAUTHBEARER_SASL_SERVER_CALLBACK_HANDLER_CLASS: 
io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
+
+      KAFKA_SUPER_USERS: "User:admin,User:service-account-kafka-broker"
+
+      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+
+
+      #
+      # Strimzi OAuth Configuration
+      #
+
+      # Authentication config
+      OAUTH_CLIENT_ID: "kafka-broker"
+      OAUTH_CLIENT_SECRET: "kafka-broker-secret"
+      OAUTH_TOKEN_ENDPOINT_URI: 
"http://${KEYCLOAK_HOST:-keycloak}:8080/realms/${REALM:-demo}/protocol/openid-connect/token";
+
+      # Validation config
+      OAUTH_VALID_ISSUER_URI: 
"https://${KEYCLOAK_HOST:-keycloak}:8443/realms/${REALM:-demo}";
+      OAUTH_JWKS_ENDPOINT_URI: 
"http://${KEYCLOAK_HOST:-keycloak}:8080/realms/${REALM:-demo}/protocol/openid-connect/certs";
+      #OAUTH_INTROSPECTION_ENDPOINT_URI: 
"http://${KEYCLOAK_HOST:-keycloak}:8080/realms/${REALM:-demo}/protocol/openid-connect/token/introspect";
+
+
+      # username extraction from JWT token claim
+      OAUTH_USERNAME_CLAIM: preferred_username
+      OAUTH_CONNECT_TIMEOUT_SECONDS: "20"
+
+      # For start.sh script to know where the keycloak is listening
+      KEYCLOAK_HOST: ${KEYCLOAK_HOST:-keycloak}
+      KEYCLOAK_URI: https://keycloak:8443
diff --git a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/Dockerfile 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/Dockerfile
new file mode 100644
index 0000000..c5f1b84
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/Dockerfile
@@ -0,0 +1,12 @@
+FROM quay.io/strimzi/kafka:0.45.0-kafka-3.9.0
+
+COPY libs/* /opt/kafka/libs/strimzi/
+COPY config/* /opt/kafka/config/
+COPY *.sh /opt/kafka/
+COPY certificates/*.p12 /tmp/kafka/
+
+USER root
+RUN chmod +x /opt/kafka/*.sh
+USER kafka
+
+CMD ["/bin/bash", "/opt/kafka/start.sh"]
\ No newline at end of file
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.keystore.p12
 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.keystore.p12
new file mode 100644
index 0000000..3099386
Binary files /dev/null and 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.keystore.p12
 differ
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.truststore.p12
 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.truststore.p12
new file mode 100644
index 0000000..a477ec4
Binary files /dev/null and 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/cluster.truststore.p12
 differ
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/gen-kafka-certs.sh 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/gen-kafka-certs.sh
new file mode 100755
index 0000000..e4772bd
--- /dev/null
+++ 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/certificates/gen-kafka-certs.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+
+STOREPASS=Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L
+
+echo "#### Generate broker keystore"
+keytool -keystore cluster.keystore.p12 -alias localhost -validity 3650 -genkey 
-keyalg RSA -ext SAN=DNS:kafka -dname "CN=my-cluster-kafka,O=io.strimzi" 
-deststoretype pkcs12 -storepass $STOREPASS -keypass $STOREPASS
+
+echo "#### Add the CA to the brokers’ truststore"
+keytool -keystore cluster.truststore.p12 -deststoretype pkcs12 -storepass 
$STOREPASS -alias CARoot -importcert -file ../../../certificates/ca.crt 
-noprompt
+
+echo "#### Export the certificate from the keystore"
+keytool -keystore cluster.keystore.p12 -storetype pkcs12 -alias localhost 
-certreq -file cert-file -storepass $STOREPASS
+
+echo "#### Sign the certificate with the CA"
+openssl x509 -req -CA ../../../certificates/ca.crt -CAkey 
../../../certificates/ca.key -in cert-file -out cert-signed -days 3650 
-CAcreateserial -passin pass:$STOREPASS
+
+echo "#### Import the CA and the signed certificate into the broker keystore"
+keytool -keystore cluster.keystore.p12 -deststoretype pkcs12 -alias CARoot 
-import -file ../../../certificates/ca.crt -storepass $STOREPASS -noprompt
+keytool -keystore cluster.keystore.p12 -deststoretype pkcs12 -alias localhost 
-import -file cert-signed -storepass $STOREPASS -noprompt
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/ca-truststore.p12 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/ca-truststore.p12
new file mode 100644
index 0000000..5899e5c
Binary files /dev/null and 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/ca-truststore.p12 differ
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/log4j.properties 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/log4j.properties
new file mode 100644
index 0000000..364addd
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/config/log4j.properties
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Unspecified loggers and loggers with additivity=true output to server.log 
and stdout
+# Note that INFO only applies to unspecified loggers, the log level of the 
child logger is used otherwise
+log4j.rootLogger=INFO, stdout, kafkaAppender
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.kafkaAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.kafkaAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.kafkaAppender.File=${kafka.logs.dir}/server.log
+log4j.appender.kafkaAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.kafkaAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.stateChangeAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.stateChangeAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.stateChangeAppender.File=${kafka.logs.dir}/state-change.log
+log4j.appender.stateChangeAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.stateChangeAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.requestAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.requestAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.requestAppender.File=${kafka.logs.dir}/kafka-request.log
+log4j.appender.requestAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.requestAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.cleanerAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.cleanerAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.cleanerAppender.File=${kafka.logs.dir}/log-cleaner.log
+log4j.appender.cleanerAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.cleanerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.controllerAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.controllerAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.controllerAppender.File=${kafka.logs.dir}/controller.log
+log4j.appender.controllerAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.controllerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+log4j.appender.authorizerAppender=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.authorizerAppender.DatePattern='.'yyyy-MM-dd-HH
+log4j.appender.authorizerAppender.File=${kafka.logs.dir}/kafka-authorizer.log
+log4j.appender.authorizerAppender.layout=org.apache.log4j.PatternLayout
+log4j.appender.authorizerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n
+
+# Change the two lines below to adjust ZK client logging
+log4j.logger.org.I0Itec.zkclient.ZkClient=INFO
+log4j.logger.org.apache.zookeeper=INFO
+
+# Change the two lines below to adjust the general broker logging level 
(output to server.log and stdout)
+log4j.logger.kafka=INFO
+log4j.logger.org.apache.kafka=INFO
+
+# Control Strimzi OAuth logging
+log4j.logger.io.strimzi=DEBUG
+
+# Change to DEBUG or TRACE to enable request logging
+log4j.logger.kafka.request.logger=WARN, requestAppender
+log4j.additivity.kafka.request.logger=false
+
+# Uncomment the lines below and change 
log4j.logger.kafka.network.RequestChannel$ to TRACE for additional output
+# related to the handling of requests
+#log4j.logger.kafka.network.Processor=TRACE, requestAppender
+#log4j.logger.kafka.server.KafkaApis=TRACE, requestAppender
+#log4j.additivity.kafka.server.KafkaApis=false
+log4j.logger.kafka.network.RequestChannel$=WARN, requestAppender
+log4j.additivity.kafka.network.RequestChannel$=false
+
+# log4j.logger.kafka.controller=TRACE, controllerAppender
+# log4j.additivity.kafka.controller=false
+
+log4j.logger.kafka.log.LogCleaner=INFO, cleanerAppender
+log4j.additivity.kafka.log.LogCleaner=false
+
+# log4j.logger.state.change.logger=TRACE, stateChangeAppender
+# log4j.additivity.state.change.logger=false
+
+# Access denials are logged at INFO level, change to DEBUG to also log allowed 
accesses
+log4j.logger.kafka.authorizer.logger=INFO, authorizerAppender
+log4j.additivity.kafka.authorizer.logger=false
diff --git a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/functions.sh 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/functions.sh
new file mode 100644
index 0000000..a59094e
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/functions.sh
@@ -0,0 +1,17 @@
+
+wait_for_url() {
+    URL=$1
+    MSG=$2
+
+    if [[ $URL == https* ]]; then
+        CMD="curl -k -sL -o /dev/null -w %{http_code} $URL"
+    else
+        CMD="curl -sL -o /dev/null -w %{http_code} $URL"
+    fi
+
+    until [ "200" == "`$CMD`" ]
+    do
+        echo "$MSG ($URL)"
+        sleep 2
+    done
+}
\ No newline at end of file
diff --git 
a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/simple_kafka_config.sh 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/simple_kafka_config.sh
new file mode 100755
index 0000000..48ad9c6
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/simple_kafka_config.sh
@@ -0,0 +1,103 @@
+#!/bin/bash
+
+declare -A props
+
+to_property_name() {
+  key=$1
+  echo ${key:6} | tr _ . | tr [:upper:] [:lower:]
+}
+
+pop_value() {
+  key=$1
+  fallback=$2
+
+  if [ -z ${props[$key]+x} ] ; then
+    echo $fallback
+  else
+    echo ${props[$key]}
+  fi
+  unset props[$key]
+}
+
+#
+# This function allows you to encode as KAFKA_* env vars property names that 
contain characters invalid for env var names
+# You can use:
+#   KAFKA_LISTENER_NAME_CLIENT_SCRAM__2DSHA__2D256_SASL_JAAS_CONFIG=something
+#
+# Which will first be converted to:
+#   KAFKA_LISTENER_NAME_CLIENT_SCRAM%2DSHA%2D256_SASL_JAAS_CONFIG=something
+#
+# And then to:
+#   KAFKA_LISTENER_NAME_CLIENT_SCRAM-SHA-256_SASL_JAAS_CONFIG=something
+#
+unescape() {
+  if [[ "$1" != "" ]]; then
+    echo "$1" | sed -e "s@__@\%@g" -e "s@+@ @g;s@%@\\\\x@g" | xargs -0 printf 
"%b"
+  fi
+}
+
+unset IFS
+for var in $(compgen -e); do
+  if [[ $var == KAFKA_* ]]; then
+
+    case $var in
+      
KAFKA_DEBUG|KAFKA_OPTS|KAFKA_VERSION|KAFKA_HOME|KAFKA_CHECKSUM|KAFKA_LOG4J_OPTS|KAFKA_HEAP_OPTS|KAFKA_JVM_PERFORMANCE_OPTS|KAFKA_GC_LOG_OPTS|KAFKA_JMX_OPTS)
 ;;
+      *)
+        props[$(to_property_name $(unescape $var))]=${!var}
+      ;;
+    esac
+  fi
+done
+
+#
+# Generate output
+#
+
+if [[ "$1" == "--kraft" ]]; then
+  #
+  # Output kraft version of server.properties
+  #
+  echo "#"
+  echo "# strimzi.properties (kraft)"
+  echo "#"
+
+  echo process.roles=`pop_value process.roles broker,controller`
+  echo node.id=`pop_value node.id 1`
+  echo log.dirs=`pop_value log.dirs /tmp/kraft-combined-logs`
+
+elif [[ "$1" == "" ]]; then
+  echo "#"
+  echo "# strimzi.properties"
+  echo "#"
+
+  echo broker.id=`pop_value broker.id 0`
+  echo log.dirs=`pop_value log.dirs /tmp/kafka-logs`
+  echo group.initial.rebalance.delay.ms=`pop_value 
group.initial.rebalance.delay.ms 0`
+else
+  echo "Unsupported argument: $1"
+  exit 1
+fi
+
+echo num.network.threads=`pop_value num.network.threads 3`
+echo num.io.threads=`pop_value num.io.threads 8`
+echo socket.send.buffer.bytes=`pop_value socket.send.buffer.bytes 102400`
+echo socket.receive.buffer.bytes=`pop_value socket.receive.buffer.bytes 102400`
+echo socket.request.max.bytes=`pop_value socket.request.max.bytes 104857600`
+echo num.partitions=`pop_value num.partitions 1`
+echo num.recovery.threads.per.data.dir=`pop_value 
num.recovery.threads.per.data.dir 1`
+echo offsets.topic.replication.factor=`pop_value 
offsets.topic.replication.factor 1`
+echo transaction.state.log.replication.factor=`pop_value 
transaction.state.log.replication.factor 1`
+echo transaction.state.log.min.isr=`pop_value transaction.state.log.min.isr 1`
+echo log.retention.hours=`pop_value log.retention.hours 168`
+echo log.segment.bytes=`pop_value log.segment.bytes 1073741824`
+echo log.retention.check.interval.ms=`pop_value 
log.retention.check.interval.ms 300000`
+
+#
+# Add what remains of KAFKA_* env vars
+#
+for K in "${!props[@]}"
+do
+  echo $K=`pop_value $K`
+done
+
+echo
\ No newline at end of file
diff --git a/kafka-oauth/docker/kafka-oauth-strimzi/kafka/start.sh 
b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/start.sh
new file mode 100755
index 0000000..8f4d721
--- /dev/null
+++ b/kafka-oauth/docker/kafka-oauth-strimzi/kafka/start.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+
+source functions.sh
+
+URI=${KEYCLOAK_URI}
+if [ "" == "${URI}" ]; then
+    URI="http://${KEYCLOAK_HOST:-keycloak}:8080";
+fi
+
+wait_for_url $URI "Waiting for Keycloak to start"
+
+wait_for_url "$URI/realms/${REALM:-demo}" "Waiting for realm '${REALM}' to be 
available"
+
+if [ "$SERVER_PROPERTIES_FILE" == "" ]; then
+  echo "Generating a new strimzi.properties file using ENV vars"
+  ./simple_kafka_config.sh $1 | tee /tmp/strimzi.properties
+else
+  echo "Using provided server.properties file: $SERVER_PROPERTIES_FILE"
+  cp $SERVER_PROPERTIES_FILE /tmp/strimzi.properties
+fi
+
+if [[ "$1" == "--kraft" ]]; then
+  KAFKA_CLUSTER_ID="$(/opt/kafka/bin/kafka-storage.sh random-uuid)"
+  /opt/kafka/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c 
/tmp/strimzi.properties
+fi
+
+# add Strimzi kafka-oauth-* jars and their dependencies to classpath
+export CLASSPATH="/opt/kafka/libs/strimzi/*:$CLASSPATH"
+
+exec /opt/kafka/bin/kafka-server-start.sh /tmp/strimzi.properties
\ No newline at end of file
diff --git a/kafka-oauth/docker/keycloak/certificates/ca-truststore.p12 
b/kafka-oauth/docker/keycloak/certificates/ca-truststore.p12
new file mode 100644
index 0000000..5899e5c
Binary files /dev/null and 
b/kafka-oauth/docker/keycloak/certificates/ca-truststore.p12 differ
diff --git a/kafka-oauth/docker/keycloak/certificates/gen-keycloak-certs.sh 
b/kafka-oauth/docker/keycloak/certificates/gen-keycloak-certs.sh
new file mode 100755
index 0000000..d3b9a7a
--- /dev/null
+++ b/kafka-oauth/docker/keycloak/certificates/gen-keycloak-certs.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -e
+
+PASSWORD=changeit
+
+echo "#### Create server certificate for Keycloak"
+keytool -keystore keycloak.server.keystore.p12 -storetype pkcs12 -keyalg RSA 
-alias keycloak -validity 3650 -genkey -storepass $PASSWORD -keypass $PASSWORD 
-dname CN=keycloak -ext SAN=DNS:keycloak
+
+#echo "#### Create CA"
+#openssl req -new -x509 -keyout ca.key -out ca.crt -days 3650 -subj 
"/CN=strimzi.io" -passout pass:$PASSWORD
+
+#echo "#### Create client truststore"
+#keytool -keystore keycloak.client.truststore.p12 -storetype pkcs12 -alias 
KeycloakCARoot -storepass $PASSWORD -keypass $PASSWORD -import -file 
../../certificates/ca.crt -noprompt
+
+echo "#### Sign server certificate (export, sign, add signed to keystore)"
+keytool -keystore keycloak.server.keystore.p12 -storetype pkcs12 -alias 
keycloak -storepass $PASSWORD -keypass $PASSWORD -certreq -file cert-file
+openssl x509 -req -CA ../../certificates/ca.crt -CAkey 
../../certificates/ca.key -in cert-file -out cert-signed -days 3650 
-CAcreateserial -passin pass:$PASSWORD
+keytool -keystore keycloak.server.keystore.p12 -alias CARoot -storepass 
$PASSWORD -keypass $PASSWORD -import -file ../../certificates/ca.crt -noprompt
+keytool -keystore keycloak.server.keystore.p12 -alias keycloak -storepass 
$PASSWORD -keypass $PASSWORD -import -file cert-signed -noprompt
diff --git 
a/kafka-oauth/docker/keycloak/certificates/keycloak.server.keystore.p12 
b/kafka-oauth/docker/keycloak/certificates/keycloak.server.keystore.p12
new file mode 100644
index 0000000..a7ad28c
Binary files /dev/null and 
b/kafka-oauth/docker/keycloak/certificates/keycloak.server.keystore.p12 differ
diff --git a/kafka-oauth/docker/keycloak/compose.yml 
b/kafka-oauth/docker/keycloak/compose.yml
new file mode 100644
index 0000000..ca52e24
--- /dev/null
+++ b/kafka-oauth/docker/keycloak/compose.yml
@@ -0,0 +1,19 @@
+services:
+
+  keycloak:
+    image: quay.io/keycloak/keycloak:26.0
+    ports:
+      - "8080:8080"
+      - "8443:8443"
+    volumes:
+      - ./realms:/opt/keycloak/data/import
+      - 
./certificates/keycloak.server.keystore.p12:/opt/keycloak/data/certs/keycloak.server.keystore.p12
+
+    command: "-v start --import-realm 
--features=token-exchange,authorization,scripts 
--https-key-store-file=/opt/keycloak/data/certs/keycloak.server.keystore.p12 
--https-key-store-password=changeit"
+
+    environment:
+      - KC_BOOTSTRAP_ADMIN_USERNAME=admin
+      - KC_BOOTSTRAP_ADMIN_PASSWORD=admin
+      - KC_HOSTNAME=keycloak
+      - KC_HOSTNAME_ADMIN_URL=https://keycloak:8443
+      - KC_HTTP_ENABLED=true
\ No newline at end of file
diff --git a/kafka-oauth/docker/keycloak/realms/demo-realm.json 
b/kafka-oauth/docker/keycloak/realms/demo-realm.json
new file mode 100644
index 0000000..60b43f7
--- /dev/null
+++ b/kafka-oauth/docker/keycloak/realms/demo-realm.json
@@ -0,0 +1,219 @@
+{
+  "realm": "demo",
+  "accessTokenLifespan": 300,
+  "ssoSessionMaxLifespan": 32140800,
+  "ssoSessionIdleTimeout": 32140800,
+  "enabled": true,
+  "sslRequired": "external",
+  "users": [
+    {
+      "username": "alice",
+      "enabled": true,
+      "email": "[email protected]",
+      "credentials": [
+        {
+          "type": "password",
+          "value": "alice-password"
+        }
+      ],
+      "realmRoles": [
+        "user"
+      ],
+      "clientRoles": {
+        "kafka": [
+          "kafka-topic:superapp_*:owner"
+        ]
+      }
+    },
+    {
+      "username": "admin",
+      "enabled": true,
+      "email": "[email protected]",
+      "credentials": [
+        {
+          "type": "password",
+          "value": "admin-password"
+        }
+      ],
+      "realmRoles": [
+        "admin"
+      ],
+      "clientRoles": {
+        "realm-management": [
+          "realm-admin"
+        ],
+        "kafka": [
+          "kafka-admin"
+        ]
+      }
+    },
+    {
+      "username": "service-account-kafka-broker",
+      "enabled": true,
+      "email": "[email protected]",
+      "serviceAccountClientId": "kafka-broker",
+      "clientRoles": {
+        "kafka" : ["kafka-admin"]
+      }
+    },
+    {
+      "username": "service-account-kafka-producer-client",
+      "enabled": true,
+      "email": "[email protected]",
+      "serviceAccountClientId": "kafka-producer-client"
+    },
+    {
+      "username": "service-account-kafka-consumer-client",
+      "enabled": true,
+      "email": "[email protected]",
+      "serviceAccountClientId": "kafka-consumer-client",
+      "clientRoles": {
+        "kafka" : ["kafka-topic:superapp_*:consumer"]
+      }
+    }
+  ],
+  "roles": {
+    "realm": [
+      {
+        "name": "user",
+        "description": "User privileges"
+      },
+      {
+        "name": "admin",
+        "description": "Administrator privileges"
+      }
+    ],
+    "client": {
+      "kafka": [
+        {
+          "name": "kafka-admin",
+          "description": "Kafka administrator - can perform any action on any 
Kafka resource",
+          "clientRole": true
+        },
+        {
+          "name": "kafka-topic:superapp_*:owner",
+          "description": "Owner of topics that begin with 'superapp_' prefix. 
Can perform any operation on these topics.",
+          "clientRole": true
+        },
+        {
+          "name": "kafka-topic:superapp_*:consumer",
+          "description": "Consumer of topics that begin with 'superapp_' 
prefix. Can perform READ, and DESCRIBE on these topics.",
+          "clientRole": true
+        }
+      ]
+    }
+  },
+  "scopeMappings": [
+    {
+      "client": "kafka-broker",
+      "roles": [
+        "offline_access"
+      ]
+    },
+    {
+      "client": "kafka-producer-client",
+      "roles": [
+        "offline_access"
+      ]
+    },
+    {
+      "client": "kafka-consumer-client",
+      "roles": [
+        "offline_access"
+      ]
+    },
+    {
+      "clientScope": "offline_access",
+      "roles": [
+        "offline_access"
+      ]
+    }
+  ],
+  "clientScopeMappings": {
+    "kafka": [
+      {
+        "client": "kafka-broker",
+        "roles": [
+          "kafka-admin"
+        ]
+      },
+      {
+        "client": "kafka-consumer-client",
+        "roles": [
+          "kafka-topic:superapp_*:consumer"
+        ]
+      },
+      {
+        "client": "kafka-producer-client",
+        "roles": [
+          "kafka-topic:superapp_*:owner"
+        ]
+      }
+    ]
+  },
+  "clients": [
+    {
+      "clientId": "kafka",
+      "enabled": true,
+      "publicClient": true,
+      "bearerOnly": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "consentRequired" : false,
+      "fullScopeAllowed" : false
+    },
+    {
+      "clientId": "kafka-broker",
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "kafka-broker-secret",
+      "publicClient": false,
+      "bearerOnly": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": true,
+      "consentRequired" : false,
+      "fullScopeAllowed" : false,
+      "attributes": {
+        "access.token.lifespan": "32140800"
+      }
+    },
+    {
+      "clientId": "kafka-producer-client",
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "kafka-producer-client-secret",
+      "publicClient": false,
+      "bearerOnly": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": true,
+      "consentRequired" : false,
+      "fullScopeAllowed" : false,
+      "attributes": {
+        "access.token.lifespan": "36000"
+      }
+    },
+    {
+      "clientId": "kafka-consumer-client",
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "kafka-consumer-client-secret",
+      "publicClient": false,
+      "bearerOnly": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": true,
+      "consentRequired" : false,
+      "fullScopeAllowed" : false,
+      "attributes": {
+        "access.token.lifespan": "32140800"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/kafka-oauth/pom.xml b/kafka-oauth/pom.xml
new file mode 100644
index 0000000..8857084
--- /dev/null
+++ b/kafka-oauth/pom.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.camel.springboot.example</groupId>
+        <artifactId>examples</artifactId>
+        <version>4.10.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>camel-example-spring-boot-kafka-oauth</artifactId>
+    <name>Camel SB Examples :: Kafka :: OAuth</name>
+    <description>An example of Kafka authentication using OAuth.</description>
+
+    <properties>
+        <category>Messaging</category>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <strimzi.version>0.15.0</strimzi.version>
+        <nimbus.jose.version>9.37.2</nimbus.jose.version>
+    </properties>
+
+    <dependencyManagement>
+         <dependencies>
+            <!-- Camel BOM -->
+            <dependency>
+                <groupId>org.apache.camel.springboot</groupId>
+                <artifactId>camel-spring-boot-bom</artifactId>
+                <version>${project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- Spring Boot BOM -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot-version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+
+        <!-- Spring Boot -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator</artifactId>
+        </dependency>
+
+        <!-- Camel -->
+        <dependency>
+            <groupId>org.apache.camel.springboot</groupId>
+            <artifactId>camel-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.springboot</groupId>
+            <artifactId>camel-kafka-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.springboot</groupId>
+            <artifactId>camel-timer-starter</artifactId>
+        </dependency>
+
+        <!-- Strimzi OAuth -->
+        <dependency>
+            <groupId>io.strimzi</groupId>
+            <artifactId>kafka-oauth-common</artifactId>
+            <version>${strimzi.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.strimzi</groupId>
+            <artifactId>kafka-oauth-client</artifactId>
+            <version>${strimzi.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-clean-plugin</artifactId>
+                <configuration>
+                <filesets>
+                    <fileset>
+                        
<directory>${basedir}/docker/kafka-oauth-strimzi/kafka/target</directory>
+                    </fileset>
+                </filesets>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot-version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <!-- copy the truststore CA to the camel-spring-boot, 
so the strimzi-oauth jaas login module 
+                             connects to the keycloak https and trust the CA
+                        -->
+                        <id>copy-resources</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>target/classes</outputDirectory>
+                            <resources>
+                                <resource>
+                                    
<directory>${basedir}/docker/certificates/</directory>
+                                    <includes>
+                                        <include>ca-truststore.p12</include>
+                                    </includes>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <!-- copy the required files to build the 
strimzi-oauth-kafka image -->
+                        <id>copy-resources-strimzi</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <configuration>
+                            
<outputDirectory>${basedir}/docker/kafka-oauth-strimzi/kafka/target</outputDirectory>
+                            <resources>
+                                <resource>
+                                    
<directory>${basedir}/docker/kafka-oauth-strimzi/kafka</directory>
+                                    <includes>
+                                        <include>functions.sh</include>
+                                        <include>start.sh</include>
+                                        
<include>simple_kafka_config.sh</include>
+                                        <include>Dockerfile</include>
+                                        <include>config/</include>
+                                        <include>certificates/</include>
+                                    </includes>
+                                    <filtering>false</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <artifactItems>
+                        <artifactItem>
+                            <groupId>io.strimzi</groupId>
+                            <artifactId>kafka-oauth-client</artifactId>
+                            <version>${strimzi.version}</version>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>io.strimzi</groupId>
+                            <artifactId>kafka-oauth-server</artifactId>
+                            <version>${strimzi.version}</version>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>io.strimzi</groupId>
+                            <artifactId>kafka-oauth-server-plain</artifactId>
+                            <version>${strimzi.version}</version>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>io.strimzi</groupId>
+                            <artifactId>kafka-oauth-common</artifactId>
+                            <version>${strimzi.version}</version>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>io.strimzi</groupId>
+                            
<artifactId>kafka-oauth-keycloak-authorizer</artifactId>
+                            <version>${strimzi.version}</version>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>com.nimbusds</groupId>
+                            <artifactId>nimbus-jose-jwt</artifactId>
+                            <version>${nimbus.jose.version}</version>
+                        </artifactItem>
+                    </artifactItems>
+                    
<outputDirectory>${basedir}/docker/kafka-oauth-strimzi/kafka/target/libs</outputDirectory>
+                    <overWriteReleases>false</overWriteReleases>
+                    <overWriteSnapshots>true</overWriteSnapshots>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Application.java 
b/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Application.java
new file mode 100644
index 0000000..a53b159
--- /dev/null
+++ b/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Application.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.example.kafka;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+//CHECKSTYLE:OFF
+/**
+ * A sample Spring Boot application that starts the Camel routes.
+ */
+@SpringBootApplication
+public class Application {
+                                                                               
                                                                                
                           
+    // must have a main method spring-boot can run
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+}
+//CHECKSTYLE:ON
diff --git 
a/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Route.java 
b/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Route.java
new file mode 100644
index 0000000..6b8f18c
--- /dev/null
+++ b/kafka-oauth/src/main/java/org/apache/camel/example/kafka/Route.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.example.kafka;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Route extends RouteBuilder {
+
+    @Override
+    public void configure() throws Exception {
+
+        from("timer://foo?period=5s&repeatCount=5&includeMetadata=true")
+            .setBody(simple("Hi from Camel - ${header[CamelTimerFiredTime]}"))
+            .to("kafka:{{topic}}")
+            .log(">> Message sent: ${body}");
+
+        from("kafka:{{topic}}")
+            .log("<< Message received: ${body}");
+    }
+}
diff --git a/kafka-oauth/src/main/resources/application.properties 
b/kafka-oauth/src/main/resources/application.properties
new file mode 100644
index 0000000..aabb7b1
--- /dev/null
+++ b/kafka-oauth/src/main/resources/application.properties
@@ -0,0 +1,37 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+camel.component.kafka.brokers=kafka:9092
+topic=a_Topic1
+
+server.port=10080
+
+camel.component.kafka.security-protocol = SASL_PLAINTEXT
+camel.component.kafka.sasl-mechanism = OAUTHBEARER
+camel.component.kafka.sasl-jaas-config = 
org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
+  oauth.client.id="kafka-producer-client" \
+  oauth.client.secret="kafka-producer-client-secret" \
+  oauth.username.claim="preferred_username" \
+  oauth.ssl.truststore.location="docker/certificates/ca-truststore.p12" \
+  oauth.ssl.truststore.type="pkcs12" \
+  oauth.ssl.truststore.password="changeit" \
+  
oauth.token.endpoint.uri="https://keycloak:8443/realms/demo/protocol/openid-connect/token";
 ;
+camel.component.kafka.additional-properties[sasl.login.callback.handler.class]=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
+
+# logging.level.org.apache.camel.component.kafka=DEBUG
+# logging.level.org.apache.kafka=DEBUG
+# logging.level.io.strimzi=DEBUG
diff --git a/pom.xml b/pom.xml
index 227a8c8..c842d5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
                <module>jira</module>
                <module>jolokia</module>
                <module>kafka-avro</module>
+               <module>kafka-oauth</module>
                <module>kafka-offsetrepository</module>
                <module>kamelet-chucknorris</module>
                <module>load-balancer-eip</module>

Reply via email to