Initial import; migrate some roles from irl.wip

This commit is contained in:
Iain Learmonth 2025-10-31 22:36:32 +00:00
commit 2ba6c6691b
44 changed files with 1573 additions and 0 deletions

View file

@ -0,0 +1,17 @@
---
podman_keycloak_certbot_testing: false
podman_keycloak_enable_ldap: true
# podman_keycloak_keycloak_admin_password:
podman_keycloak_keycloak_admin_username: admin
podman_keycloak_keycloak_hostname: "{{ inventory_hostname }}"
podman_keycloak_keycloak_providers: []
# - url: https://github.com/jacekkow/keycloak-protocol-cas/releases/download/26.4.1/keycloak-protocol-cas-26.4.1.jar
# sha256: 7692526943063434443411b2d0fac63fb4e46f89b20fb07bb45c360916407367
# podman_keycloak_ldap_administrator_password:
# podman_keycloak_ldap_directory_manager_password:
# podman_keycloak_ldap_database_suffix_dn:
podman_keycloak_podman_rootless_user: keycloak
podman_keycloak_postgres_keycloak_database: keycloak
# podman_keycloak_postgres_keycloak_password:
podman_keycloak_postgres_keycloak_username: keycloak
podman_keycloak_keycloak_additional_volumes: []

View file

@ -0,0 +1,27 @@
---
- name: Restart ldap
ansible.builtin.systemd_service:
name: ldap
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
- name: Restart postgres
ansible.builtin.systemd_service:
name: postgres
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
- name: Restart keycloak
ansible.builtin.systemd_service:
name: keycloak
state: restarted
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"

View file

@ -0,0 +1,115 @@
---
- name: wait 30 seconds for ldap server to start
ansible.builtin.pause:
seconds: 30
- name: create ldap suffix
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- backend
- create
- --suffix
- "{{ podman_keycloak_ldap_database_suffix_dn }}"
- --be-name
- "{{ podman_keycloak_ldap_database_backend_name }}"
- --create-suffix
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
register: podman_keycloak_create_suffix
ignore_errors: true
changed_when: false
tags:
- ldap
- name: create suffix result (only when changed)
debug:
msg: "Suffix was created"
when: not podman_keycloak_create_suffix.failed
changed_when: not podman_keycloak_create_suffix.failed
- name: ldap organisational units
community.general.ldap_entry:
dn: "ou={{ item }},{{ podman_keycloak_ldap_database_suffix_dn }}"
objectClass:
- top
- organizationalUnit
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_keycloak_ldap_directory_manager_password }}"
delegate_to: localhost
with_items:
- Administrators
- People
- Groups
environment:
- LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap
- name: enable memberOf plugin
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- -D "cn=Directory Manager"
- plugin
- memberof
- enable
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
tags:
- ldap
- name: disable anonymous bind
containers.podman.podman_container_exec:
name: ldap
argv:
- dsconf
- -v
- localhost
- -D "cn=Directory Manager"
- config
- replace
- nsslapd-allow-anonymous-access=off
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
tags:
- ldap
- name: ldap read-only administrator
community.general.ldap_entry:
dn: "uid=admin,ou=Administrators,{{ podman_keycloak_ldap_database_suffix_dn }}"
objectClass:
- top
- person
- organizationalPerson
- inetOrgPerson
attributes:
cn: admin
sn: admin
userPassword: "{{ podman_keycloak_ldap_administrator_password }}"
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_keycloak_ldap_directory_manager_password }}"
delegate_to: localhost
environment:
- LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap
- name: ldap access control information
community.general.ldap_attrs:
dn: "{{ podman_keycloak_ldap_database_suffix_dn }}"
attributes:
aci: '(target="ldap:///{{ podman_keycloak_ldap_database_suffix_dn }}")(targetattr="*") (version 3.0; acl "readonly"; allow (search,read,compare) userdn="ldap:///uid=admin,ou=Administrators,{{ podman_keycloak_ldap_database_suffix_dn }}";)'
server_uri: ldaps://{{ inventory_hostname }}/
bind_dn: "cn=Directory Manager"
bind_pw: "{{ podman_keycloak_ldap_directory_manager_password }}"
delegate_to: localhost
environment:
- LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}"
tags: ldap

View file

