--- # mc URL-embedded credentials (`MC_HOST_=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', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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 named OIDC IdP is picked up by the Console" environment: MINIO_ROOT_USER: "{{ lookup('users', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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 when: mc_idp_add.changed | bool - name: "⏳ Wait for MinIO IAM subsystem to come back after restart" environment: MINIO_ROOT_USER: "{{ lookup('users', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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 when: mc_idp_add.changed | bool - name: "🔎 Check policy (RAW with slash) exists" environment: MINIO_ROOT_USER: "{{ lookup('users', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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', 'administrator').username }}" MINIO_ROOT_PASSWORD: "{{ lookup('users', '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: []