Added SSH Public Key Logic for keycloak

This commit is contained in:
Kevin Veen-Birkenbach 2025-06-27 23:27:59 +02:00
parent b3e82fa457
commit d815b9ee62
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
8 changed files with 155 additions and 29 deletions

View File

@ -21,4 +21,4 @@ galaxy_info:
class: "fa-solid fa-lock"
run_after:
- docker-matomo
dependencies: []
- docker-ldap

View File

@ -0,0 +1,82 @@
- name: "Wait until Keycloak is reachable at {{ keycloak_server_host_url }}"
uri:
url: "{{ keycloak_server_host_url }}/realms/master"
method: GET
status_code: 200
validate_certs: false
register: keycloak_check
retries: 30
delay: 5
until: keycloak_check.status == 200
# Configure Credentials
- name: Ensure Keycloak CLI credentials are configured
shell: |
{{ keycloak_kcadm_path }} config credentials \
--server {{ keycloak_server_internal_url }} \
--realm master \
--user {{ keycloak_administrator_username }} \
--password {{ keycloak_administrator_password }}
# LDAP Source
- name: Get ID of LDAP storage provider
shell: |
{{ keycloak_kcadm_path }} get components \
-r {{ keycloak_realm }} \
--query 'providerId=ldap' \
--fields id,name \
--format json
register: ldap_components
- name: Extract LDAP component ID
set_fact:
ldap_component_id: "{{ (ldap_components.stdout | from_json)[0].id }}"
- name: Ensure {{ ldap.attributes.ssh_public_key }} LDAP Mapper exists
shell: |
docker exec -i keycloak_application bash -c '
/opt/keycloak/bin/kcadm.sh get components -r {{ keycloak_realm }} \
| grep -q "\"name\" : \"{{ ldap.attributes.ssh_public_key }}\"" \
|| printf "%s\n" "{
\"name\": \"{{ ldap.attributes.ssh_public_key }}\",
\"parentId\": \"{{ ldap_component_id }}\",
\"providerId\": \"user-attribute-ldap-mapper\",
\"providerType\": \"org.keycloak.storage.ldap.mappers.LDAPStorageMapper\",
\"config\": {
\"user.model.attribute\": [\"{{ ldap.attributes.ssh_public_key }}\"],
\"ldap.attribute\": [\"{{ ldap.attributes.ssh_public_key }}\"],
\"read.only\": [\"false\"],
\"write.only\": [\"true\"],
\"always.read.value.from.ldap\": [\"false\"],
\"multivalued\": [\"true\"]
}
}" | /opt/keycloak/bin/kcadm.sh create components -r {{ keycloak_realm }} -f -'
register: mapper_create
changed_when: mapper_create.rc == 0 and mapper_create.stdout != ""
# GUI
- name: Enable user profile in realm
shell: >
{{ keycloak_kcadm_path }} update realms/{{ keycloak_realm }}
-s 'attributes.userProfileEnabled=true'
- name: Re-authenticate to Keycloak after enabling user profile
shell: |
{{ keycloak_kcadm_path }} config credentials \
--server {{ keycloak_server_internal_url }} \
--realm master \
--user {{ keycloak_administrator_username }} \
--password {{ keycloak_administrator_password }}
- name: Render user-profile JSON for SSH key
template:
src: import/user-profile.json.j2
dest: "{{ import_directory_host }}/user-profile.json"
mode: '0644'
notify: docker compose up
- name: Apply SSH Public Key to user-profile via kcadm
shell: |
docker exec -i {{ container_name }} \
/opt/keycloak/bin/kcadm.sh update realms/{{ keycloak_realm }} -f {{ import_directory_docker }}user-profile.json

View File

@ -24,4 +24,8 @@
dest: "{{ import_directory_host }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
mode: '770'
loop: "{{ lookup('fileglob', '{{ role_path }}/templates/import/*.j2', wantlist=True) }}"
notify: docker compose up
notify: docker compose up
# Deactivated temporary. Import now via realm.yml
#- name: Implement SSH Public Key Attribut
# include_tasks: attributes/ssh_public_key.yml

View File