@ -0,0 +1,160 @@
---
- name: Podman Keycloak | PATCH | Install podman and create rootless podman user
ansible.builtin.include_role:
role: sr2c.core.podman_host
vars:
podman_host_minimum_unpriv_port: 80
podman_host_rootless_users: ["keycloak"]
- name: Podman Keycloak | PATCH | Enable http service with firewalld
ansible.posix.firewalld:
service: http
state: enabled
immediate: true
permanent: true
zone: public
- name: Podman Keycloak | PATCH | Enable https service with firewalld
ansible.posix.firewalld:
service: https
state: enabled
immediate: true
permanent: true
zone: public
# TODO: These will be relabelled by podman but in the future we should label them from the start
- name: Podman Keycloak | PATCH | Create service configuration directories
ansible.builtin.file:
path: "/home/{{ podman_keycloak_podman_rootless_user }}/{{ item }}"
state: directory
owner: "{{ podman_keycloak_podman_rootless_user }}"
group: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0755"
become: true
with_items:
- keycloak
- ldap
- postgres
when: (item != 'ldap') or podman_keycloak_enable_ldap
- name: Podman Keycloak | PATCH | Download keycloak providers
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/keycloak/{{ item.url | basename }}"
checksum: "sha256:{{ item.sha256 }}"
with_items: "{{ podman_keycloak_keycloak_providers }}"
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
notify: restart keycloak
- name: Podman Keycloak | PATCH | Install systemd target
ansible.builtin.template:
src: "keycloak.target"
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/.config/systemd/user/keycloak.target"
owner: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0400"
- name: Podman Keycloak | PATCH | Install systemd slice
ansible.builtin.template:
src: "keycloak.slice"
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/.config/systemd/user/keycloak.slice"
owner: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0400"
- name: Podman Keycloak | PATCH | Install container quadlets
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0400"
with_items:
- ldap.container
- keycloak.container
- postgres.container
when: (item != 'ldap.container') or podman_keycloak_enable_ldap
notify:
- "Restart {{ item | split('.') | first }}"
become: true
- name: Podman Keycloak | PATCH | Install network quadlets
ansible.builtin.template:
src: "{{ item }}"
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/.config/containers/systemd/{{ item }}"
owner: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0400"
with_items:
- frontend.network
- ldap.network
- keycloak.network
when: (item != 'ldap.network') or podman_keycloak_enable_ldap
become: true
- name: Podman Keycloak | AUDIT | Verify quadlets are correctly defined
ansible.builtin.command: /usr/libexec/podman/quadlet -dryrun -user
register: podman_keycloak_quadlet_result
ignore_errors: true
changed_when: false
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
- name: Podman Keycloak | AUDIT | Assert that the quadlet verification succeeded
ansible.builtin.assert:
that:
- podman_keycloak_quadlet_result.rc == 0
fail_msg: "'/usr/libexec/podman/quadlet -dryrun -user' failed! Output withheld to prevent leaking secrets."
- name: Podman Keycloak | PATCH | Start PostgreSQL and keycloak containers
ansible.builtin.systemd_service:
name: "{{ item }}"
state: started
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
with_items:
- postgres
- keycloak
- name: Podman Keycloak | PATCH | Configure nginx container
ansible.builtin.include_role:
name: sr2c.core.podman_nginx
vars:
podman_nginx_podman_rootless_user: "{{ podman_keycloak_podman_rootless_user }}"
podman_nginx_primary_hostname: "{{ podman_keycloak_keycloak_hostname }}"
podman_nginx_frontend_network: frontend
podman_nginx_systemd_service_slice: keycloak.slice
podman_nginx_systemd_service_target: keycloak.target
- name: Podman Keycloak | PATCH | Start LDAP container
ansible.builtin.systemd_service:
name: ldap
state: started
scope: user
when: podman_keycloak_enable_ldap
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"
- name: Podman Keycloak | PATCH | Create nginx configuration file
ansible.builtin.template:
src: nginx.conf
dest: "/home/{{ podman_keycloak_podman_rootless_user }}/nginx/nginx.conf"
owner: "{{ podman_keycloak_podman_rootless_user }}"
group: "{{ podman_keycloak_podman_rootless_user }}"
mode: "0644"
become: true
notify: restart nginx
- name: Podman Keycloak | PATCH | Configure the LDAP directory
ansible.builtin.include_tasks:
file: ldap.yml
when: podman_keycloak_enable_ldap
- name: Podman Keycloak | PATCH | Enable keycloak.target
ansible.builtin.systemd_service:
name: keycloak.target
state: started
enabled: true
scope: user
daemon_reload: true
become: true
become_user: "{{ podman_keycloak_podman_rootless_user }}"

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=keycloak.target

View file

