When a Keycloak instance is running, upload the example realm to Keycloak using the Admin Console.
First, determine the external hostname to access the Admin Console.
For example, if the Keycloak Operator installed the Keycloak server in the sso
namespace, run the following command:
NS=sso
kubectl get ingress keycloak -n $NS
Second, get the password for the admin user.
If you used Keycloak Operator, the password is stored as a secret, so you first have to identify the name of the secret. You can inspect the yaml for the Keycloak instance. For example:
kubectl get -n $NS pod keycloak-0 -o yaml | less
Search for KEYCLOAK_PASSWORD
and note the secretKeyRef.name
(usually credential-keycloak
or similar).
You can then obtain the clear text password by running:
SECRET_NAME=credential-keycloak
kubectl get -n $NS secret $SECRET_NAME -o yaml | grep PASSWORD | awk '{print $2}' | base64 -D
Log in to the Admin Console with the username admin
and the password you obtained earlier. You must use https://HOST
to access the Kubernetes ingress.
When you are logged in, click Add Realm to import the example realm.
Select the examples/security/keycloak-authorization/kafka-authz-realm.json
file from your disk, and then click Create.
You should now have kafka-authz
as your current realm in the Admin Console.
After logging into the Admin Console, the default view displays the Master realm.
For this example, locate and select the example kafka-authz
realm that you uploaded earlier.
Initially, the Realm Settings section is selected, but you can navigate to Groups, Roles, Clients and Users.
Under Groups, you can view user groups and set user permissions.
Groups are sets of users with a name assigned. Typically, they are used to compartmentalize users into geographical, organizational or departmental units.
In Keycloak, groups can be stored in an LDAP identity provider.
You can make a user a member of a group through a custom LDAP server admin user interface, for example, to grant 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
, and 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.
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.
Before checking authorized access to Kafka, the imported authorization rules must be present in the Admin Console.
From Decision Strategy must be set to Affirmative, and NOT to Unanimous.
The Resources, Authorization claims, Policies, and Permissions tabs must contain the authorization content.
With the configuration in place, we 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.
NS=sso
kubectl run -ti --restart=Never --image=quay.io/strimzi/kafka:0.24.0-kafka-2.8.0 kafka-cli -n $NS -- /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 -n $NS
To produce messages as client team-a-client
, we prepare a Kafka client configuration file.
We use the SASL/OAUTHBEARER mechanism with a Client ID and Client Secret, which means the client first connects to the Keycloak server to obtain an access token. The client then connects to the Kafka broker and uses the obtained access token to authenticate.
We need to prepare and configure the truststore for TLS connections to work.
First, we use the external hostname exposing the Keycloak to obtain the certificate.
SSO_HOST=<sso_hostname>
SSO_HOST_PORT=$SSO_HOST:443
STOREPASS=storepass
echo "Q" | openssl s_client -showcerts -connect $SSO_HOST_PORT 2>/dev/null | awk ' /BEGIN CERTIFICATE/,/END CERTIFICATE/ { print $0 } ' > /tmp/sso.crt
keytool -keystore /tmp/truststore.p12 -storetype pkcs12 -alias sso -storepass $STOREPASS -import -file /tmp/sso.crt -noprompt
Then, we add to the same truststore the certificate for the Kafka broker, which we obtain using the my-cluster-kafka-bootstrap
as a hostname and tls
listener port (9093):
KAFKA_HOST_PORT=my-cluster-kafka-bootstrap:9093
STOREPASS=storepass
echo "Q" | openssl s_client -showcerts -connect $KAFKA_HOST_PORT 2>/dev/null | awk ' /BEGIN CERTIFICATE/,/END CERTIFICATE/ { print $0 } ' > /tmp/my-cluster-kafka.crt
keytool -keystore /tmp/truststore.p12 -storetype pkcs12 -alias my-cluster-kafka -storepass $STOREPASS -import -file /tmp/my-cluster-kafka.crt -noprompt
Finally, we prepare the Kafka Client configuration parameters:
SSO_HOST=<sso_hostname>
cat > /tmp/team-a-client.properties << EOF
security.protocol=SASL_SSL
ssl.truststore.location=/tmp/truststore.p12
ssl.truststore.password=$STOREPASS
ssl.truststore.type=PKCS12
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.ssl.truststore.location="/tmp/truststore.p12" \
oauth.ssl.truststore.password="$STOREPASS" \
oauth.ssl.truststore.type="PKCS12" \
oauth.token.endpoint.uri="https://$SSO_HOST/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 on the Service Account Roles tab for Clients in the Admin Console.
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 topics that start with a_
or x_
.
The first command will result in an error as it tries to write to topic my-topic
:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9093 --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:9093 --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 -n $NS
Consuming messages with authorized access
The team-a-client
configuration can be used to consume messages from topic a_messages
. However, the following command will produce an error:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --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
properties are updated to specify the custom consumer group it is permitted to use:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --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
is an account without any cluster-level access, but it can be used with some administrative operations.
Listing topics returns the a_messages
topic:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --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:9093 --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:9093 --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_SSL
ssl.truststore.location=/tmp/truststore.p12
ssl.truststore.password=$STOREPASS
ssl.truststore.type=PKCS12
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.ssl.truststore.location="/tmp/truststore.p12" \
oauth.ssl.truststore.password="$STOREPASS" \
oauth.ssl.truststore.type="PKCS12" \
oauth.token.endpoint.uri="https://$SSO_HOST/auth/realms/kafka-authz/protocol/openid-connect/token" ;
sasl.login.callback.handler.class=io.strimzi.kafka.oauth.client.JaasClientOauthLoginCallbackHandler
EOF
The team-b-client
Keycloak 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
, and read access on topics that start with x_
.
The team-b-client
configuration is used to produce messages to topics that start with b_
. Writing to topic a_messages
will result in error:
bin/kafka-console-producer.sh --broker-list my-cluster-kafka-bootstrap:9093 --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:9093 --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:9093 --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:9093 --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 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, an 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 alice
is created in Keycloak with full access to manage everything on any Kafka cluster.
We can authenticate as alice
by using curl
and performing a password grant authentication to obtain a refresh token, which we can then use to configure the Kafka client.
USERNAME=alice
PASSWORD=alice-password
GRANT_RESPONSE=$(curl -X POST "https://$SSO_HOST/auth/realms/kafka-authz/protocol/openid-connect/token" -H 'Content-Type: application/x-www-form-urlencoded' -d "grant_type=password&username=$USERNAME&password=$PASSWORD&client_id=kafka-cli&scope=offline_access" -s -k)
REFRESH_TOKEN=$(echo $GRANT_RESPONSE | awk -F "refresh_token\":\"" '{printf $2}' | awk -F "\"" '{printf $1}')
The refresh token in this case is an offline token which is a long-lived refresh token that does not expire.
The configuration file for alice
:
cat > /tmp/alice.properties << EOF
security.protocol=SASL_SSL
ssl.truststore.location=/tmp/truststore.p12
ssl.truststore.password=$STOREPASS
ssl.truststore.type=PKCS12
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.ssl.truststore.location="/tmp/truststore.p12" \
oauth.ssl.truststore.password="$STOREPASS" \
oauth.ssl.truststore.type="PKCS12" \
oauth.token.endpoint.uri="https://$SSO_HOST/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 alice
has permission to create the x_messages
topic:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --command-config /tmp/alice.properties \
--topic x_messages --create --replication-factor 1 --partitions 1
User alice
can list all the topics, whereas team-a-client
and team-b-client
can only list the topics they have access to:
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --command-config /tmp/alice.properties --list
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --command-config /tmp/team-a-client.properties --list
bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --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 because 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:9093 --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:9093 --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:9093 --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:9093 --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:9093 --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 alice
can read from or write to any topic:
bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --topic x_messages \
--from-beginning --consumer.config /tmp/alice.properties
User alice
can also read the cluster configuration (which in this case is empty):
bin/kafka-configs.sh --bootstrap-server my-cluster-kafka-bootstrap:9093 --command-config /tmp/alice.properties \
--entity-type brokers --describe --entity-default