@ -8,7 +8,7 @@ services:
command: start {% if applications[application_id].import_realm | bool %}--import-realm{% endif %}
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
ports:
- "127.0.0.1:{{ports.localhost.http[application_id]}}:8080"
- "{{ keycloak_server_host }}:8080"
volumes:
- "{{import_directory_host}}:{{import_directory_docker}}"
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}

View File

@ -10,8 +10,11 @@ KC_HTTP_ENABLED= true
KC_HEALTH_ENABLED= true
KC_METRICS_ENABLED= true
# Administrator
KEYCLOAK_ADMIN= "{{applications[application_id].users.administrator.username}}"
KEYCLOAK_ADMIN_PASSWORD= "{{applications[application_id].credentials.administrator_password}}"
# Database
KC_DB= postgres
KC_DB_URL= {{database_url_jdbc}}
KC_DB_USERNAME= {{database_username}}

View File

@ -1,6 +1,6 @@
{
"id": "3b03105b-5fe6-4b53-ba24-c8796525be0e",
"realm": "{{realm}}",
"realm": "{{ keycloak_realm }}",
"displayName": "",
"displayNameHtml": "",
"notBefore": 0,
@ -60,7 +60,7 @@
},
{
"id": "01d9dd2a-75b2-47a2-af36-b14251f1b956",
"name": "default-roles-{{realm}}",
"name": "default-roles-{{ keycloak_realm }}",
"description": "${role_default-roles}",
"composite": true,
"composites": {
@ -302,7 +302,7 @@
"attributes": {}
}
],
"{{realm}}": [],
"{{ keycloak_realm }}": [],
"security-admin-console": [],
"admin-cli": [],
"account-console": [],
@ -410,7 +410,7 @@
"groups": [],
"defaultRole": {
"id": "01d9dd2a-75b2-47a2-af36-b14251f1b956",
"name": "default-roles-{{realm}}",
"name": "default-roles-{{ keycloak_realm }}",
"description": "${role_default-roles}",
"composite": true,
"clientRole": false,
@ -464,18 +464,18 @@
"users": [
{
"id": "19ecedfd-acf2-49e8-9f66-91ab71d54fc3",
"username": "service-account-{{realm}}",
"username": "service-account-{{ keycloak_realm }}",
"emailVerified": false,
"createdTimestamp": 1737925519602,
"enabled": true,
"totp": false,
"serviceAccountClientId": "{{realm}}",
"serviceAccountClientId": "{{ keycloak_realm }}",
"disableableCredentialTypes": [],
"requiredActions": [
"CONFIGURE_TOTP"
],
"realmRoles": [
"default-roles-{{realm}}"
"default-roles-{{ keycloak_realm }}"
],
"notBefore": 0,
"groups": []
@ -508,13 +508,13 @@
"description": "",
"rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/{{realm}}/account/",
"baseUrl": "/realms/{{ keycloak_realm }}/account/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"/realms/{{realm}}/account/*"
"/realms/{{ keycloak_realm }}/account/*"
],
"webOrigins": [
"{{ web_protocol }}://{{domains | get_domain('keycloak')}}"
@ -564,13 +564,13 @@
"description": "",
"rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/{{realm}}/account/",
"baseUrl": "/realms/{{ keycloak_realm }}/account/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"/realms/{{realm}}/account/*"
"/realms/{{ keycloak_realm }}/account/*"
],
"webOrigins": [
"*"
@ -756,13 +756,13 @@
"clientId": "security-admin-console",
"name": "${client_security-admin-console}",
"rootUrl": "${authAdminUrl}",
"baseUrl": "/admin/{{realm}}/console/",
"baseUrl": "/admin/{{ keycloak_realm }}/console/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"/admin/{{realm}}/console/*"
"/admin/{{ keycloak_realm }}/console/*"
],
"webOrigins": [
"+"
@ -822,12 +822,12 @@
},
{
"id": "7b5f97e3-7fa8-4d86-b1e9-80aac996da26",
"clientId": "{{realm}}",
"clientId": "{{ keycloak_realm }}",
"name": "",
"description": "",
"rootUrl": "{{ web_protocol }}://{{realm}}/",
"adminUrl": "{{ web_protocol }}://{{realm}}/",
"baseUrl": "{{ web_protocol }}://{{realm}}/",
"rootUrl": "{{ web_protocol }}://{{ keycloak_realm }}/",
"adminUrl": "{{ web_protocol }}://{{ keycloak_realm }}/",
"baseUrl": "{{ web_protocol }}://{{ keycloak_realm }}/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
@ -1792,7 +1792,7 @@
"subComponents": {},
"config": {
"kc.user.profile.config": [
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"pattern\":{\"pattern\":\"^[a-z0-9]+$\",\"error-message\":\"\"}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"pattern\":{\"pattern\":\"^[a-z0-9]+$\",\"error-message\":\"\"}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"{{ ldap.attributes.ssh_public_key }}\",\"displayName\":\"SSH Public Key\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"group\":\"user-metadata\",\"multivalued\":true}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
]
}
}
@ -1977,6 +1977,35 @@
]
}
},
{
"id": "24cd9c3b-e22d-4540-bddf-ae7faac0196c",
"name": "SSH Public Key",
"providerId": "user-attribute-ldap-mapper",
"subComponents": {},
"config": {
"ldap.attribute": [
"{{ ldap.attributes.ssh_public_key }}"
],
"is.mandatory.in.ldap": [
"false"
],
"attribute.force.default": [
"false"
],
"is.binary.attribute": [
"false"
],
"read.only": [
"false"
],
"always.read.value.from.ldap": [
"true"
],
"user.model.attribute": [
"{{ ldap.attributes.ssh_public_key }}"
]
}
},
{
"id": "85cd9847-4063-4d8b-be03-fa16377cde56",
"name": "email",

View File

@ -1,6 +1,14 @@
application_id: "keycloak"
database_type: "postgres"
container_name: "{{application_id}}_application"
realm: "{{primary_domain}}" # This is the name of the default realm which is used by the applications
import_directory_host: "{{docker_compose.directories.volumes}}import/" # Directory in which keycloack import files are placed on the host
import_directory_docker: "/opt/keycloak/data/import/" # Directory in which keycloack import files are placed in the running docker container
application_id: "keycloak"
database_type: "postgres"
container_name: "{{application_id}}_application"
import_directory_host: "{{docker_compose.directories.volumes}}import/" # Directory in which keycloack import files are placed on the host
import_directory_docker: "/opt/keycloak/data/import/" # Directory in which keycloack import files are placed in the running docker container
keycloak_realm: "{{ primary_domain}}" # This is the name of the default realm which is used by the applications
keycloak_administrator: "{{ applications[application_id].users.administrator }}" # Master Administrator
keycloak_administrator_username: "{{ keycloak_administrator.username}}" # Master Administrator Username
keycloak_administrator_password: "{{ keycloak_administrator.password}}" # Master Administrator Password
keycloak_kcadm_path: "docker exec -i {{ container_name }} /opt/keycloak/bin/kcadm.sh"
keycloak_server_internal_url: "http://127.0.0.1:8080"
keycloak_server_host: "127.0.0.1:{{ports.localhost.http[application_id]}}"
keycloak_server_host_url: "http://{{ keycloak_server_host }}"

View File

@ -8,7 +8,7 @@
vars:
schema_name: "openssh-lpk"
attribute_defs:
- "( 1.3.6.1.4.1.24552.1.1 NAME 'sshPublicKey' DESC 'OpenSSH Public Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
- "( 1.3.6.1.4.1.24552.1.1 NAME '{{ ldap.attributes.ssh_public_key }}' DESC 'OpenSSH Public Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
- "( 1.3.6.1.4.1.24552.1.2 NAME 'sshFingerprint' DESC 'OpenSSH Public Key Fingerprint' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )"
objectclass_defs:
- >-
@ -17,7 +17,7 @@
DESC 'Auxiliary class for OpenSSH public keys'
SUP top
AUXILIARY
MAY ( sshPublicKey $ sshFingerprint ) )
MAY ( {{ ldap.attributes.ssh_public_key }} $ sshFingerprint ) )
command: >
ldapsm