Skip to content
Merged
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
147 changes: 147 additions & 0 deletions _posts/2026-09-04-release-0_24_0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
layout: post
title: "Kroxylicious release 0.24.0"
date: 2026-09-04 15:00:00 +1200
author: "Sam Barker"
author_url: "https://github.com/sambarker"
# noinspection YAMLSchemaValidation
categories: blog kroxylicious-proxy releases
tags: [ "releases", "kroxylicious-proxy" ]
---

# Kroxylicious 0.24.0: SASL Termination & API Evolution for 1.0

Kroxylicious 0.24.0 has snapped 🐊 into existence!

0.24.0 delivers **SASL Termination** and a significant evolution of the Filter/Router APIs.

The API change is a breaking one: if you've written your own filter or router, you'll need to make small/easy code changes before adopting this release — we've shipped tooling to help. As maintainers, we are genuinely disappointed to have to introduce breaking changes; we never take them lightly. You can read more about why we needed to do this below.

> **Release Highlights at a Glance:**
> * **New Feature:** SASL Termination (OAUTHBEARER, SCRAM-SHA-256/512) directly at the proxy layer.
> * **Breaking API Change:** Relocated core API types from `org.apache.kafka.*` to `io.kroxylicious.kafka.*`.
> * **Automated Upgrade:** OpenRewrite recipes available to migrate your codebase automatically.
> * **Community Feedback:** Active discussion open on upcoming design proposals.

---

### Introducing SASL Termination

Kroxylicious 0.24.0 adds built-in **SASL Termination**, allowing the proxy to authenticate incoming client connections directly rather than forwarding authentication handshakes to upstream Kafka brokers.

**Why SASL Termination?**

Filters that rely on client identity (such as custom authorization or dynamic routing) need to know who is making a request. While SASL Inspection handles direct downstream-to-upstream connections easily enough, it finds itself out of depth across multiple upstreams. PLAIN and OAUTHBEARER require identical credentials across all upstreams, while SCRAM fails entirely because each upstream expects its own unique handshake token.

SASL Termination resolves this by authenticating requests downstream at the proxy. This opens up support for mixed authentication: with this release, you can accept SCRAM from a downstream client while using mTLS upstream (where the proxy authenticates using its own certificate). While dynamically selecting an identity and how to present it upstream isn't supported yet, termination provides the baseline needed to build that flexibility.

**What it does in 0.24.0:**

