tchiotludo

AKHQ (previously known as KafkaHQ)

Kafka GUI for Apache Kafka to manage topics, topics data, consumers group, schema registry, connect and more...
Under Apache License 2.0
By tchiotludo

java gui kafka kafkahq kafka-ui kafka-utils kafka-tools

AKHQ (previously known as KafkaHQ)



Kafka GUI for Apache Kafka to manage topics, topics data, consumers group, schema registry, connect and more...






Contents


Features

New React UI

Since this is a major rework, the new UI can have some issues, so please report any issue, thanks!


Quick preview

It will start a Kafka node, a Zookeeper node, a Schema Registry, a Kafka Connect, fill with some sample data, start a consumer
group and a kafka stream & start AKHQ.


Installation

First you need a configuration files in order to configure AKHQ connections to Kafka Brokers.


Docker

sh
docker run -d \
-p 8080:8080 \
-v /tmp/application.yml:/app/application.yml \
tchiotludo/akhq

* With -v /tmp/application.yml must be an absolute path to configuration file
* Go to http://localhost:8080


Stand Alone

Running in Kubernetes (using a Helm Chart)
Using Helm repository

Requirements

Using git

Configuration

Configuration file can by default be provided in either Java properties, YAML, JSON or Groovy files. YML Configuration
file example can be found here :application.example.yml


Pass custom Java opts

By default, the docker container will allow a custom JVM options setting the environments vars JAVA_OPTS.
For example, if you want to change the default timezone, just add -e "JAVA_OPTS=-Duser.timezone=Europe/Paris"


Run with another jvm.options file

By default, the docker container will run with a jvm.options file, you can override it with
your own with an Environment Variable. With the JVM_OPTS_FILE environment variable, you can override the jvm.options file by passing
the path of your file instead.


Override the JVM_OPTS_FILE with docker run:


sh
docker run -d \
--env JVM_OPTS_FILE={{path-of-your-jvm.options-file}}
-p 8080:8080 \
-v /tmp/application.yml:/app/application.yml \
tchiotludo/akhq


Override the JVM_OPTS_FILE with docker-compose:


yaml
version: '3.7'
services:
akhq:
image: tchiotludo/akhq-jvm:dev
environment:
JVM_OPTS_FILE: /app/jvm.options
ports:
- "8080:8080"
volumes:
- /tmp/application.yml:/app/application.yml


If you do not override the JVM_OPTS_FILE, the docker container will take the defaults one instead.


Kafka cluster configuration

SSL Kafka Cluster with basic auth

Configuration example for kafka cluster secured by ssl for saas provider like aiven (full https & basic auth):


You need to generate a jks & p12 file from pem, cert files give by saas provider.
bash
openssl pkcs12 -export -inkey service.key -in service.cert -out client.keystore.p12 -name service_key
keytool -import -file ca.pem -alias CA -keystore client.truststore.jks


Configurations will look like this example:


yaml
akhq:
connections:
ssl-dev:
properties:
bootstrap.servers: "{{host}}.aivencloud.com:12835"
security.protocol: SSL
ssl.truststore.location: {{path}}/avnadmin.truststore.jks
ssl.truststore.password: {{password}}
ssl.keystore.type: "PKCS12"
ssl.keystore.location: {{path}}/avnadmin.keystore.p12
ssl.keystore.password: {{password}}
ssl.key.password: {{password}}
schema-registry:
url: "https://{{host}}.aivencloud.com:12838"
type: "confluent"
basic-auth-username: avnadmin
basic-auth-password: {{password}}
properties:
schema.registry.ssl.truststore.location: {{path}}/avnadmin.truststore.jks
schema.registry.ssl.truststore.password: {{password}}
connect:
- name: connect-1
url: "https://{{host}}.aivencloud.com:{{port}}"
basic-auth-username: avnadmin
basic-auth-password: {{password}}


AKHQ configuration
Pagination

Avro Serializer

Topic List

Topic creation default values

These parameters are the default values used in the topic creation page.



Topic Data

Ui Settings
Topics

Topic Data

Protobuf deserialization

