We login to the Keycloak Admin Console by creating a tunnel to the keycloak pod:
kubectl port-forward keycloak 8080
The default view usually displays the Master realm.
For this example we are interested in the kafka-authz
realm.
Initially, the Realm Settings section is selected, but you can navigate to Groups, Roles, Clients and Users.
Under Groups, you can view groups to mark users as having some permissions.
Groups are sets of users with a name assigned. Typically, they are used to compartmentalize users into geographical, organizational or departmental units, and so on.
In Keycloak, groups can be stored in an LDAP identity provider.
That makes it possible to make a user a member of a group through a custom LDAP server admin user interface, for example, to grant them some permissions on Kafka resources.
Under Users, you can view all defined users. For this example, alice
and bob
are defined. alice
is a member of the ClusterManager Group
, and bob
is a member of ClusterManager-my-cluster Group
.
In Keycloak, users can be stored in an LDAP identity provider.
Under Roles, you can view the realm roles to mark users or clients as having some permissions.
Roles are a concept analogous to groups. They are usually used to tag users with organizational roles and have the requisite permissions.
Roles cannot be stored in an LDAP identity provider.
If LDAP is a requirement, you can use groups instead, and add Keycloak roles to the groups so that when users are assigned a group, they also get a corresponding role.
Under Clients, you can view the additional client configurations. For this example, kafka
, kafka-cli
, team-a-client
, team-b-client
are configured.
The client with client id kafka
is used by Kafka Brokers to perform the necessary OAuth 2.0 communication for access token validation,
and to authenticate to other Kafka Broker instances using OAuth 2.0 client authentication.
This client also contains the Authorization Services resource definitions, policies and authorization scopes used to perform authorization on the Kafka Brokers.
The client with client id kafka-cli
is a public client that can be used by the Kafka command line tools when authenticating with username and password to obtain an access token or a refresh token.
Clients team-a-client
, and team-b-client
are confidential clients representing services with partial access to certain Kafka topics.
The authorization configuration is defined in the kafka
client from the Authorization tab, which becomes visible when Authorization Enabled is switched on from the Settings tab.
To ensure that authorization rules have been properly imported, from we check that Decision Strategy is set to Affirmative, and NOT to Unanimous.
From Keycloak, you can check that the expected resources, authorization claims, policies and permissions are defined.
With the configuration in place, you can check access to Kafka by using a producer and consumer to create topics using different user and service accounts.
First, a new interactive pod container is run using a Strimzi Kafka image to connect to a running Kafka broker.
kubectl run -ti --rm --restart=Never --image=quay.io/strimzi/kafka:0.23.0-kafka-2.8.0 kafka-cli -- /bin/sh
Note
|
If kubectl times out waiting on the image download, subsequent attempts may result in an AlreadyExists error.
|
You can attach to the existing pod by running:
kubectl attach -ti kafka-cli
To produce messages as client team-a-client
, we prepare a Kafka client configuration file with authentication parameters:
cat > /tmp/team-a-client.properties << EOF
security.protocol=SASL_PLAINTEXT
sasl.mechanism=OAUTHBEARER
sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
oauth.client.id="team-a-client" \
oauth.client.secret="team-a-client-secret" \
oauth.token.endpoint.uri="http://keycloak:8080/auth/realms/kafka-authz/protocol/openid-connect/token" ;
sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
EOF
The roles assigned to a client, such as the Dev Team A
realm role assigned to the team-a-client
service account, are presented in Keycloak on the Service Account Roles tab from Clients.
We can use this configuration from the Kafka CLI to produce and consume messages, and perform other administration tasks.
Producing messages with authorized access
The team-a-client
configuration is used to produce messages to topic my-topic
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic my-topic \
--producer.config=/tmp/team-a-client.properties
First message
A Not authorized to access topics: [my-topic]
error is returned when trying to push the first message.
team-a-client
has a Dev Team A
role that gives it permission to perform any supported actions on topics that start with a_
, but can only write to topics that start with x_
.
The topic named my-topic
matches neither of those rules.
The team-a-client
configuration is then used to produce messages to topic a_messages
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic a_messages \
--producer.config /tmp/team-a-client.properties
First message
Second message
The messages are pushed out successfully, and in the Kafka container log there is DEBUG level output saying Authorization GRANTED
.
Use CTRL-C to exit the CLI application.
You can see the Kafka container log by running:
kubectl logs my-cluster-kafka-0 -f
Consuming messages with authorized access
The team-a-client
configuration is used to consume messages from topic a_messages
:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic a_messages \
--from-beginning --consumer.config /tmp/team-a-client.properties
An error is returned as the Dev Team A
role for team-a-client
only has access to consumer groups that have names starting with a_
.
The team-a-client
configuration is then used to consume messages when specifying a custom consumer group with a name that starts with a_
:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic a_messages \
--from-beginning --consumer.config /tmp/team-a-client.properties --group a_consumer_group_1
This time the consumer receives all the messages from the a_messages
topic.
Administering Kafka with authorized access
The team-a-client
configuration is used in administrative operations.
Listing topics returns the a_messages
topic:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/team-a-client.properties --list
Listing consumer groups returns the a_consumer_group_1
consumer group:
bin/kafka-consumer-groups.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/team-a-client.properties --list
Fetching the default cluster configuration fails cluster authorization, because the operation requires cluster level permissions that team-a-client
does not have:
bin/kafka-configs.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/team-a-client.properties \
--entity-type brokers --describe --entity-default
Using clients with different permissions
As with team-a-client
, we prepare a Kafka client configuration file with authentication parameters for team-b-client
:
cat > /tmp/team-b-client.properties << EOF
security.protocol=SASL_PLAINTEXT
sasl.mechanism=OAUTHBEARER
sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
oauth.client.id="team-b-client" \
oauth.client.secret="team-b-client-secret" \
oauth.token.endpoint.uri="http://keycloak:8080/auth/realms/kafka-authz/protocol/openid-connect/token" ;
sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
EOF
The team-b-client
client configuration includes a Dev Team B
realm role and permissions that start with Dev Team B
. These match the users and service accounts that have the Dev Team B
realm role assigned to them.
The Dev Team B
users have full access to topics beginning with b_
on the Kafka cluster my-cluster
, the name of the designated cluster, and read access on topics that start with x_
.
The team-b-client
configuration is used to produce messages to topic a_messages
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic a_messages \
--producer.config /tmp/team-b-client.properties
Message 1
A Not authorized to access topics: [a_messages]
error is returned when trying to push the first message, as expected, so we switch to topic b_messages
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic b_messages \
--producer.config /tmp/team-b-client.properties
Message 1
Message 2
Message 3
Producing messages to topic b_messages
is authorized and successful.
We switch again, but this time to a topic that team-b-client
can only read from, topic x_messages
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic x_messages \
--producer.config /tmp/team-b-client.properties
Message 1
A Not authorized to access topics: [x_messages]
error is returned, as expected, so we switch to team-a-client
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic x_messages \
--producer.config /tmp/team-a-client.properties
Message 1
A Not authorized to access topics: [x_messages]
error is returned again. Though team-a-client
can write to the x_messages
topic, it it does not have a permission to create a topic if it does not yet exist.
Before team-a-client
can write to the x_messages
topic, a admin power user must create it with the correct configuration, such as the number of partitions and replicas.
Managing Kafka with an authorized admin
Admin user bob
is created with full access to manage everything on the Kafka cluster my-cluster
.
Helper scripts are used to authenticate to the keycloak
instance.
The following scripts are downloaded to /tmp
dir and made executable:
curl https://raw.githubusercontent.com/strimzi/strimzi-kafka-oauth/0.7.2/examples/docker/kafka-oauth-strimzi/kafka/oauth.sh -s > /tmp/oauth.sh
chmod +x /tmp/oauth.sh
curl https://raw.githubusercontent.com/strimzi/strimzi-kafka-oauth/0.7.2/examples/docker/kafka-oauth-strimzi/kafka/jwt.sh -s > /tmp/jwt.sh
chmod +x /tmp/jwt.sh
User bob
authenticates to the Keycloak server with his username and password to get a refresh token:
export TOKEN_ENDPOINT=http://keycloak:8080/auth/realms/kafka-authz/protocol/openid-connect/token
REFRESH_TOKEN=$(/tmp/oauth.sh -q bob)
When prompted for a password, 'bob-password' is used.
The refresh token in this case is an offline token which is a long-lived refresh token that does not expire:
/tmp/jwt.sh $REFRESH_TOKEN
A configuration file is created for bob
:
cat > /tmp/bob.properties << EOF
security.protocol=SASL_PLAINTEXT
sasl.mechanism=OAUTHBEARER
sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required \
oauth.refresh.token="$REFRESH_TOKEN" \
oauth.client.id="kafka-cli" \
oauth.token.endpoint.uri="http://keycloak:8080/auth/realms/kafka-authz/protocol/openid-connect/token" ;
sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
EOF
The kafka-cli
public client is used for the oauth.client.id
in the sasl.jaas.config
.
Since that is a public client it does not require a Secret.
We can use this because we authenticate with a token directly. In this case, the refresh token requests an access token behind the scenes, which is then sent to the Kafka broker for authentication. The refresh token has already been authenticated.
User bob
has permission to create the x_messages
topic:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/bob.properties \
--topic x_messages --create --replication-factor 1 --partitions 1
User bob
can list the topic, but team-a-client
and team-b-client
cannot:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/bob.properties --list
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/team-a-client.properties --list
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --command-config /tmp/team-b-client.properties --list
The Dev Team A
, and Dev Team B
roles both have Describe
permission on topics that start with x_
, but they cannot see the other team’s topics as they do not have Describe
permissions on them.
The team-a-client
can now successfully produce to the x_messages
topic:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic x_messages \
--producer.config /tmp/team-a-client.properties
Message 1
Message 2
Message 3
As expected, team-b-client
still cannot produce to the x_messages
topic, and the following operation returns an error:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9092 --topic x_messages \
--producer.config /tmp/team-b-client.properties
Message 4
Message 5
However, due to its Keycloak settings team-b-client
can consume messages from the x_messages
topic:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic x_messages \
--from-beginning --consumer.config /tmp/team-b-client.properties --group x_consumer_group_b
Conversely, even though team-a-client
can write to topic x_messages
, the following read request returns a Not authorized to access group: x_consumer_group_a
error:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic x_messages \
--from-beginning --consumer.config /tmp/team-a-client.properties --group x_consumer_group_a
A consumer group that begins with a_
is used in the next read request:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic x_messages \
--from-beginning --consumer.config /tmp/team-a-client.properties --group a_consumer_group_a
An error is still returned, but this time it is Not authorized to access topics: [x_messages]
.
Dev Team A
has no Read
access on topics that start with 'x_'.
User bob
can read from or write to any topic:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic x_messages \
--from-beginning --consumer.config /tmp/bob.properties