From c5df8fac4a74f40902241feca4b16502354b0616 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 4 Jul 2026 16:58:15 -0400 Subject: [PATCH] =?UTF-8?q?RBAC=20via=20SSO=20:=20r=C3=B4le=20de=20realm?= =?UTF-8?q?=20Keycloak=20=E2=86=92=20niveau=20Grafana=20(authZ)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Machinerie additive/idempotente dans serveur_keycloak (rbac-oidc.yml) : rôles de realm + mapper 'roles' sur les clients choisis + assignations rôle→utilisateur. kcadm à chaud (zéro coupure SSO). Grafana : role_attribute_path (grafana-admin→Admin, grafana-editor→Editor, sinon Viewer). Prouvé sur id-sso-01 (idempotence) : testmail = grafana-editor. Complète le dashboard logs (Viewer-friendly) : les deux volets de la question « testmail peut-il voir les logs ? ». Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 17 ++++ roles/serveur_grafana/defaults/main.yml | 3 + .../serveur_grafana/templates/setops.conf.j2 | 4 + roles/serveur_keycloak/defaults/main.yml | 9 ++ roles/serveur_keycloak/tasks/main.yml | 6 ++ roles/serveur_keycloak/tasks/rbac-oidc.yml | 91 +++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 roles/serveur_keycloak/tasks/rbac-oidc.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aab4d4..a31a77b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # CHANGELOG — Set-OPS +## 2026-07-04 + +### Ajouté +- **UI des logs Loki : dashboard Grafana provisionné.** Loki n'a pas d'UI ; son UI est Grafana. + Ajout d'un **dashboard « Journaux de la flotte »** (dossier Set-OPS) : sélecteur d'hôte multi + + filtre regex insensible à la casse + panneau logs + débit par hôte. Référence Loki par une + **variable de datasource** (`ds_loki`), pas un UID codé en dur (leçon : ajouter un uid explicite à + une datasource déjà provisionnée casse le démarrage de Grafana). **Visible par les Viewers** (dont + testmail) sans accès Explore. Prouvé sur obs-01 (dashboard chargé, Grafana actif). +- **RBAC via SSO : rôle de realm → niveau Grafana.** Machinerie *additive et idempotente* dans + `serveur_keycloak` (`rbac-oidc.yml`) : rôles de realm (`serveur_keycloak_realm_roles`), **mapper + `roles`** sur les clients choisis (`_role_mapper_clients`, rôles de realm → claim `roles` dans + ID token + userinfo), **assignations** rôle→utilisateur (`_role_assignments`). Côté Grafana, + `role_attribute_path` (`grafana-admin→Admin`, `grafana-editor→Editor`, sinon Viewer). kcadm **à + chaud, zéro coupure SSO**. Prouvé (idempotence `changed=0`) sur id-sso-01 : rôles créés, mapper + présent, `testmail` = `grafana-editor` (→ Explore). Illustre l'**authZ** (vs authN du SSO). + ## 2026-07-03 ### Ajouté diff --git a/roles/serveur_grafana/defaults/main.yml b/roles/serveur_grafana/defaults/main.yml index 51ae3a1..c44f541 100644 --- a/roles/serveur_grafana/defaults/main.yml +++ b/roles/serveur_grafana/defaults/main.yml @@ -29,6 +29,9 @@ serveur_grafana_oidc_client_secret: "{{ vault_grafana_oidc | default('') }}" # serveur_grafana_oidc_realm: "chezlepro" serveur_grafana_oidc_base: "https://keycloak.{{ domaine_interne }}/realms/{{ serveur_grafana_oidc_realm }}/protocol/openid-connect" serveur_grafana_oidc_scopes: "openid email profile" +# RBAC via SSO : mappe le claim 'roles' (rôles de realm Keycloak) vers le niveau Grafana. +# Défaut : grafana-admin→Admin, grafana-editor→Editor, sinon Viewer. Vider pour désactiver. +serveur_grafana_oidc_role_path: "contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'" # Depot apt officiel Grafana (partage avec serveur_loki). serveur_grafana_depot_cle_url: "https://apt.grafana.com/gpg-full.key" diff --git a/roles/serveur_grafana/templates/setops.conf.j2 b/roles/serveur_grafana/templates/setops.conf.j2 index bc26ef0..3021d21 100644 --- a/roles/serveur_grafana/templates/setops.conf.j2 +++ b/roles/serveur_grafana/templates/setops.conf.j2 @@ -13,4 +13,8 @@ Environment=GF_AUTH_GENERIC_OAUTH_AUTH_URL={{ serveur_grafana_oidc_base }}/auth Environment=GF_AUTH_GENERIC_OAUTH_TOKEN_URL={{ serveur_grafana_oidc_base }}/token Environment=GF_AUTH_GENERIC_OAUTH_API_URL={{ serveur_grafana_oidc_base }}/userinfo Environment=GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH=preferred_username +{% if serveur_grafana_oidc_role_path %} +Environment="GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH={{ serveur_grafana_oidc_role_path }}" +Environment=GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT=false +{% endif %} {% endif %} diff --git a/roles/serveur_keycloak/defaults/main.yml b/roles/serveur_keycloak/defaults/main.yml index 659f8b3..719538e 100644 --- a/roles/serveur_keycloak/defaults/main.yml +++ b/roles/serveur_keycloak/defaults/main.yml @@ -49,3 +49,12 @@ serveur_keycloak_truststore_path: "/etc/ssl/certs/ca-certificates.crt" # web_origins: ["https://grafana.{{ domaine_interne }}"] # secret: "{{ vault_grafana_oidc }}" serveur_keycloak_clients: [] + +# --- RBAC OIDC (additif, idempotent) --- +# Rôles de realm à garantir (ex. ['grafana-admin', 'grafana-editor']). +serveur_keycloak_realm_roles: [] +# Clients recevant le mapper « roles » (rôles de realm → claim 'roles' dans le jeton). +serveur_keycloak_role_mapper_clients: [] +# Assignations rôle→utilisateur (ex. [{user: testmail, role: grafana-editor}]). +# En prod, préférer l'assignation via groupe d'annuaire ; ici, explicite pour la preuve. +serveur_keycloak_role_assignments: [] diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index d321903..fb32c84 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -146,6 +146,12 @@ ansible.builtin.include_tasks: clients-oidc.yml when: serveur_keycloak_clients | length > 0 +- name: Configurer le RBAC OIDC (roles de realm + mapper + assignations) + ansible.builtin.include_tasks: rbac-oidc.yml + when: > + serveur_keycloak_realm_roles | length > 0 + or serveur_keycloak_role_assignments | length > 0 + - name: Definir le theme de login du realm (identite Alliance Boreale) ansible.builtin.shell: executable: /bin/bash diff --git a/roles/serveur_keycloak/tasks/rbac-oidc.yml b/roles/serveur_keycloak/tasks/rbac-oidc.yml new file mode 100644 index 0000000..37bd2f0 --- /dev/null +++ b/roles/serveur_keycloak/tasks/rbac-oidc.yml @@ -0,0 +1,91 @@ +--- +# RBAC OIDC : rôles de realm + mapper de rôles sur les clients concernés + assignations. +# Additif et idempotent (create-si-absent, kcadm à chaud — pas de restart Keycloak). +# Permet à une app (ex. Grafana) de mapper un rôle du realm vers son niveau de droit. + +- name: Assurer les roles de realm (kcadm, idempotent) + ansible.builtin.shell: + executable: /bin/bash + cmd: | + set -euo pipefail + KC={{ serveur_keycloak_home }}/bin/kcadm.sh + "$KC" config credentials --server http://localhost:8080 --realm master \ + --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null + if "$KC" get roles/{{ item }} -r {{ serveur_keycloak_realm }} >/dev/null 2>&1; then + echo SETOPS_OK + else + "$KC" create roles -r {{ serveur_keycloak_realm }} -s name={{ item }} >/dev/null + echo SETOPS_CHANGED + fi + environment: + KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" + loop: "{{ serveur_keycloak_realm_roles }}" + register: serveur_keycloak_role_reg + changed_when: "'SETOPS_CHANGED' in serveur_keycloak_role_reg.stdout" + no_log: true + when: + - not ansible_check_mode + - serveur_keycloak_realm_roles | length > 0 + +- name: Assurer le mapper de roles de realm sur les clients (claim 'roles') + ansible.builtin.shell: + executable: /bin/bash + cmd: | + set -euo pipefail + KC={{ serveur_keycloak_home }}/bin/kcadm.sh + "$KC" config credentials --server http://localhost:8080 --realm master \ + --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null + CID=$("$KC" get clients -r {{ serveur_keycloak_realm }} -q clientId={{ item }} 2>/dev/null \ + | grep -oP '"id"\s*:\s*"\K[^"]+' | head -1) + if [ -z "$CID" ]; then echo "SETOPS_OK client absent"; exit 0; fi + if "$KC" get clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} 2>/dev/null \ + | grep -q '"realm-roles"'; then + echo SETOPS_OK + else + "$KC" create clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} \ + -s name=realm-roles -s protocol=openid-connect \ + -s protocolMapper=oidc-usermodel-realm-role-mapper \ + -s 'config."claim.name"=roles' \ + -s 'config."jsonType.label"=String' \ + -s 'config."multivalued"=true' \ + -s 'config."id.token.claim"=true' \ + -s 'config."access.token.claim"=true' \ + -s 'config."userinfo.token.claim"=true' >/dev/null + echo SETOPS_CHANGED + fi + environment: + KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" + loop: "{{ serveur_keycloak_role_mapper_clients }}" + register: serveur_keycloak_mapper_reg + changed_when: "'SETOPS_CHANGED' in serveur_keycloak_mapper_reg.stdout" + no_log: true + when: + - not ansible_check_mode + - serveur_keycloak_role_mapper_clients | length > 0 + +- name: Assigner les roles de realm aux utilisateurs (add-roles idempotent) + ansible.builtin.shell: + executable: /bin/bash + cmd: | + set -euo pipefail + KC={{ serveur_keycloak_home }}/bin/kcadm.sh + "$KC" config credentials --server http://localhost:8080 --realm master \ + --user {{ serveur_keycloak_admin_user }} --password "$KC_ADMIN_PW" >/dev/null + if "$KC" get-roles -r {{ serveur_keycloak_realm }} --uusername {{ item.user }} 2>/dev/null \ + | grep -q '"{{ item.role }}"'; then + echo SETOPS_OK + else + "$KC" add-roles -r {{ serveur_keycloak_realm }} --uusername {{ item.user }} --rolename {{ item.role }} >/dev/null + echo SETOPS_CHANGED + fi + environment: + KC_ADMIN_PW: "{{ serveur_keycloak_admin_password }}" + loop: "{{ serveur_keycloak_role_assignments }}" + loop_control: + label: "{{ item.user }} -> {{ item.role }}" + register: serveur_keycloak_assign_reg + changed_when: "'SETOPS_CHANGED' in serveur_keycloak_assign_reg.stdout" + no_log: true + when: + - not ansible_check_mode + - serveur_keycloak_role_assignments | length > 0