* **Authenticates clients directly** — terminates SASL at the proxy for OAUTHBEARER, SCRAM-SHA-256, and SCRAM-SHA-512. The broker never sees the client's authentication exchange.
* **Exposes a verified principal to downstream filters** — after authentication, the identity is available to any filter further down the chain, such as the new [Authorization filter](https://kroxylicious.io/documentation/0.24.0/html/authorization-guide).
* **Works without a broker connection** — authentication completes before any upstream connection is made, which is a prerequisite for routing decisions that depend on who the client is.
* **Credential isolation** — the proxy holds only PBKDF2-derived keys, not plaintext passwords. Client credentials never reach the broker.
* **KIP-368 reauthentication** — enforces session lifetimes and handles periodic reauthentication transparently.

**Getting started**

For SCRAM mechanisms, credentials are managed using the bundled `scram-credential-tool`, which ships in the Kroxylicious distribution at `bin/scram-credential-tool.sh`. The tool creates and manages a proxy SCRAM credential file—a PKCS#12 file containing only derived keys, never plaintext passwords. Use it to create the credential file and provision users before starting the proxy.

The filter is also hardened against timing side-channel attacks by default: a `fixedAuthDelay` (200 ms by default) ensures authentication responses take consistent time regardless of whether a username exists or a password is correct. Phantom SCRAM challenges are generated for unknown usernames so that an attacker cannot distinguish a missing user from a failed authentication by counting protocol round-trips.

The filter works in both standalone and Kubernetes deployments. In Kubernetes, credentials are stored in a Secret and referenced using `${secret:...}` interpolation in the `KafkaProtocolFilter` resource—the operator mounts the secret entries automatically.

For full configuration details, see the [Authentication Guide](https://kroxylicious.io/documentation/0.24.0/html/authentication-guide). To explore the architecture and design behind this feature, see [Design Proposal 124](https://github.com/kroxylicious/design/blob/main/proposals/124-sasl-termination.md).

---

### The API Shift: Decoupling the Kroxylicious Public API from Kafka Internal APIs.

Until now, the Kroxylicious filter API has depended directly on classes from Apache Kafka's `kafka-clients` JAR — types like `*Data` message classes, protocol infrastructure, and record classes that appear directly in filter method signatures. In Kafka 4.3, the Kafka maintainers did something entirely reasonable: they moved classes they consider implementation details into internal packages to make that boundary explicit. That's good API hygiene on their part. The uncomfortable truth it forced us to confront is that Kroxylicious was depending on things Kafka never intended as public API — and those classes lived in Kafka's codebase, under Kafka's package naming, on Kafka's timeline. That's not a stable foundation for a 1.0.

So in 0.24.0 we took source ownership of every type that appears in the Kroxylicious public API. The classes previously imported from `org.apache.kafka.*` now live under `io.kroxylicious.kafka.*`, mechanically translated so the sub-package hierarchy is preserved. The original Apache Software Foundation copyright headers are retained, as is appropriate for code redistributed under the Apache 2.0 licence. We now own the source, which means we can evolve it where appropriate for Kroxylicious going forward.

The practical upshot for filter developers: `kafka-clients` is no longer a compile or runtime dependency of `kroxylicious-api`. If your filter depends on `kafka-clients` directly, that's still fine — it's your dependency to manage. But the proxy core no longer drags it in transitively. The separation is real at the artifact level, not just cosmetic.

**One break, then stable**

We made a deliberate choice to do this in a single, clean migration rather than absorb the changes piecemeal. Drip-feeding breaking changes across releases is worse than one honest "here's what moved, here's how to update." Our goal for 1.0 is a stable, predictable API that we control — this is the last time we expect to move these types.

**Wire compatibility**

Owning the source raises an obvious question: how do you know the classes you copied still produce byte-identical wire output to Kafka's originals? The answer is that we prove it on every build. We ship a suite of byte-level round-trip fidelity tests that serialise a message with our generated classes, deserialise with Kafka's, and assert equality — then run the same test in reverse. Every protocol message type, every supported protocol version. If something drifts, CI breaks before it ships.

The wire format is governed by KIPs and is genuinely stable. The Java classes representing it were not. We've separated those two concerns.

If you encounter any gaps or missing classes in your custom filters after upgrading, please [open an issue on GitHub](https://github.com/kroxylicious/kroxylicious/issues) — we moved what was required, but we may have missed something in the long tail.

---

### Automated Migration with OpenRewrite

To make updating your codebase as smooth as possible, we are providing automated migration recipes powered by **OpenRewrite**. While we make it sound like a simple `x -> y` transition, a bash one-liner isn't going to cut it (no matter how good your Perl is—yes, I'm looking at you, Claude).

**Maven**

```bash
mvn org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=io.kroxylicious:kroxylicious-migrations:0.24.0 \
-Drewrite.activeRecipes=io.kroxylicious.migrations.rewrite.v0_24.MigrateTo0_24
```

**Gradle**

```kotlin
plugins {
id("org.openrewrite.rewrite") version "6.x.x"
}

dependencies {
rewrite("io.kroxylicious:kroxylicious-migrations:0.24.0")
}
```

```bash
./gradlew rewriteRun -Drewrite.activeRecipe=io.kroxylicious.migrations.rewrite.v0_24.MigrateTo0_24
```

Further details around running these migrations can be found on [GitHub](https://github.com/kroxylicious/kroxylicious/tree/main/kroxylicious-proxy-core/kroxylicious-migrations).

---

### Design Proposals & Shaping the Future

We explored the trade-offs and options for this migration in [Design Proposal 116](https://github.com/kroxylicious/design/blob/main/proposals/116-kafka-api-migration.md).

If you read Proposal 116, you will notice it originally suggested a simple `sed` script for migration. Design proposals are point-in-time snapshots of our thinking, not unchangeable commandments. As we got into implementation, OpenRewrite offered a far superior, AST-aware refactoring experience.

This shift highlights the importance of our proposal process. If API stability or new proxy features impact how you use Kroxylicious, we strongly encourage you to review and participate in active design proposals on the [Kroxylicious Design Repository](https://github.com/kroxylicious/design). Your feedback helps shape the project as we head toward 1.0.

### Community Contributions

This release included commits from:
- [AdityaThakur1998](https://github.com/AdityaThakur1998)
- [DragonFSKY](https://github.com/DragonFSKY)
- [jjj-n](https://github.com/jjj-n)
- [k-wall](https://github.com/k-wall)
- [lntutor](https://github.com/lntutor)
- [lukman48](https://github.com/lukman48)
- [matheusandre1](https://github.com/matheusandre1)
- [oozan](https://github.com/oozan)
- [piotrpdev](https://github.com/piotrpdev)
- [RafaelReia](https://github.com/RafaelReia)
- [robobario](https://github.com/robobario)
- [SamBarker](https://github.com/SamBarker)
- [tamikikoz](https://github.com/tamikikoz)
- [TheDarkniteFalls](https://github.com/TheDarkniteFalls)
- [tombentley](https://github.com/tombentley)

Thank you all, your hard work is massively appreciated by the PMC!

### Artefacts

Download binary distributions and container images from the [download](https://kroxylicious.io/download/0.24.0/) page.

### Feedback

Drop by and say hello on [Slack](https://kroxylicious.slack.com), [GitHub](https://github.com/kroxylicious/kroxylicious/issues), or [bsky](https://bsky.app/profile/kroxylicious.io). You can also join us in person at a [community call]({% link join-us/community-call/index.md %}).
Loading