01_oidc.ymlΒΆ

---
# mc URL-embedded credentials (`MC_HOST_<alias>=https://USER:PASS@host`)
# are NOT URL-decoded by mc when read from the environment, while the
# MinIO server stores the URL-decoded password. With administrator
# credentials containing $ / € / ! / ' the two sides disagree and every
# admin call fails with "request signature we calculated does not
# match". Authenticate via `mc alias set` with explicit args instead;
# mc applies the credentials verbatim, matching the server.

# MinIO finishes booting its IAM subsystem (where policies and the
# OIDC IdP live) AFTER `mc admin info` already reports the server
# as up. Probing IAM directly via `mc admin policy ls` is the only
# readiness signal that matches what the policy admin API needs;
# `mc admin info` returns rc=0 in a window where the very next
# `mc admin policy info` still fails with `Server not initialized
# yet, please try again.`.
- name: "⏳ Wait for MinIO IAM subsystem to finish initializing"
  environment:
    MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
    MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
  shell: |
    set -euo pipefail
    container run --rm \
        -e MINIO_ROOT_USER \
        -e MINIO_ROOT_PASSWORD \
        --entrypoint /bin/sh \
        {{ MINIO_MC_IMAGE }} \
        -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
            && mc admin policy ls minio'
  args: { executable: /bin/bash }
  register: mc_admin_policy_ls
  retries: 60
  delay: 5
  until: mc_admin_policy_ls.rc == 0
  changed_when: false

- name: "πŸ“ Register Keycloak as named OIDC IdP on MinIO"
  environment:
    MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
    MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
    MINIO_OIDC_CONFIG_URL: "{{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}"
    MINIO_OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}"
    MINIO_OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
    MINIO_OIDC_DISPLAY_NAME: "{{ OIDC.BUTTON_TEXT }}"
    MINIO_OIDC_CLAIM_NAME: "{{ RBAC.GROUP.CLAIM }}"
  shell: |
    set -euo pipefail
    container run --rm \
        -e MINIO_ROOT_USER \
        -e MINIO_ROOT_PASSWORD \
        -e MINIO_OIDC_CONFIG_URL \
        -e MINIO_OIDC_CLIENT_ID \
        -e MINIO_OIDC_CLIENT_SECRET \
        -e MINIO_OIDC_DISPLAY_NAME \
        -e MINIO_OIDC_CLAIM_NAME \
        --entrypoint /bin/sh \
        {{ MINIO_MC_IMAGE }} \
        -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
            && mc idp openid add minio keycloak \
                "config_url=$MINIO_OIDC_CONFIG_URL" \
                "client_id=$MINIO_OIDC_CLIENT_ID" \
                "client_secret=$MINIO_OIDC_CLIENT_SECRET" \
                "display_name=$MINIO_OIDC_DISPLAY_NAME" \
                "claim_name=$MINIO_OIDC_CLAIM_NAME" \
                scopes=openid,profile,email,groups \
                redirect_uri_dynamic=on'
  args: { executable: /bin/bash }
  register: mc_idp_add
  changed_when: '"Successfully" in mc_idp_add.stdout or "Successfully" in mc_idp_add.stderr'
  failed_when:
    - mc_idp_add.rc != 0
    - '"already exists" not in (mc_idp_add.stderr | default(""))'

- name: "πŸ”„ Restart MinIO so the new OIDC IdP takes effect"
  when: mc_idp_add.changed | bool
  block:
    - name: "πŸ”„ Restart MinIO so the named OIDC IdP is picked up by the Console"
      environment:
        MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
        MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
      shell: |
        set -euo pipefail
        container run --rm \
            -e MINIO_ROOT_USER \
            -e MINIO_ROOT_PASSWORD \
            --entrypoint /bin/sh \
            {{ MINIO_MC_IMAGE }} \
            -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
                && mc admin service restart minio --json'
      args: { executable: /bin/bash }
      register: mc_admin_service_restart
      changed_when: mc_admin_service_restart.rc == 0

    - name: "⏳ Wait for MinIO IAM subsystem to come back after restart"
      environment:
        MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
        MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
      shell: |
        set -euo pipefail
        container run --rm \
            -e MINIO_ROOT_USER \
            -e MINIO_ROOT_PASSWORD \
            --entrypoint /bin/sh \
            {{ MINIO_MC_IMAGE }} \
            -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
                && mc admin policy ls minio'
      args: { executable: /bin/bash }
      register: mc_admin_policy_ls_after_restart
      retries: 60
      delay: 5
      until: mc_admin_policy_ls_after_restart.rc == 0
      changed_when: false

- name: "πŸ”Ž Check policy (RAW with slash) exists"
  environment:
    MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
    MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
  shell: |
    set -euo pipefail
    container run --rm \
        -e MINIO_ROOT_USER \
        -e MINIO_ROOT_PASSWORD \
        --entrypoint /bin/sh \
        {{ MINIO_MC_IMAGE }} \
        -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
            && mc admin policy info minio {{ MINIO_OIDC_POLICY_NAME | quote }}'
  args: { executable: /bin/bash }
  register: mc_policy_info_raw
  failed_when: false
  changed_when: false

- name: "πŸ“ Create policy (RAW with slash) if missing"
  environment:
    MINIO_ROOT_USER: "{{ lookup('users', 'platform_administrator').username }}"
    MINIO_ROOT_PASSWORD: "{{ lookup('users', 'platform_administrator').password }}"
  shell: |
    set -euo pipefail
    printf '%s' '{{ (MINIO_OIDC_POLICY_CONTENT | from_yaml | to_json) | b64encode }}' \
      | base64 -d \
      | container run --rm -i \
          -e MINIO_ROOT_USER \
          -e MINIO_ROOT_PASSWORD \
          --entrypoint /bin/sh \
          {{ MINIO_MC_IMAGE }} \
          -c 'mc alias set minio {{ MINIO_API_URL | quote }} "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null \
              && mc admin policy create minio {{ MINIO_OIDC_POLICY_NAME | quote }} /dev/stdin'
  args: { executable: /bin/bash }
  register: minio_policy_create
  changed_when: false
  # rationale: async; consumed only by the STS playwright spec, reaped at the end of this file
  async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
  poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
  when:
    - mc_policy_info_raw.rc != 0

- name: "⏳ Reap the MinIO policy job"
  ansible.builtin.include_tasks: utils/async/reap.yml
  vars:
    async_reap_jobs: "{{ minio_policy_create.results | default([minio_policy_create]) }}"
    async_reap_label: "MinIO OIDC policy"
    async_reap_tolerate: []