02_keycloak_client.ymlΒΆ

---
- 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 }}"