So in this post let me show how I solved the installation of Keycloak on a bare-metal (VM) server. I have a couple stuff in my homelab like Jenkins, Sonar, Nexus OSS, etc where I want to use and I have some projects that need SSO.
I picked Keycloak because it integrates well with Kubernetes / OpenShift and this fact is important to me, because I work a lot with these systems.
Install the Keycloak server
You can download the binary from the GitHub Release page of the project. I created an ansible playbook to perform the install. It was a good practice and I use Fedora servers in my homelab and if a new major version come out I can easily reinstall my services in the future.
site.yaml
So I created the following site.yaml
:
- name: Install Keycloak
hosts: keycloak
vars:
keycloak_version: 26.1.1
keycloak_db_password: xxx
become: true
become_user: root
become_method: sudo
gather_facts: yes
roles:
- { role: keycloak }
Of course the keycloak_db_password
was hashed, I used the ansible-vault
command to generate the sercure form of the password.
ansible-vault encrypt_string 'secret_password' --name 'dbPassword'
This command gave me an output that I copied into the site.yaml
file. Simple as that :).
Keycloak configuration
Database configuration
Because it is in my homelab environment I didn't overthink the database configuration and security settings for now. I SSH into the database server, started the psql
and created the database for the Keycloak with the following statements:
-- Create user for db connection
CREATE USER keycloak_db WITH PASSWORD '1234';
-- Create database
CREATE DATABASE keycloak OWNER keycloak_db ENCODING 'UTF8';
-- stepped into the database with \c keycloak
CREATE SCHEMA prod;
-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak_db;
GRANT ALL PRIVILEGES ON SCHEMA prod TO keycloak_db;
In the following block you can see the configuration which is used by the Keycloak. There are some database configuration that points to one of my other local VM that runs... surprise: PostgreSQL.
# Database configuration
db=postgres
db-driver=org.postgresql.Driver
db-password={{ keycloak_db_password }}
db-schema=prod
db-url-database=keycloak
db-url-host=postgres.home
db-url-port=5432
db-username=keycloak_db
# Hostname
hostname=keycloak.home
# HTTP config
https-certificate-file=/etc/ssl/certs/keycloak/server.crt
https-certificate-key-file=/etc/ssl/certs/keycloak/server.key
https-port=443
# Health and metrics
health-enabled=true
metrics-enabled=true
# Logging
log=console,file
# Tracing
tracing-enabled=true
As you can see above I configured the hostname
property. It's quite important because of the redirections and other security stuffs that I don't know yet and the Keycloak does behind the scenes. If you are a Keycloak expert please let me know why got so big role this setting. After that comes the TLS certificates and the HTTPS port (which is 8443 by default). I set the standard 443 because I'm lazy, I did not want write always the port number. It's important to mention no service should be run in the name of the root
user. I also did not run the Keycloak as root
, I created a user and group for this purpose, but in this case you have to enable the use of privileged ports.
Systemd unit
Nothing interesting here. I want to run Keycloak as a service so I wrote the Systemd unit file below. I used the AmbientCapabilities=CAP_NET_BIND_SERVICE
setting to enable the use of privileged ports.
[Unit]
Description=Keycloak Identity and Access Management
After=network.target
Wants=network-online.target
[Service]
User=keycloak
Group=keycloak
WorkingDirectory=/opt/keycloak
ExecStart=/opt/keycloak/bin/kc.sh start
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
TimeoutStopSec=10
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Install role
The essence of the ansible playbook is below:
- name: Install keycloak
block:
- name: Check if there's any Keycloak installation
ansible.builtin.shell: "test -L /opt/keycloak && echo 'installed' || echo 'not_installed'"
register: is_installed
- ansible.builtin.debug:
msg: "Keycloak is not installed"
when: is_installed.stdout == 'not_installed'
- name: Get installed version
ansible.builtin.shell: readlink -f /opt/keycloak | sed -E 's/.*keycloak-([0-9]+\.[0-9]+\.[0-9]+\-[0-9]+)/\1/'
args:
chdir: /opt
ignore_errors: true
register: current_version
when: is_installed.stdout == 'installed'
- ansible.builtin.debug:
msg: "Keycloak is installed. Version: {{ current_version.stdout }}"
when: is_installed.stdout == 'installed'
- name: Perform installation
when: is_installed.stdout == 'not_installed' or current_version.stdout != keycloak_version
block:
# Create non-root group and user to run the service
- name: Create 'keycloak' group
ansible.builtin.group:
name: keycloak
state: present
- name: Create 'keycloak' user
ansible.builtin.user:
name: keycloak
group: keycloak
state: present
# Install prerequisites to run Keycloak and connect to DB
- name: Install OpenJDK 21
ansible.builtin.dnf:
name:
- java-21-openjdk
- postgresql-jdbc
state: latest
# Stop the service if it's already installed
- name: Stop Keycloak service
ansible.builtin.systemd_service:
name: keycloak
state: stopped
ignore_errors: true
- name: Create temporary download directory
ansible.builtin.file:
path: /tmp/keycloak/
state: directory
- name: Download Keycloak binary
ansible.builtin.get_url:
url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_version }}/keycloak-{{ keycloak_version }}.tar.gz"
dest: /tmp/keycloak/
checksum: sha1:a6faa5d97eb349a1898aa7ab81b2f9fc2778bde5
- name: Get the tarball path
ansible.builtin.find:
paths: /tmp/keycloak/
file_type: file
recurse: no
register: tmp_files
- name: Unarchive Keycloak tarball
ansible.builtin.unarchive:
src: "{{ tmp_files.files[0].path }}"
dest: /tmp/keycloak/
remote_src: true
- name: Install Keycloak
ansible.builtin.copy:
src: "/tmp/keycloak/keycloak-{{ keycloak_version }}"
dest: /opt
remote_src: true
# I did not turn the SELinux into permissive or disabled mode, so I have to deal with this also.
- name: Set SELinux type
ansible.builtin.file:
path: "/opt/keycloak-{{ keycloak_version }}"
owner: keycloak
group: keycloak
setype: usr_t
recurse: true
- name: Create symlink to Keycloak directory
ansible.builtin.file:
src: "/opt/keycloak-{{ keycloak_version }}"
dest: /opt/keycloak
state: link
- name: Add configuration file
ansible.builtin.template:
src: keycloak.conf.j2
dest: /opt/keycloak/conf/keycloak.conf
group: keycloak
owner: keycloak
backup: true
- name: Add Keycloak systemd unit file
ansible.builtin.copy:
src: keycloak.service
dest: /etc/systemd/system/
# Open 443 port on the firewall
- name: Enable network connection, HTTP and HTTPS services
ansible.builtin.shell: |
setsebool httpd_can_network_connect 1
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
# Do some revert work if needed
rescue:
- name: "Remove {{ keycloak_version }} form /opt"
ansible.builtin.file:
path: "/opt/keycloak-{{ keycloak_version }}/"
state: absent
- name: Create symlink to original Keycloak directory
ansible.builtin.file:
src: "/opt/keycloak-{{ current_version.stdout }}/"
dest: /opt/keycloak
state: link
when: is_installed.stdout == 'installed'
# Alwazs try to restart the service and delete temporary files
always:
- name: Start Keycloak service
ansible.builtin.systemd_service:
name: keycloak
daemon_reload: true
state: started
enabled: true
- name: Delete temporary files
ansible.builtin.file:
path: /tmp/keycloak/
state: absent
I tried to put some error handling logic into this role so if some error happens it rolls back for the previous Keycloak version. After the playbook had finished successfully I was able to open the web UI.
First login
When you open first time the Keycloak UI it tells you that you should go in some localhost
page. Okay, but I installed it on a headless VM. Fortunately we have a tool called SSH. With the following command I opened a tunnel and mapped the Keycloak server port onto my dev machine:
ssh -L 8443:127.0.0.1:443 myUser@keycloak
With this trick I was able to create the first temporary user with admin rights and use the Keycloak UI through the supposed URL.
Create administrator user
After you first logged in with the temporary admin user Keycloak warns you to create a permanent admin user and delete the temporary one.
I'm sure I have skill issues, but I was not able to create this permanent admin user from the UI, so went back to CLI. With the following command I created a session with the temp. admin.
./kcadm.sh config credentials --server https://keycloak.home:443 --realm master --user tmpadmin
Then created a permanent user who called superadmin:
./kcadm.sh create users -r master -s username=superadmin -s enabled=true -o --fields id,username
Granted administrative permissions to the newly created user:
./kcadm.sh add-roles --rolename=admin --uusername=superadmin -r master
And after all one more strep. Set the password for this user:
./kcadm.sh set-password --username=superadmin --new-password='Almafa1234'
Tadaaa. We can log in the admin console and create many interesting things.
Top comments (0)