Deploying Keycloak via Ansible

Keystone needs to work with multiple federation sources. Keycloak is a JBoss based project that provides, among other things, SAML and OpenID connect protocols. As part of my work in getting the two integrated, I needed to deploy Keycloak. The rest of my development setup is done via Ansible and I wanted to handle Keycloak the same way.

Unlike Ipsilon, Keycloak is not deployed via RPMs and Yum. Instead, the most common deployment method is to download and expand the tarball. This provides a great deal of flexibility to the deployer. While I am not going for a full live-deployment approach here, I did want to use best practices. Here were the decisions I made:

  • Use the System deployed Java runtime
  • Run as a non-root dedicated user named keycloak.
  • Manage the process via systemd
  • Put the majority of the files under /var/run/keycloak.
  • Have all code and configuration owned by root and not be editable by the Keycloak user
  • Use firewalld to open only the ports necessary (8080 and 9990) to communicate with the Keycloak server itself.

Here is the roles/keycloak/tasks/main.yml file that has the majority of the logic:

 

---
- name: install keycloak prerequisites
  tags:
    - keycloak
  yum: name={{ item }} state=present
  with_items:
    - java-1.7.0-openjdk.x86_64
    - firewalld

- name: create keycloak user
  tags:
  - keycloak
  user: name=keycloak

- name: keycloak target directory
  tags:
  - keycloak
  file: dest={{ keycloak_dir }}
        mode=755
        owner=root
        group=root
        state=directory


- name: get Keycloak distribution tarball
  tags:
    - keycloak
  get_url: url={{ keycloak_url }}
           dest={{ keycloak_dir }}

- name: unpack keycloak
  tags:
    - keycloak
  unarchive: src={{ keycloak_dir }}/{{keycloak_archive}}
             dest={{ keycloak_dir }}
             copy=no

- name: keycloak log directory
  tags:
  - keycloak
  file: dest={{ keycloak_log_dir }}
        mode=755
        owner=keycloak
        group=keycloak
        state=directory

- name: keycloak data directory
  tags:
  - keycloak
  file: dest={{ keycloak_jboss_home }}/standalone/data
        mode=755
        owner=keycloak
        group=keycloak
        state=directory


- name: keycloak tmp directory
  tags:
  - keycloak
  file: dest={{ keycloak_jboss_home }}/standalone/tmp
        mode=755
        owner=keycloak
        group=keycloak
        state=directory

- name: make keycloak configuration directory readable
  tags:
  - keycloak
  file: dest={{ keycloak_jboss_home }}/standalone/configuration
        mode=755
        owner=keycloak
        group=keycloak
        state=directory
        recurse=yes

- name: keycloak systemd setup
  tags:
    - keycloak
  template:
       owner=root group=root mode=0644
      src=keycloak.service.j2
      dest=/etc/systemd/system/keycloak.service
  notify:
    - reload systemd

- name: enable firewalld
  tags:
    - ipaserver
  service: enabled=yes
           state=started
           name=firewalld

- name: Open Firewall for services
  tags:
    - keycloak
  firewalld: port={{ item }}
             permanent=true
             state=enabled
             immediate=yes
  with_items:
    - 8080/tcp
    - 9990/tcp

- name: keycloak systemd service enable and start
  tags:
    - keycloak
  service: name=keycloak
           enabled=yes
           state=started

It makes use of some variables that I expect to have to tweak as package versions increase. Here is the roles/keycloak/vars/main.yml file

---
keycloak_version: 1.6.1.Final
keycloak_dir: /var/lib/keycloak
keycloak_archive: keycloak-{{ keycloak_version }}.tar.gz
keycloak_url: http://downloads.jboss.org/keycloak/{{ keycloak_version }}/{{keycloak_archive }}
keycloak_jboss_home: "{{ keycloak_dir }}/keycloak-{{ keycloak_version }}"
keycloak_log_dir: "{{ keycloak_jboss_home }}/standalone/log"

For Systemd I started with the configuration as suggested by: Jens Krämer Which I tailored to reference Keycloak explicitly and also to listen on 0.0.0.0. Here is the template file roles/keycloak/templates/keycloak.service.j2

[Unit]
Description=Jboss Application Server
After=network.target

[Service]
Type=idle
Environment=JBOSS_HOME={{ keycloak_jboss_home }} JBOSS_LOG_DIR={{ keycloak_log_dir }} "JAVA_OPTS=-Xms1024m -Xmx20480m -XX:MaxPermSize=768m"
User=keycloak
Group=keycloak
ExecStart={{ keycloak_jboss_home }}/bin/standalone.sh -b 0.0.0.0
TimeoutStartSec=600
TimeoutStopSec=600

[Install]
WantedBy=multi-user.target

The top level playbook for this is somewhat muddied by having other roles, not relevant for this post. It looks like this:

- hosts: keycloak
  remote_user: "{{ cloud_user }}"
  tags: all
  tasks: []

- hosts: keycloak
  sudo: yes
  remote_user: "{{ cloud_user }}"
  tags:
    - ipa
  roles:
    - common
    - ipaclient
    - keycloak
  vars:
    hostname: "{{ ansible_fqdn }}"
    ipa_admin_password: "{{ ipa_admin_user_password }}"

And I call it is using:

 ansible-playbook -i ~/.ossipee/deployments/ayoung.os1/inventory.ini keycloak.yml

I’m tempted to split this code off into its own repository; right now I have it as part of Rippowam.

4 thoughts on “Deploying Keycloak via Ansible

  1. For security I would separate the user that is running the service from the owner of the application files. So I prefer having everything owned by root, only give read-access to group keycloak (exception to some files/paths Keycloak requires write-access to).

  2. keyclock != keycloak
    tempaltes != templates
    keycloak.service.js != keycloak.service.j2

Leave a Reply

Your email address will not be published. Required fields are marked *