@ -0,0 +1,40 @@
[Unit]
Requires=postgres.service
After=postgres.service
PartOf=keycloak.target
[Container]
AutoUpdate=registry
ContainerName=keycloak
Environment=KC_LOG_LEVEL=info
Environment=KC_DB=postgres
Environment=KC_DB_PASSWORD={{ podman_keycloak_postgres_keycloak_password }}
Environment=KC_DB_URL=jdbc:postgresql://postgres/{{ podman_keycloak_postgres_keycloak_database }}
Environment=KC_DB_USERNAME={{ podman_keycloak_postgres_keycloak_username }}
Environment=KC_HOSTNAME={{ podman_keycloak_keycloak_hostname }}
Environment=KC_HTTP_ENABLED=true
Environment=KC_HTTP_PORT=8080
Environment=KC_PROXY_HEADERS=xforwarded
Environment=KC_BOOTSTRAP_ADMIN_USERNAME={{ podman_keycloak_keycloak_admin_username }}
Environment=KC_BOOTSTRAP_ADMIN_PASSWORD={{ podman_keycloak_keycloak_admin_password }}
Environment=PROXY_ADDRESS_FORWARDING=true
Exec=start --features=quick-theme
Image=quay.io/keycloak/keycloak:26.4
Network=keycloak.network
{% if podman_keycloak_enable_ldap %}
Network=ldap.network
{% endif %}
Network=frontend.network
{% for provider in podman_keycloak_keycloak_providers %}
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/keycloak/{{ provider.url | basename }}:/opt/keycloak/providers/{{ provider.url | basename }}:ro,z
{% endfor %}
{% for item in podman_keycloak_keycloak_additional_volumes %}
Volume={{ item.src }}:{{ item.dest }}:{{ item.options }}
{% endfor %}
[Service]
Slice=keycloak.slice
Restart=always
[Install]
WantedBy=keycloak.target

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=keycloak.target

View file

@ -0,0 +1,2 @@
[Unit]
Description=Podman Keycloak Stack by SR2 Communications

View file

@ -0,0 +1,10 @@
[Unit]
Description=Podman Keycloak Stack by SR2 Communications
Requires=keycloak.service
{% if podman_keycloak_enable_ldap %}
Requires=ldap.service
{% endif %}
Requires=nginx.service
[Install]
WantedBy=default.target

View file

@ -0,0 +1,22 @@
[Unit]
PartOf=keycloak.target
[Container]
ContainerName=ldap
Environment=DS_DM_PASSWORD={{ podman_keycloak_ldap_directory_manager_password }}
Image=quay.io/389ds/dirsrv:latest
Network=ldap.network
PublishPort=636:3636/tcp
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/ldap:/data:rw,Z
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/certbot/conf/live/{{ podman_keycloak_keycloak_hostname }}/privkey.pem:/data/tls/server.key:ro,z
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/certbot/conf/live/{{ podman_keycloak_keycloak_hostname }}/cert.pem:/data/tls/server.crt:ro,z
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/certbot/conf/live/{{ podman_keycloak_keycloak_hostname }}/chain.pem:/data/tls/ca/chain.crt:ro,z
[Service]
Slice=keycloak.slice
Restart=always
# RuntimeMaxSec is used to restart the service periodically to pick up new Let's Encrypt certificates
RuntimeMaxSec=604800
[Install]
WantedBy=keycloak.target

View file

@ -0,0 +1,5 @@
[Network]
Driver=bridge
[Install]
WantedBy=keycloak.target

View file

@ -0,0 +1,39 @@
# {{ ansible_managed }}
server {
listen 80;
listen [::]:80;
server_name {{ podman_keycloak_keycloak_hostname }};
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://{{ podman_keycloak_keycloak_hostname }}$request_uri;
}
}
server {
listen 443 default_server ssl;
listen [::]:443 ssl;
http2 on;
server_name {{ podman_keycloak_keycloak_hostname }};
server_tokens off;
ssl_certificate /etc/letsencrypt/live/{{ podman_keycloak_keycloak_hostname }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ podman_keycloak_keycloak_hostname }}/privkey.pem;
location / {
proxy_pass http://keycloak:8080/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 443;
}
}

View file

@ -0,0 +1,21 @@
[Unit]
PartOf=keycloak.target
[Container]
AutoUpdate=registry
ContainerName=postgres
Environment=POSTGRES_DB={{ podman_keycloak_postgres_keycloak_database }}
Environment=POSTGRES_PASSWORD={{ podman_keycloak_postgres_keycloak_password }}
Environment=POSTGRES_USER={{ podman_keycloak_postgres_keycloak_username }}
Environment=POSTGRES_HOST_AUTH_METHOD=scram-sha-256
Environment=POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
Image=docker.io/postgres:17.3
Network=keycloak.network
Volume=/home/{{ podman_keycloak_podman_rootless_user }}/postgres:/var/lib/postgresql/data:rw,Z
[Service]
Slice=keycloak.slice
Restart=always
[Install]
WantedBy=keycloak.target