Configuration Reference¶
Table of Contents¶
- Overview
- Configuration Sources
- Route Configuration
- Match Modes
- Retry Configuration
- TLS/SSL Configuration
- Template Variables
- Environment Variable Examples
Overview¶
The extension is configured entirely through environment variables following the 12-factor app methodology. This makes it container-friendly and easy to manage across different environments.
Configuration Hierarchy¶
flowchart LR
subgraph KETE["kete"]
R["routes"]
subgraph ROUTE["<ROUTE_NAME>"]
EN["enabled"]
subgraph SER["serializer"]
SK["kind"]
end
subgraph DEST["destination"]
DK["kind"]
DP["...properties"]
end
subgraph RM["realm-matchers"]
RMN["<MATCHER_NAME>"]
end
RMM["realm-match-mode"]
subgraph EM["event-matchers"]
EMN["<MATCHER_NAME>"]
end
EMM["event-match-mode"]
subgraph RETRY["retry"]
RE["enabled"]
MA["max-attempts"]
WD["wait-duration"]
end
end
end
Property Path Example: kete.routes.my-route.destination.kind=kafka
Configuration Sources¶
KETE reads configuration from two sources, merged in this order:
- Keycloak SPI Configuration (XML/properties) - Base configuration
- Environment Variables - Override/extend base configuration
Environment variables take precedence over SPI configuration, allowing you to define defaults in XML and override specific values via environment.
Keycloak SPI Configuration (Bare Metal / Standalone)¶
For traditional Keycloak deployments, configure KETE via the Keycloak SPI mechanism.
Quarkus-based Keycloak (v17+)¶
Add to conf/keycloak.conf or conf/quarkus.properties:
# Enable the event listener
spi-events-listener-kete-enabled=true
# KETE configuration
spi-events-listener-kete-routes-kafka-example-destination-kind=kafka
spi-events-listener-kete-routes-kafka-example-destination-bootstrap-servers=kafka:9092
spi-events-listener-kete-routes-kafka-example-destination-topic=keycloak-events
Or pass as CLI arguments:
bin/kc.sh start \
--spi-events-listener-kete-routes-kafka-example-destination-kind=kafka \
--spi-events-listener-kete-routes-kafka-example-destination-bootstrap-servers=kafka:9092 \
--spi-events-listener-kete-routes-kafka-example-destination-topic=keycloak-events
Legacy Keycloak (WildFly-based, pre-v17)¶
Add to standalone.xml under <subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">:
<spi name="eventsListener">
<provider name="kete" enabled="true">
<properties>
<property name="routes.kafka-example.destination.kind" value="kafka"/>
<property name="routes.kafka-example.destination.bootstrap.servers" value="kafka:9092"/>
<property name="routes.kafka-example.destination.topic" value="keycloak-events"/>
<property name="routes.kafka-example.realm-matchers.realm" value="list:master"/>
</properties>
</provider>
</spi>
Environment Variables¶
All configuration is read from environment variables with the prefix: kete.
Docker Example¶
docker run -e kete.routes.kafka-example.destination.kind=kafka \
# ... more variables ...
keycloak:latest
Kubernetes Example¶
apiVersion: v1
kind: ConfigMap
metadata:
name: keycloak-config
data:
kete.routes.kafka-example.realm-matchers.realm: "list:master"
kete.routes.kafka-example.destination.kind: "kafka"
kete.routes.kafka-example.destination.bootstrap.servers: "kafka:9092"
kete.routes.kafka-example.destination.topic: "keycloak-events"
# ...
Docker Compose Example¶
services:
keycloak:
environment:
kete.routes.kafka-example.realm-matchers.realm: "list:master"
kete.routes.kafka-example.destination.kind: "kafka"
kete.routes.kafka-example.destination.bootstrap.servers: "kafka:9092"
kete.routes.kafka-example.destination.topic: "keycloak-events"
# ...
Standalone Keycloak¶
For standalone Keycloak, set environment variables before starting:
Linux/macOS:
export kete.routes.kafka-example.realm-matchers.realm=list:master
export kete.routes.kafka-example.destination.kind=kafka
export kete.routes.kafka-example.destination.bootstrap.servers=kafka:9092
export kete.routes.kafka-example.destination.topic=keycloak-events
# ...
$KEYCLOAK_HOME/bin/kc.sh start
Windows (CMD):
set kete.routes.kafka-example.realm-matchers.realm=list:master
REM ...
%KEYCLOAK_HOME%\bin\kc.bat start
Windows (PowerShell):
$env:kete.routes.kafka-example.destination.kind="kafka"
# ...
& "$env:KEYCLOAK_HOME\bin\kc.bat" start
Route Configuration¶
Each route is configured with a unique name under kete.routes.<NAME>.
Route Properties¶
| Property | Type | Default | Description |
|---|---|---|---|
kete.routes.<NAME>.enabled |
Boolean | true |
Enable/disable this route |
kete.routes.<NAME>.realm-match-mode |
String | any |
How to evaluate realm matchers: any or all |
kete.routes.<NAME>.event-match-mode |
String | any |
How to evaluate event matchers: any or all |
Serializer Configuration¶
| Property | Type | Default | Description |
|---|---|---|---|
kete.routes.<NAME>.serializer.kind |
String | json |
Serializer kind: json, xml, yaml, csv, toml, smile, cbor, properties |
JSON serialization is used by default if no serializer is specified.
Destination Configuration¶
| Property | Type | Default | Description |
|---|---|---|---|
kete.routes.<NAME>.destination.kind |
String | - | Destination kind (required): kafka, amqp-0.9.1, amqp-1, mqtt-3, mqtt-5, http |
kete.routes.<NAME>.destination.min-pool-size |
Integer | 5 |
Minimum connection pool size. Must be > 0. |
kete.routes.<NAME>.destination.max-pool-size |
Integer | 20 |
Maximum connection pool size. Must be >= min-pool-size. |
kete.routes.<NAME>.destination.* |
Various | - | Destination-specific properties (see destination guides) |
kete.routes.my-route.realm-matchers.realm=list:master
kete.routes.my-route.destination.kind=kafka
kete.routes.my-route.destination.bootstrap.servers=kafka:9092
kete.routes.my-route.destination.topic=keycloak-events
# Optional: Configure connection pool size
kete.routes.my-route.destination.min-pool-size=3
kete.routes.my-route.destination.max-pool-size=15
Matcher Configuration¶
Matchers filter which events a route processes. There are two types:
- Realm Matchers (
realm-matchers.<NAME>) - Filter by Keycloak realm - Event Matchers (
event-matchers.<NAME>) - Filter by event type
Each matcher value follows the format kind:pattern or kind:not:pattern.
| Format | Description | Example |
|---|---|---|
kind:pattern |
Match values matching pattern | glob:LOGIN* |
kind:not:pattern |
Exclude values matching pattern | glob:not:REFRESH* |
Matcher Kinds:
- glob - Unix-style wildcards (*, ?, [abc])
- regex - Regular expressions
- list - Comma-separated list (case-insensitive)
- sql - SQL LIKE patterns (%, _)
All matchers perform case-insensitive matching.
# Filter by realm
kete.routes.my-route.realm-matchers.realm=list:master,production
# Accept login events
kete.routes.my-route.event-matchers.login=glob:LOGIN*
# Exclude refresh events
kete.routes.my-route.event-matchers.no-refresh=glob:not:REFRESH*
# Multiple event matchers with ANY mode (default)
kete.routes.my-route.event-match-mode=any
kete.routes.my-route.event-matchers.login=glob:LOGIN*
kete.routes.my-route.event-matchers.logout=glob:LOGOUT*
Retry Configuration¶
| Property | Type | Default | Description |
|---|---|---|---|
kete.routes.<NAME>.retry.enabled |
Boolean | false |
Enable retry with Resilience4j |
kete.routes.<NAME>.retry.max-attempts |
Integer | - | Maximum retry attempts |
kete.routes.<NAME>.retry.wait-duration |
Duration | - | Wait between retries (ISO 8601 format) |
kete.routes.my-route.retry.enabled=true
kete.routes.my-route.retry.max-attempts=3
kete.routes.my-route.retry.wait-duration=PT1S # 1 second
Match Modes¶
Routes support separate match modes for realm and event matchers:
Variables:
- kete.routes.<NAME>.realm-match-mode
- kete.routes.<NAME>.event-match-mode
Type: String (enum)
Default: any
Valid Values: any, all
Description: Controls how multiple matchers are evaluated for realms and events independently.
ANY Mode (Default)¶
Accept an event if ANY matcher matches (logical OR):
kete.routes.alerts.event-match-mode=any
kete.routes.alerts.event-matchers.login=glob:LOGIN*
kete.routes.alerts.event-matchers.logout=glob:LOGOUT*
# Matches: LOGIN, LOGIN_ERROR, LOGOUT, LOGOUT_TIMEOUT
ALL Mode¶
Accept an event only if ALL matchers match (logical AND):
kete.routes.error-handler.event-match-mode=all
kete.routes.error-handler.event-matchers.has-login=glob:*LOGIN*
kete.routes.error-handler.event-matchers.no-success=glob:not:*SUCCESS*
# Matches: LOGIN_ERROR, LOGIN_FAILED (but NOT: LOGIN_SUCCESS)
Negation with Match Modes¶
Combine not: prefix with match modes for powerful filtering:
# Match all events EXCEPT refresh tokens
kete.routes.my-route.event-match-mode=all
kete.routes.my-route.event-matchers.filter=glob:*
kete.routes.my-route.event-matchers.no-refresh=glob:not:REFRESH*
# Match logins that are not errors
kete.routes.success.event-match-mode=all
kete.routes.success.event-matchers.login=glob:LOGIN*
kete.routes.success.event-matchers.no-error=glob:not:*ERROR*
Retry Configuration¶
Retry is configured using Resilience4j exponential backoff at the route level.
Properties¶
| Property | Type | Default | Description |
|---|---|---|---|
kete.routes.<NAME>.retry.enabled |
Boolean | false |
Enable retry |
kete.routes.<NAME>.retry.max-attempts |
Integer | - | Maximum retry attempts |
kete.routes.<NAME>.retry.wait-duration |
Duration | - | Wait between retries (ISO 8601) |
Configuration¶
# Conservative: 3 retries
kete.routes.api.retry.enabled=true
kete.routes.api.retry.max-attempts=3
kete.routes.api.retry.wait-duration=PT1S
# Aggressive: 10 retries for unreliable networks
kete.routes.resilient.retry.enabled=true
kete.routes.resilient.retry.max-attempts=10
kete.routes.resilient.retry.wait-duration=PT2S
# No retries: fail fast (default)
kete.routes.fast.retry.enabled=false
Retry Strategy¶
- Exponential Backoff: Configurable wait duration between retries
- Retry On: IOExceptions (network issues, timeouts, 5xx errors)
- No Retry On: 4xx client errors (authentication failures, bad requests)
Example¶
kete.routes.reliable-api.realm-matchers.realm=list:master
kete.routes.reliable-api.destination.kind=http
kete.routes.reliable-api.destination.host=api.example.com
kete.routes.reliable-api.destination.port=443
kete.routes.reliable-api.destination.path-and-query=/events
kete.routes.reliable-api.destination.tls.enabled=true
kete.routes.reliable-api.retry.enabled=true
kete.routes.reliable-api.retry.max-attempts=5
kete.routes.reliable-api.retry.wait-duration=PT1S
TLS/SSL Configuration¶
All destinations support TLS/SSL with optional mutual TLS (mTLS). Configuration uses Certificate Loaders for flexible certificate management.
TLS Structure¶
flowchart LR
subgraph TLS["kete.routes.<NAME>.destination.tls"]
EN["enabled"]
VER["version"]
subgraph KS["key-store (mTLS)"]
KT["type"]
KP["password"]
KKP["key-password"]
subgraph KL["loader"]
KLT["type"]
KLP["...properties"]
end
end
subgraph TS["trust-store"]
TT["type"]
TP["password"]
subgraph TL["loader"]
TLT["type"]
TLP["...properties"]
end
end
end
Certificate Loader Kinds¶
| Kind | Description | Required Properties |
|---|---|---|
pem-file-path |
PEM certificate file | path (path to .pem file) |
pem-file-base64 |
Base64-encoded PEM | base64 (base64 string) |
pem-file-text |
Raw PEM text | text (PEM string) |
pkcs12-file-path |
PKCS12 keystore file | path (path to .p12 file) |
pkcs12-file-base64 |
Base64-encoded PKCS12 | base64 (base64 string) |
der-file-path |
DER certificate file | path (path to .der file) |
der-file-base64 |
Base64-encoded DER | base64 (base64 string) |
jks-file-path |
JKS keystore file | path (path to .jks file) |
jks-file-base64 |
Base64-encoded JKS | base64 (base64 string) |
pkcs7-file-path |
PKCS7 certificate file | path (path to .p7b file) |
pkcs7-file-base64 |
Base64-encoded PKCS7 | base64 (base64 string) |
Example: mTLS with PKCS12 and JKS Files¶
kete.routes.secure-kafka.destination.tls.enabled=true
# Client certificate (PKCS12)
kete.routes.secure-kafka.destination.tls.key-store.type=PKCS12
kete.routes.secure-kafka.destination.tls.key-store.password=keystorepass
kete.routes.secure-kafka.destination.tls.key-store.loader.kind=pkcs12-file-path
kete.routes.secure-kafka.destination.tls.key-store.loader.path=/path/to/client.p12
# CA certificate (JKS truststore)
kete.routes.secure-kafka.destination.tls.trust-store.type=JKS
kete.routes.secure-kafka.destination.tls.trust-store.password=truststorepass
kete.routes.secure-kafka.destination.tls.trust-store.loader.kind=jks-file-path
kete.routes.secure-kafka.destination.tls.trust-store.loader.path=/path/to/truststore.jks
Example: Kubernetes Secrets (Base64)¶
Perfect for storing certificates as Kubernetes secrets:
kete.routes.k8s-kafka.destination.tls.enabled=true
# Client certificate from secret
kete.routes.k8s-kafka.destination.tls.key-store.loader.kind=pkcs12-file-base64
kete.routes.k8s-kafka.destination.tls.key-store.loader.base64=${CLIENT_CERT_BASE64}
kete.routes.k8s-kafka.destination.tls.key-store.password=${KEYSTORE_PASSWORD}
# CA certificate from secret
kete.routes.k8s-kafka.destination.tls.trust-store.loader.kind=pem-file-base64
kete.routes.k8s-kafka.destination.tls.trust-store.loader.base64=${CA_CERT_BASE64}
Example: PEM Certificates¶
For systems that use PEM format certificates:
kete.routes.mqtt-tls.destination.tls.enabled=true
# Trust the server's CA certificate
kete.routes.mqtt-tls.destination.tls.trust-store.loader.kind=pem-file-path
kete.routes.mqtt-tls.destination.tls.trust-store.loader.path=/certs/ca.pem
# Client certificate for mTLS
kete.routes.mqtt-tls.destination.tls.key-store.loader.kind=pem-file-path
kete.routes.mqtt-tls.destination.tls.key-store.loader.path=/certs/client.pem
kete.routes.mqtt-tls.destination.tls.key-store.key-password=keypass
Template Variables¶
Dynamic topic names and URLs can use template variables that are substituted at runtime.
Available Variables¶
| Variable | Description | Example Value |
|---|---|---|
${realmLowerCase} |
Realm name (lowercase) | myrealm |
${realmUpperCase} |
Realm name (uppercase) | MYREALM |
${eventTypeLowerCase} |
Event type (lowercase) | login_error |
${eventTypeUpperCase} |
Event type (uppercase) | LOGIN_ERROR |
${kindLowerCase} |
Event kind (lowercase) | event or admin-event |
${kindUpperCase} |
Event kind (uppercase) | EVENT or ADMIN-EVENT |
${resourceTypeLowerCase} |
Admin event resource type (lowercase) | user |
${resourceTypeUpperCase} |
Admin event resource type (uppercase) | USER |
${operationTypeLowerCase} |
Admin event operation (lowercase) | create |
${operationTypeUpperCase} |
Admin event operation (uppercase) | CREATE |
${resultLowerCase} |
Event result (lowercase) | success or error |
${resultUpperCase} |
Event result (uppercase) | SUCCESS or ERROR |
Note: The
resultvariable is derived from whether the event contains an error. Events with no error have resultSUCCESS, while events with any error have resultERROR.
Example: Topic per Realm¶
kete.routes.multi-tenant.destination.topic=keycloak-events-${realmLowerCase}
# Results in: keycloak-events-master, keycloak-events-production, etc.
Example: Topic per Event Type¶
kete.routes.by-type.destination.topic=keycloak-${eventTypeLowerCase}
# Results in: keycloak-login, keycloak-logout, keycloak-login_error, etc.
Example: Dynamic HTTP URL¶
kete.routes.dynamic-api.destination.url=https://api.example.com/${realmLowerCase}/events
# Results in: https://api.example.com/master/events
Environment Variable Examples¶
Minimal Configuration¶
kete.routes.my-route.destination.kind=kafka
kete.routes.my-route.realm-matchers.realm=list:master
kete.routes.my-route.destination.bootstrap.servers=kafka:9092
kete.routes.my-route.destination.topic=events
Multi-Route Configuration¶
# Kafka for all events
kete.routes.example-1.realm-matchers.realm=list:master
kete.routes.example-1.destination.kind=kafka
kete.routes.example-1.destination.bootstrap.servers=kafka:9092
kete.routes.example-1.destination.topic=all-events
kete.routes.example-1.event-matchers.filter=glob:*
# RabbitMQ for failed logins
kete.routes.alerts-example.realm-matchers.realm=list:master
kete.routes.alerts-example.destination.kind=amqp-0.9.1
kete.routes.alerts-example.destination.host=rabbitmq
kete.routes.alerts-example.destination.exchange=security
kete.routes.alerts-example.event-matchers.error-filter=glob:LOGIN_ERROR
Multi-Realm Configuration¶
# Production realm
kete.routes.prod.realm-matchers.realm=list:production
kete.routes.prod.destination.kind=kafka
kete.routes.prod.destination.bootstrap.servers=kafka:9092
kete.routes.prod.destination.topic=prod-events
kete.routes.prod.event-matchers.filter=glob:*
# Staging realm
kete.routes.staging.realm-matchers.realm=list:staging
kete.routes.staging.destination.kind=kafka
kete.routes.staging.destination.bootstrap.servers=kafka:9092
kete.routes.staging.destination.topic=staging-events
kete.routes.staging.event-matchers.filter=glob:*
Production-Ready Configuration¶
# Kafka with reliability settings
kete.routes.prod-kafka.realm-matchers.realm=list:production
kete.routes.prod-kafka.destination.kind=kafka
kete.routes.prod-kafka.destination.bootstrap.servers=kafka1:9092,kafka2:9092,kafka3:9092
kete.routes.prod-kafka.destination.topic=keycloak-events
kete.routes.prod-kafka.event-matchers.filter=glob:*
# Kafka reliability
kete.routes.prod-kafka.destination.acks=all
kete.routes.prod-kafka.destination.enable.idempotence=true
kete.routes.prod-kafka.destination.max.in.flight.requests.per.connection=5
kete.routes.prod-kafka.destination.retries=10
# Kafka performance
kete.routes.prod-kafka.destination.compression.type=snappy
kete.routes.prod-kafka.destination.batch.size=32768
kete.routes.prod-kafka.destination.linger.ms=10
kete.routes.prod-kafka.destination.buffer.memory=67108864
Troubleshooting Configuration¶
Extension Not Loading¶
Check:
1. enabled is not explicitly set to false (defaults to true)
2. JAR file in providers/ directory
3. Keycloak startup logs
Logs:
Destination Not Creating¶
Check: 1. All required properties set (kind, realm) 2. Destination-specific properties (bootstrap.servers, host, etc.) 3. Destination-specific properties (bootstrap.servers, host, etc.)
Logs:
Events Not Streaming¶
Check: 1. Realm listener registered: Admin Console → Realm → Events → Event Listeners 2. Event types enabled: Admin Console → Realm → Events → Event Configs 3. Filters not too restrictive 4. Destination connection working
Test:
# Remove matchers temporarily (no event-matchers = accept all)
# Or explicitly accept all:
kete.routes.<NAME>.event-matchers.filter=glob:*
# Enable all event types in Keycloak admin console
Invalid Configuration¶
Common Errors:
IllegalStateException: 'bootstrap.servers' is not configured
→ Check kafka.bootstrap.servers property
IllegalStateException: 'exchange' is not configured
→ Check rabbitmq.exchange property
IllegalStateException: Unknown filter type: xxxxx
→ Check filter syntax: glob: or regex:
Destination 'name' references unknown serializer 'xxxx'
→ Check serializer is 'json' or 'xml'
Destination 'name' is configured for non-existent realm 'xxxx'
→ Check realm name matches exactly (case-sensitive)