I encountered a challenge with a Kafka management tool — it supports SSO, and I was able to get an OAUTH connection set up to control what users could see when logging in through the UI, but the API component didn’t extract information from the bearer token and there was nothing in the rbac mapping to allow the bearer-token client ID to access anything.
Updates to allow the /api components to be authenticated by simple bearer tokens and a client ID mapped into a role are at https://github.com/ljr55555/kafka-ui
AuthorizationController.java was updated to properly support non-browser, machine-principal auth.
Added/fixed:
- avoids null failure when
authentication.getName()is missing - resolves principal name from alternate attributes such as:
client_idsubusername
- updated displayed permissions logic so
/api/authorizationuses the same effective RBAC matching idea as the backend
Result
/api/authorization now works for bearer-token API callers and shows:
- username = client ID
- populated permissions list
AccessControlService.java
Added support for API bearer-token principals
Previously, getUser() only worked when the authenticated principal was a RbacUser, which covered the browser/user flow.
Now it can also derive an AuthenticatedUser from opaque-token authenticated principals by extracting:
- principal name
- group-like values from attributes/authorities if present
Updated role matching logic
Previously, role matching was only role name matches one of user.groups(). Now it also supports role name matches user.principal(). That enables RBAC binding directly to the API client ID.
Result
RBAC now works for:
- normal browser users via groups
- API bearer-token callers via client principal name
DynamicConfigMapper.java
Fixed a mapper bug.
Before
The method mapping resource server config created a populated OAuth2ResourceServerProperties result object but always returned null.
After
It now returns result.
Result
Dynamic/config mapping for resource-server settings no longer silently discards the mapped object.
Build/package note
To preserve the browser UI, the jar needs to be built with frontend included — which you know if you read the doc … or you take my route, start it all up, test the API successfully, and then get baffled that the user UI throws
bash./gradlew clean assemble -Pinclude-frontend=true
application.yml
server:
port: 8443
ssl:
enabled: true
key-store: file:/etc/kafkaui/certs/kafbat.rushworth.us.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-alias: kafbat
auth:
type: OAUTH2
oauth2:
client:
pingfed:
client-id: ${OAUTH_CLIENT_ID}
client-secret: ${OAUTH_CLIENT_SECRET}
scope:
- openid
- profile
- email
client-name: oauthclient
provider: oauthclient
redirect-uri: https://kafbat.rushworth.us:8443/login/oauth2/code/oauthclient
authorization-grant-type: authorization_code
issuer-uri: https://login.example.com
jwk-set-uri: https://login.example.com/pf/JWKS
authorization-uri: https://login.example.com/as/authorization.oauth2
token-uri: https://login.example.com/as/token.oauth2
user-info-uri: https://login.example.com/idp/userinfo.openid
user-name-attribute: username
custom-params:
type: oauth
roles-field: memberOf
resource-server:
opaque-token:
client-id: ${OAUTH_CLIENT_ID}
client-secret: ${OAUTH_CLIENT_SECRET}
introspection-uri: https://login.example.com/as/introspect.oauth2
kafka:
clusters:
- name: test
bootstrapServers: ${KAFKA_BOOTSTRAP_SERVERS}
roles.yml
rbac:
roles:
- name: "admins"
clusters:
- test
subjects:
- provider: oauth
type: role
value: "CN=KafbatAdmins,OU=SecurityGroups,DC=example,DC=com"
permissions:
- resource: applicationconfig
actions: all
- resource: clusterconfig
actions: all
- resource: topic
value: ".*"
actions: all
- resource: consumer
value: ".*"
actions: all
- resource: schema
value: ".*"
actions: all
- resource: connect
value: ".*"
actions: all
- resource: ksql
actions: all
- resource: acl
actions: [ view ]
- name: "${OAUTH_CLIENT_ID}"
clusters:
- test
subjects:
- provider: oauth
type: user
value: "${OAUTH_CLIENT_ID}"
permissions:
- resource: applicationconfig
actions: all
- resource: clusterconfig
actions: all
- resource: topic
value: ".*"
actions: all
- resource: consumer
value: ".*"
actions: all
- resource: schema
value: ".*"
- resource: connect
value: ".*"
actions: all
- resource: ksql
actions: all
- resource: acl
actions: [ view ]
docker-compose.yml
services:
redpanda:
image: redpandadata/redpanda:v25.1.2
container_name: redpanda
command:
- redpanda
- start
- --overprovisioned
- --smp=1
- --memory=1G
- --reserve-memory=0M
- --node-id=0
- --check=false
- --kafka-addr=PLAINTEXT://0.0.0.0:9092
- --advertise-kafka-addr=PLAINTEXT://redpanda:9092
ports:
- "9092:9092"
kafbat-ui:
image: ghcr.io/kafbat/kafka-ui:latest
container_name: kafbat-ui
restart: unless-stopped
depends_on:
- redpanda
ports:
- "8443:8443"
volumes:
- ./config/application.yml:/etc/kafkaui/application.yml:ro
- ./config/roles.yml:/etc/kafkaui/roles.yml:ro
- ./certs:/etc/kafkaui/certs:ro
environment:
SPRING_CONFIG_LOCATION: file:/etc/kafkaui/application.yml
SPRING_CONFIG_ADDITIONAL_LOCATION: file:/etc/kafkaui/roles.yml
KEYSTORE_PASSWORD: REDACTED
OAUTH_CLIENT_ID: REDACTED
OAUTH_CLIENT_SECRET: REDACTED
KAFKA_BOOTSTRAP_SERVERS: redpanda:9092



