To deserialize topics containing data in Protobuf format, you can set topics mapping:
for each topic-regex you can specify descriptor-file-base64 (descriptor file encoded to Base64 format),
or you can put descriptor files in descriptors-folder and specify descriptor-file name,
also specify corresponding message types for keys and values.
If, for example, keys are not in Protobuf format, key-message-type can be omitted,
the same for value-message-type.
This configuration can be specified for each Kafka cluster.


Example configuration can look like as follows:


akhq:
connections:
kafka:
properties:
# standard kafka properties
deserialization:
protobuf:
descriptors-folder: "/app/protobuf_desc"
topics-mapping:
- topic-regex: "album.*"
descriptor-file-base64: "Cs4BCgthbGJ1bS5wcm90bxIXY29tLm5ldGNyYWNrZXIucHJvdG9idWYidwoFQWxidW0SFAoFdGl0bGUYASABKAlSBXRpdGxlEhYKBmFydGlzdBgCIAMoCVIGYXJ0aXN0EiEKDHJlbGVhc2VfeWVhchgDIAEoBVILcmVsZWFzZVllYXISHQoKc29uZ190aXRsZRgEIAMoCVIJc29uZ1RpdGxlQiUKF2NvbS5uZXRjcmFja2VyLnByb3RvYnVmQgpBbGJ1bVByb3RvYgZwcm90bzM="
value-message-type: "Album"
- topic-regex: "film.*"
descriptor-file-base64: "CuEBCgpmaWxtLnByb3RvEhRjb20uY29tcGFueS5wcm90b2J1ZiKRAQoERmlsbRISCgRuYW1lGAEgASgJUgRuYW1lEhoKCHByb2R1Y2VyGAIgASgJUghwcm9kdWNlchIhCgxyZWxlYXNlX3llYXIYAyABKAVSC3JlbGVhc2VZZWFyEhoKCGR1cmF0aW9uGAQgASgFUghkdXJhdGlvbhIaCghzdGFycmluZxgFIAMoCVIIc3RhcnJpbmdCIQoUY29tLmNvbXBhbnkucHJvdG9idWZCCUZpbG1Qcm90b2IGcHJvdG8z"
value-message-type: "Film"
- topic-regex: "test.*"
descriptor-file: "other.desc"
key-message-type: "Row"
value-message-type: "Envelope"


More examples about Protobuf deserialization can be found in tests.
Info about the descriptor files generation can be found in test resources.


AKHQ Configuration Bootstrap OAuth2
Requirement Library Strimzi

The kafka brokers must be configured with the Strimzi library and an OAuth2 provider (Keycloak example).


This contains documentation and examples.



Configuration Bootstrap

It's not necessary to compile AKHQ to integrate the Strimzi libraries since the libs will be included on the final image !



You must configure AKHQ through the application.yml file.


yaml
akhq:
connections:
my-kafka-cluster:
properties:
bootstrap.servers: "<url broker kafka>:9094,<url broker kafka>:9094"
sasl.jaas.config: org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required auth.valid.issuer.uri="https://<url keycloak>/auth/realms/sandbox_kafka" oauth.jwks.endpoint.uri="https:/<url keycloak>//auth/realms/sandbox_kafka/protocol/openid-connect/certs" oauth.username.claim="preferred_username" oauth.client.id="kafka-producer-client" oauth.client.secret="" oauth.ssl.truststore.location="kafka.server.truststore.jks" oauth.ssl.truststore.password="xxxxx" oauth.ssl.truststore.type="jks" oauth.ssl.endpoint_identification_algorithm="" oauth.token.endpoint.uri="https:///auth/realms/sandbox_kafka/protocol/openid-connect/token";
sasl.login.callback.handler.class: io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
security.protocol: SASL_PLAINTEXT
sasl.mechanism: OAUTHBEARER

I put oauth.ssl.endpoint_identification_algorithm = "" for testing or my certificates did not match the FQDN. In a production, you have to remove it.


Security

