Companion fixture for the #[ignore] E2E tests in
crates/kafka-backup-core/tests/integration_suite/sasl_gssapi_tests.rs.
A three-container stack that boots a self-contained MIT Kerberos realm
(TEST.LOCAL) plus an Apache Kafka 7.7.0 broker configured for
SASL_PLAINTEXT + GSSAPI. The KDC container creates the service and
client principals on first start, exports keytabs into a shared volume,
and the Kafka broker mounts that volume read-only to authenticate
clients against the realm.
Exercises the full GssapiPlugin dispatch path end-to-end:
- Phase 1 multi-round
gss_init_sec_contextagainst a real broker. - Phase 1→2 turnaround (client sends empty, broker replies with wrapped security-layer proposal).
- Phase 2 wrap/unwrap with layer = 0x01 (no security layer, no size).
- Post-auth
MetadataRPC on the authenticated session. - KIP-368 reauth trigger (broker advertises 60s window; client schedules ~48s).
macOS. Install MIT krb5 (Apple's bundled Heimdal does not expose the
symbols libgssapi 0.9 links against):
brew install krb5
export PKG_CONFIG_PATH="$(brew --prefix krb5)/lib/pkgconfig:${PKG_CONFIG_PATH:-}"Linux. Install krb5 development headers:
# Debian/Ubuntu
sudo apt-get install libkrb5-dev
# Fedora/RHEL
sudo dnf install krb5-develHost /etc/hosts. The GSSAPI service principal is
kafka/[email protected]. Clients MUST connect to that exact
hostname — localhost will return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN.
sudo sh -c 'grep -q kafka.test.local /etc/hosts \
|| echo "127.0.0.1 kafka.test.local kdc.test.local" >> /etc/hosts'Host port 88. The fixture publishes the KDC on the standard Kerberos
port (88/tcp + 88/udp) so the same krb5.conf works from inside the
compose network and from the host test runner. Check nothing else is
bound before up:
sudo lsof -iTCP:88 -iUDP:88 -sTCP:LISTEN 2>/dev/nullcd tests/sasl-gssapi-test-infra
docker compose -f docker-compose-gssapi.yml up -d --wait
# Confirm the keytabs were created and principals exist:
docker compose -f docker-compose-gssapi.yml logs kdc | grep "principals created"
# Tear down — `-v` drops Docker-managed volumes so a fresh up re-seeds
# the KDC database. The host-bind-mounted keytabs/ directory is NOT
# wiped by -v, so remove it manually if you want fresh keys:
docker compose -f docker-compose-gssapi.yml down -v
rm -f keytabs/*.keytabKeytabs land in tests/sasl-gssapi-test-infra/keytabs/ on the host once
the KDC container reaches the healthy state. If a previous run's keytab
is still present when a fresh KDC boots, you will hit Password incorrect / Credential cache is empty errors — the keytab's keys do
not match the freshly-minted KDC principal. Delete the old keytabs
before bringing the stack back up.
# macOS: keep PKG_CONFIG_PATH exported for the krb5 link step.
cargo test --features gssapi -p kafka-backup-core \
--test integration_suite_tests -- --ignored sasl_gssapi_From a second shell, with the compose stack up. offset-rollback snapshot hits the broker's OffsetFetch RPC, so a successful return
proves the full GSSAPI handshake completed against the live broker:
export PKG_CONFIG_PATH="$(brew --prefix krb5)/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
mkdir -p /tmp/gssapi-smoke-snap
cargo run --release --features gssapi -p kafka-backup-cli -- \
offset-rollback snapshot \
--path /tmp/gssapi-smoke-snap \
--bootstrap-servers kafka.test.local:9098 \
--security-protocol SASL_PLAINTEXT \
--sasl-mechanism GSSAPI \
--sasl-keytab tests/sasl-gssapi-test-infra/keytabs/client.keytab \
--sasl-krb5-config tests/sasl-gssapi-test-infra/krb5.conf \
--sasl-kerberos-service-name kafka \
--groups smoke-test-groupTurn on RUST_LOG=kafka_backup_core=debug to see the
GSSAPI Phase 2 complete server_layers=0x01 trace that confirms
the wrap/unwrap round-trip.
run-cli-smoke.sh drives the fully-built kafka-backup binary through
a backup → restore cycle against this fixture using the YAML configs
at config/gssapi-backup.yaml and config/gssapi-restore.yaml. This
catches regressions that cargo test alone cannot — arg parsing, YAML
deserialisation, and the CLI's populate_sasl_plugin handoff to the
runtime plugin.
# From the repo root, with the compose stack up:
bash tests/sasl-gssapi-test-infra/run-cli-smoke.shThe script:
- Verifies
/etc/hostsand fixture readiness. - Builds
cargo build --release --features gssapi -p kafka-backup-cli. - Creates
smoke-topicon the broker's internal PLAINTEXT listener and produces 50 key/value records. - Runs
kafka-backup backup --config config/gssapi-backup.yaml(hits the broker over GSSAPI) and asserts a manifest was emitted. - Runs
kafka-backup restore --config config/gssapi-restore.yamlwhich remapssmoke-topic→smoke-topic-restoredon the same cluster. - Consumes from
smoke-topic-restoredand asserts 50 records arrived. - Deletes both topics and removes
/tmp/gssapi-backup-smokeregardless of pass/fail.
KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN— host does not resolvekafka.test.localto127.0.0.1, or the client is connecting tolocalhostinstead of the FQDN. Verify/etc/hostsand thebootstrap_serversvalue.Credential cache is empty/Password incorrect— the client keytab on the host is stale.init-kdc.shauto-cleans the bind-mountedkeytabs/directory when it creates a fresh realm, so this should only happen if the init script did not run to completion (e.g., interruptedup). Fix:rm keytabs/*.keytabanddocker compose restart kdc.gss_acquire_credreturnsNo credentials cache found— the client keytab is missing or unreadable. Checktests/sasl-gssapi-test-infra/keytabs/client.keytabexists and is not empty; if not, rundocker compose logs kdcfor init errors.Authentication failed due to invalid credentials(broker log) — the client sent an AP-REQ that the broker can't decrypt, typically because the ticket was issued by an earlier KDC instance whose service key has since rotated.GssapiPluginisolates its credential cache viaKRB5CCNAME=MEMORY:<ptr>whenever a--sasl-keytabis passed, so stale tickets in the OS ccache cannot leak into the plugin. If you see this error it usually means you are running an older build; upgrade, or clear the system ccache (kdestroy -A) as a workaround.Cannot find key of appropriate type— the broker enctype list inkdc.conf(aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96) must be a subset of what the JDK supports. OpenJDK 11+ supports both; ancient JDKs without the unlimited-strength JCE policy will fail on AES-256.- Clock skew — Kerberos rejects tickets more than 5 minutes out of
sync.
docker composetimekeeping follows the host clock, so the usual culprit is a stopped host (laptop resume). Restart the stack:docker compose -f docker-compose-gssapi.yml restart. - Port 9098 conflict — something else bound the port. Either stop
that service or edit the
ports:mapping and setadvertised_listenersaccordingly.