
Apache Kafka 4.1.0 (KRaft) مع مصادقة Keycloak OAuth2 باستخدام Strimzi - يتجاوز قيد قائمة العناوين المسموح بها في CVE-2025-27817
Apache Kafka 4.1.0 جاهز للإنتاج (وضع KRaft) مع مصادقة Keycloak 26.1.1 OAuth2/OIDC باستخدام صورة Strimzi Kafka.
هذا تطوير للإثبات السابق للمفهوم مع تحسينات كبيرة:
قدم Apache Kafka 4.0.0+ قائمة عناوين URL (org.apache.kafka.sasl.oauthbearer.allowed.urls) كخاصية نظام JVM لإصلاح ثغرة SSRF/قراءة الملفات التعسفية. هذا يكسر استخدام OAuth القياسي في عملاء Apache Kafka الأصليين.
الحل: مكتبة Strimzi Kafka OAuth لا تنفذ هذا القيد، مما يتيح وظيفة OAuth مع Kafka 4.1.0.
# توليد شهادات SSL
cd kafka-security
./generate-certs.sh
cd ..
# بدء الخدمات
docker compose up -d
# التحقق من Keycloak
curl http://localhost:8080/health/ready
# إعداد realm وعملاء Keycloak
./scripts/setup-keycloak.sh
# اختبار منتج OAuth
source ~/.venv/bin/activate
uv pip install confluent-kafka
python tests/quick_test.py
keycloak:8080 (HTTP) ←→ kafka-broker:9093 (SASL_SSL/OAuth)
↔ kafka-broker:19092 (PLAINTEXT/inter-broker)
↔ kafka-broker:29093 (PLAINTEXT/KRaft controller)
kafka-security/ca-cert + ca-keykafka-security/broker/kafka.server.keystore.jks (يحتوي على شهادة الخادم + المفتاح الخاص)kafka-security/broker/kafka.server.truststore.jks (يحتوي على شهادة CA)changeit (جميع مخازن المفاتيح/الثقة)# شهادة الوسطاء
CN=kafka-broker
SAN=DNS:kafka-broker,DNS:localhost,IP:127.0.0.1
# الصلاحية: 3650 يومًا
# خوارزمية المفتاح: RSA 2048-bit
# خوارزمية التوقيع: SHA256withRSA
kafka-broker (سري)
kafka-brokersetup-keycloak.shkafka-broker إلى مطالبة aud في JWTpreferred_username في الرمزkafka-producer (سري)
kafka-producerclient_credentialskafka-consumer (سري)
kafka-consumerclient_credentialsPOST http://localhost:8080/realms/kafka-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=kafka-producer
&client_secret=<secret>
&scope=profile email
{
"aud": ["kafka-broker", "account"],
"iss": "http://localhost:8080/realms/kafka-realm",
"azp": "kafka-producer",
"preferred_username": "service-account-kafka-producer",
"scope": "profile email"
}
# هوية العقدة
node.id=1
process.roles=broker,controller
controller.quorum.voters=1@kafka-broker:29093
# المستمعون
listeners=SASL_SSL://0.0.0.0:9093,PLAINTEXT://0.0.0.0:19092,CONTROLLER://0.0.0.0:29093
advertised.listeners=SASL_SSL://localhost:9093,PLAINTEXT://kafka-broker:19092
listener.security.protocol.map=SASL_SSL:SASL_SSL,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
# آلية SASL
sasl.enabled.mechanisms=OAUTHBEARER
# معالجات Strimzi OAuth (لكل مستمع لـ SASL_SSL)
listener.name.sasl_ssl.oauthbearer.sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
listener.name.sasl_ssl.oauthbearer.sasl.server.callback.handler.class=io.strimzi.kafka.oauth.server.JaasServerOauthValidatorCallbackHandler
# تكوين OAuth عبر JAAS
listener.name.sasl_ssl.oauthbearer.sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
oauth.client.id="kafka-broker" \
oauth.client.secret="<secret>" \
oauth.token.endpoint.uri="http://keycloak:8080/realms/kafka-realm/protocol/openid-connect/token" \
oauth.valid.issuer.uri="http://localhost:8080/realms/kafka-realm" \
oauth.jwks.endpoint.uri="http://keycloak:8080/realms/kafka-realm/protocol/openid-connect/certs" \
oauth.username.claim="preferred_username";
oauth.client.id: معرف العميل للحصول على الرمزoauth.client.secret: سر العميل للحصول على الرمزoauth.token.endpoint.uri: نقطة نهاية رمز Keycloak (يستخدم الوسطاء اسم المضيف الداخلي keycloak:8080)oauth.valid.issuer.uri: مُصدر JWT المتوقع (يجب أن يطابق مطالبة iss في الرمز، يستخدم localhost:8080 الخارجي)oauth.jwks.endpoint.uri: نقطة نهاية JWKS للتحقق من توقيع JWToauth.username.claim: مطالبة JWT لاستخراج principalauthorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer
super.users=User:kafka-broker;User:ANONYMOUS
allow.everyone.if.no.acl.found=true
ملاحظة: حاليًا متساهل للاختبار. يجب استخدام ACLs في الإنتاج.
from confluent_kafka import Producer
conf = {
'bootstrap.servers': 'localhost:9093',
'security.protocol': 'SASL_SSL',
'sasl.mechanisms': 'OAUTHBEARER',
'sasl.oauthbearer.method': 'oidc',
'sasl.oauthbearer.client.id': 'kafka-producer',
'sasl.oauthbearer.client.secret': '<secret>',
'sasl.oauthbearer.token.endpoint.url': 'http://localhost:8080/realms/kafka-realm/protocol/openid-connect/token',
'ssl.ca.location': 'kafka-security/ca-cert',
'ssl.endpoint.identification.algorithm': 'none',
}
producer = Producer(conf)
producer.produce('topic', b'message')
producer.flush()
from confluent_kafka import Consumer