By default, security & roles is disabled and anonymous user have full access, i.e. micronaut.security.enabled: false.
To enable security & roles set micronaut.security.enabled: true and configure desired type of authentication (basic auth, LDAP, etc.).


If you need a read-only application, simply add this to your configuration files :
yaml
akhq:
security:
default-group: reader


Auth
JWT

AKHQ uses JWT tokens to perform authentication.
Please generate a secret that is at least 256 bits and change the config like this:


yaml
micronaut:
security:
enabled: true
token:
jwt:
signatures:
secret:
generator:
secret: <Your secret here>


Groups

Groups allow you to limit user


Define groups with specific roles for your users
* akhq.security.default-group: Default group for all the user even unlogged user



:warning: topics-filter-regexp, connects-filter-regexp and consumer-groups-filter-regexp are only used when listing resources.
If you have topics/create or connect/create roles and you try to create a resource that doesn't follow the regexp, that resource WILL be created.


3 defaults group are available :
- admin with all right
- reader with only read access on all AKHQ
- no-roles without any roles, that force user to login


Basic Auth


Take care that basic auth will use session store in the server memory. If your instance is behind a reverse proxy or a
loadbalancer, you will need to forward the session cookie named SESSION and / or use
session stickiness



Configure basic-auth connection in AKHQ
yaml
micronaut:
security:
enabled: true
akhq.security:
basic-auth:
- username: admin
password: "$2a$<hashed password>"
passwordHash: BCRYPT
groups:
- admin
- username: reader
password: "<SHA-256 hashed password>"
groups:
- reader


LDAP

Configure how the ldap groups will be matched in AKHQ groups
* akhq.security.ldap.groups: Ldap groups list
* - name: ldap-group-name: Ldap group name (same name as in ldap)
* groups: AKHQ group list to be used for current ldap group


Example using online ldap test server


Configure ldap connection in micronaut
yaml
micronaut:
security:
enabled: true
ldap:
default:
enabled: true
context:
server: 'ldap://ldap.forumsys.com:389'
managerDn: 'cn=read-only-admin,dc=example,dc=com'
managerPassword: 'password'
search:
base: "dc=example,dc=com"
groups:
enabled: true
base: "dc=example,dc=com"


If you want to enable anonymous auth to your LDAP server you can pass :
yaml
managerDn: ''
managerPassword: ''


In Case your LDAP groups do not use the default UID for group membership, you can solve this using


yaml
micronaut:
security:
enabled: true
ldap:
default:
search:
base: "OU=UserOU,dc=example,dc=com"
attributes:
- "cn"
groups:
enabled: true
base: "OU=GroupsOU,dc=example,dc=com"
filter: "member={0}"

Replace
yaml
attributes:
- "cn"

with your group membership attribute


Configure AKHQ groups and Ldap groups and users
```yaml
micronaut:
security:
enabled: true
akhq:
security:
groups:
topic-reader:
name: topic-reader # Group name
roles: # roles for the group
- topic/read
attributes:
# List of Regexp to filter topic available for group
# Single line String also allowed
# topics-filter-regexp: "^(projectA_topic|projectB_.)$"
topics-filter-regexp:
- "^projectA_topic$" # Individual topic
- "^projectB_.
$" # Topic group
connects-filter-regexp:
- "^test.$"
consumer-groups-filter-regexp:
- "consumer.
"
topic-writer:
name: topic-writer # Group name
roles:
- topic/read
- topic/insert
- topic/delete
- topic/config/update
attributes:
topics-filter-regexp:
- "test."
connects-filter-regexp:
- "^test.
$"
consumer-groups-filter-regexp:
- "consumer.*"
ldap:
groups:
- name: mathematicians
groups:
- topic-reader
- name: scientists
groups:
- topic-reader
- topic-writer
users:
- username: franz
groups:
- topic-reader
- topic-writer


```


OIDC

To enable OIDC in the application, you'll first have to enable OIDC in micronaut:


yaml
micronaut:
security:
oauth2:
enabled: true
clients:
google:
client-id: "<client-id>"
client-secret: "<client-secret>"
openid:
issuer: "<issuer-url>"


