Skip to content

Commit c8a16be

Browse files
committed
fix(native): register javax.security.auth.Subject for GraalVM reflection (#726)
SASL authentication fails in the native binary because the Kafka client's CompositeStrategy uses reflection to find Subject.current() and Subject.callAs(), which are not registered in the GraalVM native image metadata. Add reflection entries for javax.security.auth.Subject, PlainSaslClientFactory, and PlainSaslClientProvider to the Kafka provider's reachability-metadata.json. Also add E2E tests for SASL_PLAINTEXT authentication: a SASL-enabled Kafka broker via docker-compose, HOCON config, topic resources, and test scenarios integrated into the existing run-tests.sh suite. A standalone run-sasl-tests.sh is also provided for focused SASL testing.
1 parent cbff333 commit c8a16be

7 files changed

Lines changed: 205 additions & 4 deletions

File tree

e2e/docker-compose-e2e-sasl.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Copyright (c) The original authors
4+
#
5+
# Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Docker Compose for Jikkou e2e SASL tests.
8+
# Minimal setup: Kafka (KRaft) with SASL_PLAINTEXT authentication.
9+
services:
10+
kafka-sasl:
11+
image: "confluentinc/cp-kafka:7.5.0"
12+
ports:
13+
- "9093:9093"
14+
container_name: e2e-kafka-sasl
15+
environment:
16+
KAFKA_NODE_ID: 101
17+
CLUSTER_ID: 'xtzWWN4bTjitpL3kfd9s5g'
18+
KAFKA_CONTROLLER_QUORUM_VOTERS: '101@kafka-sasl:29094'
19+
KAFKA_PROCESS_ROLES: 'broker,controller'
20+
KAFKA_ADVERTISED_LISTENERS: 'SASL_PLAINTEXT://kafka-sasl:29093,SASL_PLAINTEXT_HOST://localhost:9093'
21+
KAFKA_LISTENERS: 'SASL_PLAINTEXT://kafka-sasl:29093,SASL_PLAINTEXT_HOST://0.0.0.0:9093,CONTROLLER://kafka-sasl:29094'
22+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_PLAINTEXT_HOST:SASL_PLAINTEXT'
23+
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
24+
KAFKA_INTER_BROKER_LISTENER_NAME: 'SASL_PLAINTEXT'
25+
KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL: 'PLAIN'
26+
KAFKA_SASL_ENABLED_MECHANISMS: 'PLAIN'
27+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
28+
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
29+
KAFKA_SUPER_USERS: 'User:admin'
30+
KAFKA_OPTS: >-
31+
-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf
32+
volumes:
33+
- ./resources/kafka_server_jaas.conf:/etc/kafka/kafka_server_jaas.conf:ro
34+
- ./resources/sasl_client.properties:/etc/kafka/sasl_client.properties:ro
35+
healthcheck:
36+
test: >-
37+
kafka-broker-api-versions
38+
--bootstrap-server localhost:9093
39+
--command-config /etc/kafka/sasl_client.properties
40+
|| exit 1
41+
interval: 10s
42+
timeout: 10s
43+
retries: 12
44+
networks:
45+
- e2e-sasl-network
46+
47+
networks:
48+
e2e-sasl-network:
49+
driver: bridge

e2e/resources/jikkou-e2e-sasl.conf

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
jikkou {
2+
# Apache Kafka Provider (SASL_PLAINTEXT)
3+
provider.kafka {
4+
enabled = true
5+
type = io.streamthoughts.jikkou.kafka.KafkaExtensionProvider
6+
default = true
7+
config = {
8+
client {
9+
bootstrap.servers = "localhost:9093"
10+
security.protocol = "SASL_PLAINTEXT"
11+
sasl.mechanism = "PLAIN"
12+
sasl.jaas.config = "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin-secret\";"
13+
}
14+
brokers {
15+
waitForEnabled = true
16+
waitForMinAvailable = 1
17+
waitForTimeoutMs = 60000
18+
}
19+
}
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
apiVersion: "kafka.jikkou.io/v1"
3+
kind: KafkaTopic
4+
metadata:
5+
name: 'e2e-sasl-topic-1'
6+
labels:
7+
environment: e2e-sasl
8+
spec:
9+
partitions: 3
10+
replicas: 1
11+
configs:
12+
min.insync.replicas: 1
13+
cleanup.policy: 'delete'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
KafkaServer {
2+
org.apache.kafka.common.security.plain.PlainLoginModule required
3+
username="admin"
4+
password="admin-secret"
5+
user_admin="admin-secret";
6+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
security.protocol=SASL_PLAINTEXT
2+
sasl.mechanism=PLAIN
3+
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin-secret";

e2e/run-tests.sh

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ set -euo pipefail
1919
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
2020
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
2121
COMPOSE_FILE="${SCRIPT_DIR}/docker-compose-e2e.yml"
22+
COMPOSE_SASL_FILE="${SCRIPT_DIR}/docker-compose-e2e-sasl.yml"
2223
E2E_RESOURCES="${SCRIPT_DIR}/resources"
2324
JIKKOU_HOCON_CONFIG="${E2E_RESOURCES}/jikkou-e2e.conf"
25+
JIKKOU_SASL_HOCON_CONFIG="${E2E_RESOURCES}/jikkou-e2e-sasl.conf"
2426
# The CLI uses a JSON context file (like kubeconfig), not the HOCON directly.
2527
# We generate this at runtime so it contains the correct absolute path.
26-
JIKKOU_CONFIG="" # set in setup_config()
28+
JIKKOU_CONFIG="" # set in setup_config()
29+
JIKKOU_SASL_CONFIG="" # set in setup_config()
2730

2831
# ── Flags ────────────────────────────────────────────────────────────────────
2932
SKIP_BUILD=false
@@ -78,6 +81,18 @@ setup_config() {
7881
}
7982
EOF
8083
log_ok "Generated CLI context config: ${JIKKOU_CONFIG}"
84+
85+
JIKKOU_SASL_CONFIG=$(mktemp /tmp/jikkou-e2e-sasl-context-XXXXXX.json)
86+
cat > "${JIKKOU_SASL_CONFIG}" <<EOF
87+
{
88+
"currentContext": "e2e-sasl",
89+
"e2e-sasl": {
90+
"configFile": "${JIKKOU_SASL_HOCON_CONFIG}",
91+
"configProps": {}
92+
}
93+
}
94+
EOF
95+
log_ok "Generated SASL CLI context config: ${JIKKOU_SASL_CONFIG}"
8196
}
8297

8398
# Run jikkou binary with the e2e config.
@@ -94,6 +109,14 @@ run_jikkou_capture() {
94109
set -e
95110
}
96111

112+
# Run jikkou binary with the SASL config.
113+
run_jikkou_sasl_capture() {
114+
set +e
115+
JIKKOU_OUTPUT=$(JIKKOUCONFIG="${JIKKOU_SASL_CONFIG}" "${JIKKOU_BIN}" "$@" 2>&1)
116+
JIKKOU_EXIT=$?
117+
set -e
118+
}
119+
97120
assert_exit_code() {
98121
local expected=$1
99122
if [[ "${JIKKOU_EXIT}" -ne "${expected}" ]]; then
@@ -170,6 +193,7 @@ build_native() {
170193
start_services() {
171194
log_info "Starting Docker services..."
172195
docker compose -f "${COMPOSE_FILE}" up -d
196+
docker compose -f "${COMPOSE_SASL_FILE}" up -d
173197

174198
log_info "Waiting for services to become healthy..."
175199
local max_wait=180
@@ -178,31 +202,36 @@ start_services() {
178202

179203
while [[ ${elapsed} -lt ${max_wait} ]]; do
180204
local kafka_ok=false
205+
local kafka_sasl_ok=false
181206
local sr_ok=false
182207
local connect_ok=false
183208

184209
if docker inspect --format='{{.State.Health.Status}}' e2e-kafka 2>/dev/null | grep -q healthy; then
185210
kafka_ok=true
186211
fi
212+
if docker inspect --format='{{.State.Health.Status}}' e2e-kafka-sasl 2>/dev/null | grep -q healthy; then
213+
kafka_sasl_ok=true
214+
fi
187215
if docker inspect --format='{{.State.Health.Status}}' e2e-schema-registry 2>/dev/null | grep -q healthy; then
188216
sr_ok=true
189217
fi
190218
if docker inspect --format='{{.State.Health.Status}}' e2e-connect 2>/dev/null | grep -q healthy; then
191219
connect_ok=true
192220
fi
193221

194-
if ${kafka_ok} && ${sr_ok} && ${connect_ok}; then
222+
if ${kafka_ok} && ${kafka_sasl_ok} && ${sr_ok} && ${connect_ok}; then
195223
log_ok "All services healthy"
196224
return 0
197225
fi
198226

199-
log_info "Waiting... (${elapsed}s/${max_wait}s) kafka=${kafka_ok} schema-registry=${sr_ok} connect=${connect_ok}"
227+
log_info "Waiting... (${elapsed}s/${max_wait}s) kafka=${kafka_ok} kafka-sasl=${kafka_sasl_ok} schema-registry=${sr_ok} connect=${connect_ok}"
200228
sleep ${interval}
201229
elapsed=$((elapsed + interval))
202230
done
203231

204232
log_error "Services did not become healthy within ${max_wait}s"
205233
docker compose -f "${COMPOSE_FILE}" logs
234+
docker compose -f "${COMPOSE_SASL_FILE}" logs
206235
exit 1
207236
}
208237

@@ -212,6 +241,7 @@ stop_services() {
212241
else
213242
log_info "Tearing down Docker services..."
214243
docker compose -f "${COMPOSE_FILE}" down -v
244+
docker compose -f "${COMPOSE_SASL_FILE}" down -v
215245
fi
216246
}
217247

@@ -583,6 +613,64 @@ EOF
583613
assert_output_not_contains "e2e-file-sink" || return 1
584614
}
585615

616+
# ── Kafka Topics over SASL (create, read, delete) ───────────────────────────
617+
618+
test_sasl_kafka_topics_create() {
619+
run_jikkou_sasl_capture apply --files "${E2E_RESOURCES}/kafka-topics-sasl.yaml"
620+
assert_exit_code 0 || return 1
621+
622+
run_jikkou_sasl_capture get kafkatopics --name 'e2e-sasl-topic-1'
623+
assert_exit_code 0 || return 1
624+
assert_output_contains "e2e-sasl-topic-1" || return 1
625+
}
626+
627+
test_sasl_kafka_topics_read() {
628+
run_jikkou_sasl_capture get kafkatopics
629+
assert_exit_code 0 || return 1
630+
assert_output_contains "e2e-sasl-topic-1" || return 1
631+
632+
run_jikkou_sasl_capture get kafkatopics --name 'e2e-sasl-topic-1'
633+
assert_exit_code 0 || return 1
634+
assert_output_contains "partitions.*3" || return 1
635+
assert_output_contains "cleanup.policy" || return 1
636+
}
637+
638+
test_sasl_kafka_topics_delete() {
639+
local tmpfile
640+
tmpfile=$(mktemp /tmp/e2e-sasl-delete-XXXXXX.yaml)
641+
trap "rm -f ${tmpfile}" RETURN
642+
643+
cat > "${tmpfile}" <<'EOF'
644+
---
645+
apiVersion: "kafka.jikkou.io/v1"
646+
kind: KafkaTopic
647+
metadata:
648+
name: 'e2e-sasl-topic-1'
649+
labels:
650+
environment: e2e-sasl
651+
annotations:
652+
jikkou.io/delete: true
653+
spec:
654+
partitions: 3
655+
replicas: 1
656+
configs:
657+
min.insync.replicas: 1
658+
cleanup.policy: 'delete'
659+
EOF
660+
661+
run_jikkou_sasl_capture apply --files "${tmpfile}"
662+
assert_exit_code 0 || return 1
663+
664+
run_jikkou_sasl_capture get kafkatopics --name 'e2e-sasl-topic-1'
665+
assert_output_not_contains "e2e-sasl-topic-1" || return 1
666+
}
667+
668+
test_sasl_health() {
669+
run_jikkou_sasl_capture health get kafka
670+
assert_exit_code 0 || return 1
671+
assert_output_contains "UP|HEALTHY" || return 1
672+
}
673+
586674
# ── Health ───────────────────────────────────────────────────────────────────
587675

588676
test_health_indicators() {
@@ -646,6 +734,12 @@ main() {
646734
run_test "Kafka Connect: update" test_kafka_connect_update
647735
run_test "Kafka Connect: delete" test_kafka_connect_delete
648736

737+
# ── Kafka Topics over SASL (issue #726) ──
738+
run_test "SASL Kafka Topics: create" test_sasl_kafka_topics_create
739+
run_test "SASL Kafka Topics: read" test_sasl_kafka_topics_read
740+
run_test "SASL Kafka Topics: delete" test_sasl_kafka_topics_delete
741+
run_test "SASL Health indicators" test_sasl_health
742+
649743
# ── Health ──
650744
run_test "Health indicators" test_health_indicators
651745

@@ -669,8 +763,9 @@ main() {
669763

670764
stop_services
671765

672-
# Clean up temp config file
766+
# Clean up temp config files
673767
rm -f "${JIKKOU_CONFIG}"
768+
rm -f "${JIKKOU_SASL_CONFIG}"
674769

675770
if [[ ${TESTS_FAILED} -gt 0 ]]; then
676771
exit 1

providers/jikkou-provider-kafka/src/main/resources/META-INF/native-image/io.streamthoughts/jikkou-extension-kafka/reachability-metadata.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,20 @@
389389
"type": "sun.security.krb5.KrbException",
390390
"allDeclaredMethods": true,
391391
"allDeclaredConstructors": true
392+
},
393+
{
394+
"type": "javax.security.auth.Subject",
395+
"allDeclaredMethods": true
396+
},
397+
{
398+
"type": "org.apache.kafka.common.security.plain.internals.PlainSaslClient$PlainSaslClientFactory",
399+
"allDeclaredMethods": true,
400+
"allDeclaredConstructors": true
401+
},
402+
{
403+
"type": "org.apache.kafka.common.security.plain.PlainSaslClientProvider",
404+
"allDeclaredMethods": true,
405+
"allDeclaredConstructors": true
392406
}
393407
],
394408
"resources": [

0 commit comments

Comments
 (0)