---
- name: "π Resolve Keycloak hosting node + container id for OpenTalk provisioning"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: keycloak
resolve_service_name: "{{ lookup('container_service', 'web-app-keycloak', 'keycloak') }}"
resolve_output_prefix: OPENTALK_KEYCLOAK
resolve_compose_cid: "{{ OPENTALK_KEYCLOAK_CONTAINER_ADDRESS }}"
resolve_converge_retries: 60
resolve_converge_delay: 5
- name: "β³ Wait for the Keycloak container to be healthy before OpenTalk provisioning"
ansible.builtin.shell: >
container inspect --type container --format '{{ "{{" }}.State.Health.Status{{ "}}" }}'
{{ OPENTALK_KEYCLOAK_LOCAL_CID | quote }}
register: opentalk_kc_health
retries: 60
delay: 5
until: (opentalk_kc_health.stdout | trim) == 'healthy'
changed_when: false
delegate_to: "{{ OPENTALK_KEYCLOAK_HOST_NODE }}"
- name: "β
Authenticate kcadm against master realm for OpenTalk provisioning"
ansible.builtin.shell: |-
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
set -e
{{ KEYCLOAK_KCADM }} config credentials \
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
--realm master \
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
'
vars:
KEYCLOAK_EXEC_CONTAINER: "container exec -i {{ OPENTALK_KEYCLOAK_LOCAL_CID | quote }}" # nocheck: container-exec-resolver OPENTALK_KEYCLOAK_LOCAL_CID resolved at delegate_to runtime
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
KEYCLOAK_SERVER_INTERNAL_URL: "http://127.0.0.1:8080"
register: opentalk_kcadm_login
retries: 6
delay: 10
until: opentalk_kcadm_login.rc == 0
delegate_to: "{{ OPENTALK_KEYCLOAK_HOST_NODE }}"
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π Upsert dedicated confidential Keycloak client for the OpenTalk controller"
keycloak_kcadm_update:
object_kind: "client"
lookup_value: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
desired:
clientId: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
name: "OpenTalk Controller"
description: "Confidential client used by web-app-opentalk for token introspection and Keycloak admin API user search."
enabled: true
protocol: "openid-connect"
publicClient: false
bearerOnly: false
standardFlowEnabled: false
implicitFlowEnabled: false
directAccessGrantsEnabled: false
serviceAccountsEnabled: true
clientAuthenticatorType: "client-secret"
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
attributes:
"client.secret.creation.time": "0"
"oauth2.device.authorization.grant.enabled": "false"
"use.refresh.tokens": "true"
force_attrs:
publicClient: false
serviceAccountsEnabled: true
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
kcadm_exec: "container exec -i {{ OPENTALK_KEYCLOAK_LOCAL_CID }} /opt/keycloak/bin/kcadm.sh" # nocheck: container-exec-resolver OPENTALK_KEYCLOAK_LOCAL_CID resolved at delegate_to runtime
realm: "{{ OIDC.CLIENT.REALM }}"
assert_mode: "{{ MODE_ASSERT | bool }}"
delegate_to: "{{ OPENTALK_KEYCLOAK_HOST_NODE }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π Upsert dedicated public Keycloak client for the OpenTalk frontend"
vars:
opentalk_frontend_origin: "{{ lookup('tls', application_id, 'url.origin') }}"
keycloak_kcadm_update:
object_kind: "client"
lookup_value: "{{ OPENTALK_FRONTEND_OIDC_CLIENT_ID }}"
desired:
clientId: "{{ OPENTALK_FRONTEND_OIDC_CLIENT_ID }}"
name: "OpenTalk Frontend"
description: "Public SPA client used by web-app-opentalk; its access tokens carry the controller client as audience."
enabled: true
protocol: "openid-connect"
publicClient: true
bearerOnly: false
standardFlowEnabled: true
implicitFlowEnabled: false
directAccessGrantsEnabled: false
serviceAccountsEnabled: false
frontchannelLogout: false
redirectUris:
- "{{ opentalk_frontend_origin }}/*"
webOrigins:
- "{{ opentalk_frontend_origin }}"
attributes:
"post.logout.redirect.uris": "{{ opentalk_frontend_origin }}/*"
"oauth2.device.authorization.grant.enabled": "false"
protocolMappers:
- name: "opentalk-controller-audience"
protocol: "openid-connect"
protocolMapper: "oidc-audience-mapper"
consentRequired: false
config:
"included.client.audience": "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
"id.token.claim": "false"
"access.token.claim": "true"
"introspection.token.claim": "true"
defaultClientScopes:
- "web-origins"
- "acr"
- "roles"
- "profile"
- "basic"
- "email"
- "openid"
- "{{ RBAC.GROUP.CLAIM }}"
kcadm_exec: "container exec -i {{ OPENTALK_KEYCLOAK_LOCAL_CID }} /opt/keycloak/bin/kcadm.sh" # nocheck: container-exec-resolver OPENTALK_KEYCLOAK_LOCAL_CID resolved at delegate_to runtime
realm: "{{ OIDC.CLIENT.REALM }}"
assert_mode: "{{ MODE_ASSERT | bool }}"
register: opentalk_frontend_client
changed_when: not opentalk_frontend_client.object_exists
delegate_to: "{{ OPENTALK_KEYCLOAK_HOST_NODE }}"
- name: "π Grant OpenTalk controller service account `view-users` role"
ansible.builtin.shell: |-
{{ KCEXEC }} sh -lc '
set -e
KCADM={{ KEYCLOAK_KCADM }}
REALM={{ OIDC.CLIENT.REALM | quote }}
CLIENT_ID={{ OPENTALK_CTRL_OIDC_CLIENT_ID | quote }}
SA_USER_ID=$($KCADM get clients/$($KCADM get clients -r $REALM -q clientId=$CLIENT_ID --fields id --format csv --noquotes | tail -n1)/service-account-user -r $REALM --fields id --format csv --noquotes | tail -n1)
RM_CLIENT_ID=$($KCADM get clients -r $REALM -q clientId=realm-management --fields id --format csv --noquotes | tail -n1)
$KCADM add-roles -r $REALM --uusername service-account-$CLIENT_ID --cclientid realm-management --rolename view-users --rolename query-users || true
'
vars:
KCEXEC: "container exec -i {{ OPENTALK_KEYCLOAK_LOCAL_CID | quote }}" # nocheck: container-exec-resolver OPENTALK_KEYCLOAK_LOCAL_CID resolved at delegate_to runtime
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
delegate_to: "{{ OPENTALK_KEYCLOAK_HOST_NODE }}"
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"