To further tell AKHQ to display OIDC options on the login page and customize claim mapping, configure OIDC in the AKHQ config:


yaml
akhq:
security:
oidc:
enabled: true
providers:
google:
label: "Login with Google"
username-field: preferred_username
# specifies the field name in the oidc claim containing the use assigned role (eg. in keycloak this would be the Token Claim Name you set in your Client Role Mapper)
groups-field: roles
default-group: topic-reader
groups:
# the name of the user role set in your oidc provider and associated with your user (eg. in keycloak this would be a client role)
- name: mathematicians
groups:
# the corresponding akhq groups (eg. topic-reader/writer or akhq default groups like admin/reader/no-role)
- topic-reader
- name: scientists
groups:
- topic-reader
- topic-writer
users:
- username: franz
groups:
- topic-reader
- topic-writer


The username field can be any string field, the roles field has to be a JSON array.


Header configuration (reverse proxy)

To enable Header authentication in the application, you'll have to configure the header that will resolve users & groups:


yaml
akhq:
security:
# Header configuration (reverse proxy)
header-auth:
user-header: x-akhq-user # mandatory (the header name that will contain username)
groups-header: x-akhq-group # optional (the header name that will contain groups separated by groups-header-separator)
groups-header-separator: , # optional (separator, defaults to ',')
ip-patterns: [0.0.0.0] # optional (Java regular expressions for matching trusted IP addresses, '0.0.0.0' matches all addresses)
default-group: topic-reader
groups: # optional
# the name of the user group read from header
- name: header-admin-group
groups:
# the corresponding akhq groups (eg. topic-reader/writer or akhq default groups like admin/reader/no-role)
- admin
users: # optional
- username: header-user # username matching the `user-header` value
groups: # list of groups / additional groups
- topic-writer
- username: header-admin
groups:
- admin



External roles and attributes mapping

If you managed which topics (or any other resource) in an external system, you have access to 2 more implementations mechanisms to map your authenticated user (from either Local, Header, LDAP or OIDC) into AKHQ roles and attributes:


If you use this mechanism, keep in mind it will take the local user's groups for local Auth, and the external groups for Header/LDAP/OIDC (ie. this will NOT do the mapping between Header/LDAP/OIDC and local groups)


