How I won KeyCloak
Business requirements:
For internal company services, a single sign-on point is required with users connected from the existing Active Directory.
It is required that the user can have access to one or more services (in each of the services he has one or more roles). If there is no access to a particular service, inform him about it.
Requirements for interaction with the CS
KC should work over https.
On the front side the package from KC will be used https://www.npmjs.com/package/keycloak-js.
Ability to send events to Kafka
Sweep Requirements
KC version: 25.0.2, also tested on version 26.0.0 (also normal)
Well, we’ve sorted out the introductions, now let’s get down to implementation.
Build and deploy
Dockerfile
FROM keycloak/keycloak:25.0.2
COPY keycloak-kafka-1.1.5.jar /opt/keycloak/providers/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
To connect Kafka to CK we use keycloak-kafka-1.1.5
Dokcer-compose.yaml
version: "3.9"
services:
keycloak:
image: my_docker_hub/keycloak:latest
volumes:
- ./themes:/opt/keycloak/themes
- ./cert/cert.jks:/etc/x509/https/truststore.jks
container_name: keycloak
ports:
- "8443:8443"
env_file: ./.env
command: start
depends_on:
keycloak-postgres:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
"exec 3<>/dev/tcp/127.0.0.1/9000;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;",
]
start_period: 10s
interval: 30s
retries: 3
timeout: 5s
keycloak-postgres:
container_name: keycloak-postgres
image: postgres
volumes:
- ./db/data:/var/lib/postgresql/data
env_file: ./.env
healthcheck:
test: pg_isready -d postgres
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
.env
KC_FEATURES: preview
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true
KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KAFKA_TOPIC: user.event.user
KAFKA_ADMIN_TOPIC: user.event.admin
KAFKA_CLIENT_ID: keycloak
KAFKA_BOOTSTRAP_SERVERS: BOOTSTRAP_SERVERS
KAFKA_EVENTS: LOGIN,LOGOUT
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak
KC_DB_SCHEMA: public
POSTGRES_DB: keycloak
PGUSER: keycloak
POSTGRES_USER : keycloak
POSTGRES_PASSWORD : keycloak
PGPASSWORD: password
I omit the assembly and deployment process, because… there's nothing interesting there.
Configuring HTTPS (In my case, I have a root cert)
At this point I was really stuck, because… There are tons of variations on the Internet, but unfortunately many are outdated or not suitable for the type of certificate
Copy the root certificate to /opt/keycloak/cert
Execute commands
keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore ./cert.jks -deststoretype pkcs12 cd /opt/keycloak/cert
Specify the password for the cert.pfx certificate and assign a password for the keystore
In docker-compose we pass cert.jks
volumes:
- ./cert/cert.jks:/etc/x509/https/truststore.jks
In the .env file
We set the following variables
KC_HOSTNAME: host_keycloak
KC_HTTPS_PORT: 8443
KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD
KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks
KC_PROXY_HEADERS: xforwarded
After deployment via Docker compose, we can open KC.
https://host_name:8443/
Login as admin / admin
Connecting AD
Creating your own Realm
Go to User federation and create Ldap providers
Then I followed the description in the article
https://habr.com/ru/companies/swordfish_security/articles/533264/
Thank you guys and Swordfish Security for the article.
As usual, when I took on the KC task, I wanted to spend another day on blogs and forums looking for a solution/guide, as everything is already written here 🙂
Connecting Kafka
Go to Realm settings, go to Events, select kafka
Now all events generated in the admin will be sent to the topic specified in .env admin, and user events login/logout to the user topic
Client setup
Creating a Client
After creation, in the settings, be sure to indicate the allowed URLs from which we can access, etc.
We create a client scope audience to add the client’s audience to the token.
Go to the Mappers tab, click on Configure a new mapper
Name – specify as convenient
Included Client Audience – select our client
Save.
Adding our scope to our client
Go to the Clients menu – Client scopes tab – Add client scope
Select our scope and add it with the Default attribute
Creating an access role for our client
Menu – Realm roles – Create role
Creating a user group to access our client
Menu – Groups – Create group
I prefer to set the name for access groups = client id
We associate the access role with our group on the Role mapping tab
Creating a Browser Authentication Flow for our Client
Menu – authentication – Flows – duplicate browser
Next we form the following structure
It’s strange, but I didn’t think that the situation that I needed to resolve was so rare and there was very little information on this topic on the Internet (well, very little). I found the solution on stackoverflow, which was presented in the form of a screenshot, which was eventually modified 🙂
Flow - Required: {
name: Login: <Название клиента>
}
Step - Alternative: Cookie
Step - Alternative: Identity Provider Redirector config
Flow - Alternative: {
name: gated browser form: <Название клиента>
}
Step - Required: Username Form
Flow - Conditional: {
name: gated browser form - Conditional OTP Form config: <Название клиента>
}
Condition - Required: Condition - user configured
Step - Required: OTP Form
Flow: {
name: RBA - Conditiona: <Название клиента>
}
Condition - Required: Condition - user role
{
Alias: user role <Название клиента>,
User role: Выбираем роль созданную для нашего Client,
Negate output: On
}
Step - Required: Deny access
{
Alias: Deny access config <Название клиента>,
Error message: Доступ в приложение <Название приложения> запрещен
}
Next, we indicate our created Flow as the main one for the Client
If we now try to authenticate in KC through the keycloak-js adapter, we will get the following result
Adding a user to the my-client access group
Menu – members – Add member – select a user from the local database or imported from AD
Next, we try to pass authorization through the keycloak-js adapter, we get the result “Successful authorization” with receiving a token and a redirect to the page specified in the Client settings.
Bottom line
In this article, we looked at the implementation of authorization in our application through the popular SSO software KeyCloak. I hope it was written exhaustively, because… I experienced a lot of pain while setting everything up and maybe this will help someone.
Thanks for reading.