Default configuration-based
This is the current implementation and the default one (doesn't break compatibility)
yaml
akhq:
security:
default-group: no-roles
groups:
reader:
roles:
- topic/read
attributes:
topics-filter-regexp: [".*"]
no-roles:
roles: []
ldap: # LDAP users/groups to AKHQ groups mapping
oidc: # OIDC users/groups to AKHQ groups mapping
header-auth: # header authentication users/groups to AKHQ groups mapping


REST API
yaml
akhq:
security:
default-group: no-roles
rest:
enabled: true
url: https://external.service/get-roles-and-attributes
groups: # anything set here will not be used


In this mode, AKHQ will send to the akhq.security.rest.url endpoint a POST request with the following JSON :


json
{
"providerType": "LDAP or OIDC or BASIC_AUTH or HEADER",
"providerName": "OIDC provider name (OIDC only)",
"username": "user",
"groups": ["LDAP-GROUP-1", "LDAP-GROUP-2", "LDAP-GROUP-3"]
}

and expect the following JSON as response :
json
{
"roles": ["topic/read", "topic/write", "..."],
"attributes":
{
"topics-filter-regexp": [".*"],
"connects-filter-regexp": [".*"],
"consumer-groups-filter-regexp": [".*"]
}
}


Groovy API
yaml
akhq:
security:
default-group: no-roles
groovy:
enabled: true
file: |
package org.akhq.utils;
class GroovyCustomClaimProvider implements ClaimProvider {
@Override
AKHQClaimResponse generateClaim(AKHQClaimRequest request) {
AKHQClaimResponse a = new AKHQClaimResponse();
a.roles = ["topic/read"]
a.attributes = [
topicsFilterRegexp: [".*"],
connectsFilterRegexp: [".*"],
consumerGroupsFilterRegexp: [".*"]
]
return a
}
}
groups: # anything set here will not be used

akhq.security.groovy.file must be a groovy class that implements the interface ClaimProvider :
````java
package org.akhq.utils;
public interface ClaimProvider {


AKHQClaimResponse generateClaim(AKHQClaimRequest request);

class AKHQClaimRequest{
ProviderType providerType;
String providerName;
String username;
List<String> groups;
}
class AKHQClaimResponse {
private List<String> roles;
private Map<String,Object> attributes;
}
enum ProviderType {
BASIC_AUTH,
LDAP,
OIDC
}


}
````


Debugging authentication

Debugging auth can be done by increasing log level on Micronaut that handle most of the authentication part :
```bash
curl -i -X POST -H "Content-Type: application/json" \
-d '{ "configuredLevel": "TRACE" }' \
http://localhost:28081/loggers/io.micronaut.security


curl -i -X POST -H "Content-Type: application/json" \
-d '{ "configuredLevel": "TRACE" }' \
http://localhost:28081/loggers/org.akhq.configs
```


Server

Kafka admin / producer / consumer default properties

Micronaut configuration

Since AKHQ is based on Micronaut, you can customize configurations (server port, ssl, ...) with Micronaut configuration.
More information can be found on Micronaut documentation



Docker

The AKHQ docker image supports 4 environment variables to handle configuration :
* AKHQ_CONFIGURATION: a string that contains the full configuration in yml that will be written on
/app/configuration.yml on the container.
* MICRONAUT_APPLICATION_JSON: a string that contains the full configuration in JSON format
* MICRONAUT_CONFIG_FILES: a path to a configuration file in the container. Default path is /app/application.yml
* CLASSPATH: additional Java classpath entries. Must be used to specify the location of the TIBCO Avro client library
jar if a 'tibco' schema registry type is used


How to mount configuration file

Take care when you mount configuration files to not remove akhq files located on /app.
You need to explicitly mount the /app/application.yml and not mount the /app directory.
This will remove the AKHQ binaries and give you this error: /usr/local/bin/docker-entrypoint.sh: 9: exec: ./akhq: not found


```yaml
volumeMounts:
- mountPath: /app/application.yml
subPath: application.yml
name: config
readOnly: true


```


Using the TIBCO schema registry

If you are using the TIBCO schema registry, you will also need to mount and use the TIBCO Avro client library and its
dependencies. The akhq service in a docker compose file might look something like:


yaml
akhq:
# build:
# context: .
image: tchiotludo/akhq
volumes:
- /opt/tibco/akd/repo/1.2/lib/tibftl-kafka-avro-1.2.0-thin.jar:/app/tibftl-kafka-avro-1.2.0-thin.jar
- /opt/tibco/akd/repo/1.2/lib/deps:/app/deps
environment:
AKHQ_CONFIGURATION: |
akhq:
connections:
docker-kafka-server:
properties:
bootstrap.servers: "kafka:9092"
schema-registry:
type: "tibco"
url: "http://repo:8081"
connect:
- name: "connect"
url: "http://connect:8083"
CLASSPATH: "/app/tibftl-kafka-avro-1.2.0-thin.jar:/app/deps/*"
ports:
- 8080:8080
links:
- kafka
- repo


Api

An experimental api is available that allow you to fetch all the exposed on AKHQ through api.


Take care that this api is experimental and will change in a future release.
Some endpoints expose too many data and is slow to fetch, and we will remove
some properties in a future in order to be fast.


Example: List topic endpoint expose log dir, consumer groups, offsets. Fetching all theses
is slow for now, and we will remove these in a future.


You can discover the api endpoint here :
* /api: a RapiDoc webpage that document all the endpoints.
* /swagger/akhq.yml: a full OpenApi specifications files


Monitoring endpoint

Several monitoring endpoint is enabled by default and available on port 28081 only.


You can disable it, change the port or restrict access only for authenticated users following micronaut configuration below.



Debugging AKHQ performance issues

You can debug all query duration from AKHQ with this commands
bash
curl -i -X POST -H "Content-Type: application/json" \
-d '{ "configuredLevel": "TRACE" }' \
http://localhost:28081/loggers/org.akhq


Development Environment
Early dev image

You can have access to last feature / bug fix with docker dev image automatically build on tag dev
bash
docker pull tchiotludo/akhq:dev


The dev jar is not publish on GitHub, you have 2 solutions to have the dev jar :


Get it from docker image
bash
docker pull tchiotludo/akhq:dev
docker run --rm --name=akhq -it tchiotludo/akhq:dev
docker cp akhq:/app/akhq.jar .

Or build it with a ./gradlew shadowJar, the jar will be located here build/libs/akhq-*.jar


Development Server

A docker-compose is provided to start a development environment.
Just install docker & docker-compose, clone the repository and issue a simple docker-compose -f docker-compose-dev.yml up to start a dev server.
Dev server is a java server & webpack-dev-server with live reload.


The configuration for the dev server is in application.dev.yml.


Setup local dev environment on Windows

In case you want to develop for AKHQ on Windows with IntelliJ IDEA without Docker (for any reason) you can follow this
brief guide. For the following steps, please, make sure you meet these requirements:



First run a Kafka server locally. Therefore, you need to start Zookeeper first by opening a CMD and doing:
bash
$KAFKA_HOME\bin\windows\zookeeper-server-start.bat config\zookeper.properties
$KAFKA_HOME\bin\windows\kafka-server-start.bat config\server.properties

A zero-config Kafka server should be up and running locally on your machine now. For further details or troubleshooting
see Kafka Getting started guide. In the next step we're going to checkout AKHQ from GitHub:
bash
git clone https://github.com/tchiotludo/akhq.git


Open the checked out directory in IntelliJ IDEA. The current version (0.16.0) of AKHQ is built with Java 11. If you
don't have OpenJDK 11 installed already, do the following in IntelliJ IDEA: File > Project Structure... > Platform Settings >
SDKs > + > Download JDK... >
select a vendor of your choice (but make sure it's version 11), download + install. Make sure
that JDK 11 is set under Project Settings > Project SDK and language level is Java 11. Now tell Gradle to use Java 11
as well: File > Settings > Plugins > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM: any JDK 11.


To configure AKHQ for using the Kafka server you set up before, edit application.yml by adding the following under akhq:
yaml
akhq:
connections:
kafka:
properties:
bootstrap.servers: "localhost:9092"

/!\ Do not commit this part of application.yml. A more secure way to configure your local development Kafka server is
described in the Micronaut doc, chapter "Application Configuration".


Now you should be able to build the project with Gradle. Therefore, go to the Gradle view in IDEA, select Tasks > build >
build
. If an error occurs saying that any filename is too long: move your project directory to a root directory in your
filesystem or as a fix (only for testing purposes) set the argument -x test to skip tests temporarily.


To debug a running AKHQ instance, go to the Gradle tab in IntelliJ IDEA, Tasks > application > right click run and click
"Debug(...)". AKHQ should start up and hit the breakpoints you set in your IDE. Happy developing/debugging!


Schema references

Since Confluent 5.5.0, Avro schemas can now be reused by others schemas through schema references. This feature allows to define a schema once and use it as a record type inside one or more schemas.


When registering new Avro schemas with AKHQ UI, it is now possible to pass a slightly more complex object with a schema and a references field.


To register a new schema without references, no need to change anything:


json
{
"name": "Schema1",
"namespace": "org.akhq",
"type": "record",
"fields": [
{
"name": "description",
"type": "string"
}
]
}


To register a new schema with a reference to an already registered schema:


```json
{
"schema": {
"name": "Schema2",
"namespace": "org.akhq",
"type": "record",
"fields": [
{
"name": "name",
"type": "string"
},
{
"name": "schema1",
"type": "Schema1"
}
]
},
"references": [
{
"name": "Schema1",
"subject": "SCHEMA_1",
"version": 1
}
]
}
````


Documentation on Confluent 5.5 and schema references can be found here.


Who's using AKHQ

Credits

Many thanks to:




License

Apache 2.0 © tchiotludo