acces : Forgejo et Nextcloud cables, et le claim groups emis
Les deux services n'etaient pas encore deployes : les cabler maintenant vaut mieux que les corriger apres. `porte_par` passe de `aucun` a `claim-groupe`. Forgejo recoit --group-claim-name + --admin-group, et sa tache passe de « creer si absent » a add-oauth OU update-oauth : le meme defaut que la federation Keycloak — cree une fois, jamais corrige — l'attendait sinon. Nextcloud recoit --mapping-groups + --group-provisioning, plus une tache qui verse les membres du groupe d'habilitation dans le groupe interne `admin` : etre dans un groupe projete ne donne aucun pouvoir en soi. Le maillon qui manquait aux deux : AUCUN mapper de protocole n'emettait le claim. Les groupes existaient dans le realm et n'apparaissaient dans aucun jeton — un cablage correct des deux cotes et rien au milieu. `oidc-group-membership-mapper` pose sur les trois clients, `full.path=false` pour que le claim porte `sysadmin` et non `/sysadmin`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
74a2456f09
commit
a08c61bb4e
9 changed files with 154 additions and 17 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -2,6 +2,29 @@
|
||||||
|
|
||||||
## 2026-08-06 — le chemin nord-sud devient dérivable
|
## 2026-08-06 — le chemin nord-sud devient dérivable
|
||||||
|
|
||||||
|
### Forgejo et Nextcloud câblés — avant d'être déployés
|
||||||
|
|
||||||
|
Les deux services n'existaient pas encore : les câbler maintenant vaut mieux que les corriger
|
||||||
|
après. `porte_par` passe de `aucun` à `claim-groupe` dans leurs `meta/acces.yml`.
|
||||||
|
|
||||||
|
**Forgejo** reçoit `--group-claim-name` + `--admin-group` sur sa source OAuth2. Et sa tâche
|
||||||
|
est passée de « créer si absent » à **`add-oauth` ou `update-oauth`** : le même défaut que la
|
||||||
|
fédération Keycloak — créé une fois, jamais corrigé — l'attendait sinon.
|
||||||
|
|
||||||
|
**Nextcloud** était déjà en upsert. Il reçoit `--mapping-groups` et `--group-provisioning`,
|
||||||
|
plus une tâche qui verse les membres du groupe d'habilitation dans le groupe interne `admin` :
|
||||||
|
être dans un groupe projeté ne donne aucun pouvoir en soi. L'absence du groupe au premier
|
||||||
|
déploiement n'est **pas** une erreur — il n'existe qu'à la première connexion d'un membre.
|
||||||
|
|
||||||
|
**Le maillon qui manquait aux deux.** Les groupes existaient dans le realm mais
|
||||||
|
n'apparaissaient dans aucun jeton : pas de mapper de protocole. Forgejo et Nextcloud auraient
|
||||||
|
lu un claim vide et n'auraient rien accordé — un câblage correct des deux côtés, et rien au
|
||||||
|
milieu. `oidc-group-membership-mapper` est désormais posé sur les trois clients, avec
|
||||||
|
`full.path=false` pour que le claim porte `sysadmin` et non `/sysadmin`.
|
||||||
|
|
||||||
|
Vérifié : `grafana`, `forgejo`, `nextcloud` portent chacun le mapper.
|
||||||
|
|
||||||
|
|
||||||
### La chaîne d'habilitation est complète
|
### La chaîne d'habilitation est complète
|
||||||
|
|
||||||
`group-ldap-mapper` construit, et le rôle est **attaché au groupe** — pas à une personne.
|
`group-ldap-mapper` construit, et le rôle est **attaché au groupe** — pas à une personne.
|
||||||
|
|
|
||||||
|
|
@ -66,3 +66,14 @@ serveur_forgejo_oidc_discovery: "https://keycloak.{{ domaine_interne }}/realms/{
|
||||||
# le cert serveur contre le root_ca step-ca (via PGSSLROOTCERT dans l'unite systemd).
|
# le cert serveur contre le root_ca step-ca (via PGSSLROOTCERT dans l'unite systemd).
|
||||||
serveur_forgejo_db_sslmode: "disable"
|
serveur_forgejo_db_sslmode: "disable"
|
||||||
serveur_forgejo_db_sslrootcert: "/etc/step/certs/root_ca.crt"
|
serveur_forgejo_db_sslrootcert: "/etc/step/certs/root_ca.crt"
|
||||||
|
|
||||||
|
# --- Habilitation par groupe (D-66) -------------------------------------------
|
||||||
|
# Forgejo lit un claim de groupes dans le jeton OIDC et accorde l'administration
|
||||||
|
# aux membres d'un groupe nomme. Sans ces reglages, TOUT utilisateur authentifie
|
||||||
|
# obtient le niveau par defaut et l'administration reste au compte local de
|
||||||
|
# secours — ce que `meta/acces.yml` signalait comme `porte_par: aucun`.
|
||||||
|
#
|
||||||
|
# Le claim `groups` est celui que Keycloak emet pour les groupes projetes depuis
|
||||||
|
# LDAP (group-ldap-mapper). Voir docs/autorisation.md.
|
||||||
|
serveur_forgejo_oidc_groupe_claim: "groups"
|
||||||
|
serveur_forgejo_oidc_groupe_admin: "sysadmin"
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
acces:
|
acces:
|
||||||
- groupe: sysadmin
|
- groupe: sysadmin
|
||||||
accorde: admin
|
accorde: admin
|
||||||
# /!\ AUCUN mecanisme ne porte cette habilitation aujourd'hui : le role ne
|
# `--group-claim-name` + `--admin-group` sur la source OAuth2 : Forgejo lit le
|
||||||
# configure ni claim de groupe, ni mapping d'equipe. Toute personne qui
|
# claim `groups` du jeton et accorde l'administration aux membres du groupe.
|
||||||
# s'authentifie obtient le niveau par defaut, et le compte administrateur
|
# Les reglages sont RECONCILIES a chaque passage, pas seulement poses.
|
||||||
# reste le compte local de secours (voute).
|
porte_par: claim-groupe
|
||||||
# Forgejo sait lire un claim OIDC de groupes (`GROUP_CLAIM_NAME` +
|
|
||||||
# `ADMIN_GROUP`) : c'est le cablage manquant. Constate le 2026-08-07.
|
|
||||||
porte_par: aucun
|
|
||||||
raison: >-
|
raison: >-
|
||||||
Administration de la forge : utilisateurs, organisations, reglages.
|
Administration de la forge : utilisateurs, organisations, reglages.
|
||||||
- groupe: dev
|
- groupe: dev
|
||||||
|
|
|
||||||
|
|
@ -149,16 +149,33 @@
|
||||||
cmd: |
|
cmd: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
FJ="{{ serveur_forgejo_binaire }} --config {{ serveur_forgejo_config }}"
|
FJ="{{ serveur_forgejo_binaire }} --config {{ serveur_forgejo_config }}"
|
||||||
if $FJ admin auth list 2>/dev/null | grep -qw "{{ serveur_forgejo_oidc_nom }}"; then
|
# Les reglages de GROUPE sont RECONCILIES, pas seulement poses a la creation :
|
||||||
echo SETOPS_OK
|
# `add-oauth` une fois puis plus rien laissait un cablage perime vivre
|
||||||
else
|
# indefiniment — c'est ce qui est arrive a la federation LDAP de Keycloak, qui
|
||||||
|
# a pointe un hote inexistant pendant des semaines (2026-08-07).
|
||||||
|
OPTS_GROUPE=""
|
||||||
|
{% if serveur_forgejo_oidc_groupe_admin | length > 0 %}
|
||||||
|
OPTS_GROUPE="--group-claim-name {{ serveur_forgejo_oidc_groupe_claim }} --admin-group {{ serveur_forgejo_oidc_groupe_admin }}"
|
||||||
|
{% endif %}
|
||||||
|
ID=$($FJ admin auth list 2>/dev/null \
|
||||||
|
| awk -v n="{{ serveur_forgejo_oidc_nom }}" '$2 == n {print $1}' | head -1)
|
||||||
|
if [ -z "$ID" ]; then
|
||||||
$FJ admin auth add-oauth \
|
$FJ admin auth add-oauth \
|
||||||
--name "{{ serveur_forgejo_oidc_nom }}" \
|
--name "{{ serveur_forgejo_oidc_nom }}" \
|
||||||
--provider openidConnect \
|
--provider openidConnect \
|
||||||
--key "{{ serveur_forgejo_oidc_client_id }}" \
|
--key "{{ serveur_forgejo_oidc_client_id }}" \
|
||||||
--secret "$OIDC_SECRET" \
|
--secret "$OIDC_SECRET" \
|
||||||
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" >/dev/null
|
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" \
|
||||||
|
$OPTS_GROUPE >/dev/null
|
||||||
echo SETOPS_CHANGED
|
echo SETOPS_CHANGED
|
||||||
|
else
|
||||||
|
# `update-oauth` est idempotent cote Forgejo : il reecrit les memes valeurs
|
||||||
|
# sans effet de bord. On ne peut pas comparer avant/apres (la CLI n'expose
|
||||||
|
# pas le detail d'une source), donc on ne signale PAS `changed`.
|
||||||
|
$FJ admin auth update-oauth --id "$ID" \
|
||||||
|
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" \
|
||||||
|
$OPTS_GROUPE >/dev/null
|
||||||
|
echo SETOPS_OK
|
||||||
fi
|
fi
|
||||||
environment:
|
environment:
|
||||||
OIDC_SECRET: "{{ serveur_forgejo_oidc_client_secret }}"
|
OIDC_SECRET: "{{ serveur_forgejo_oidc_client_secret }}"
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,9 @@ serveur_keycloak_groupes_membership_attr: "member"
|
||||||
# nominative : ajouter quelqu'un au groupe suffit, aucun deploiement n'est requis.
|
# nominative : ajouter quelqu'un au groupe suffit, aucun deploiement n'est requis.
|
||||||
# Les roles doivent exister (`serveur_keycloak_realm_roles`).
|
# Les roles doivent exister (`serveur_keycloak_realm_roles`).
|
||||||
serveur_keycloak_groupes_roles: []
|
serveur_keycloak_groupes_roles: []
|
||||||
|
|
||||||
|
# Le claim qui porte les groupes dans le jeton, et les clients qui le recoivent.
|
||||||
|
# Sans ce mapper de protocole, les groupes existent dans le realm mais n'atteignent
|
||||||
|
# jamais les services : Forgejo et Nextcloud liraient un claim vide.
|
||||||
|
serveur_keycloak_groupes_claim: "groups"
|
||||||
|
serveur_keycloak_groupes_mapper_clients: []
|
||||||
|
|
|
||||||
|
|
@ -117,3 +117,43 @@
|
||||||
when:
|
when:
|
||||||
- serveur_keycloak_groupes_roles | length > 0
|
- serveur_keycloak_groupes_roles | length > 0
|
||||||
- not ansible_check_mode
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
# Le mapper de PROTOCOLE : sans lui, les groupes existent dans le realm mais
|
||||||
|
# n'apparaissent JAMAIS dans le jeton — Forgejo et Nextcloud lisent un claim vide
|
||||||
|
# et n'accordent rien. C'est le dernier maillon de la chaine.
|
||||||
|
- name: Émettre le claim de groupes dans le jeton des clients
|
||||||
|
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 '"groupes-membres"'; then
|
||||||
|
echo SETOPS_OK
|
||||||
|
else
|
||||||
|
# `full.path=false` : le claim porte `sysadmin`, pas `/sysadmin`. Les
|
||||||
|
# services comparent un nom de groupe, pas un chemin.
|
||||||
|
"$KC" create clients/$CID/protocol-mappers/models -r {{ serveur_keycloak_realm }} \
|
||||||
|
-s name=groupes-membres -s protocol=openid-connect \
|
||||||
|
-s protocolMapper=oidc-group-membership-mapper \
|
||||||
|
-s 'config."claim.name"={{ serveur_keycloak_groupes_claim }}' \
|
||||||
|
-s 'config."full.path"=false' \
|
||||||
|
-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_groupes_mapper_clients }}"
|
||||||
|
register: serveur_keycloak_grp_mapper
|
||||||
|
changed_when: "'SETOPS_CHANGED' in serveur_keycloak_grp_mapper.stdout"
|
||||||
|
no_log: true
|
||||||
|
when:
|
||||||
|
- serveur_keycloak_groupes_mapper_clients | length > 0
|
||||||
|
- not ansible_check_mode
|
||||||
|
|
|
||||||
|
|
@ -100,3 +100,11 @@ serveur_nextcloud_theme_ciel: true # thème custom « ciel boréal » (log
|
||||||
|
|
||||||
# --- Sécurité : whitelister le sous-réseau d'admin (PAS désactiver l'anti-force-brute) ---
|
# --- Sécurité : whitelister le sous-réseau d'admin (PAS désactiver l'anti-force-brute) ---
|
||||||
serveur_nextcloud_bruteforce_whitelist: "{{ (sous_reseau_admin | default('')) }}"
|
serveur_nextcloud_bruteforce_whitelist: "{{ (sous_reseau_admin | default('')) }}"
|
||||||
|
|
||||||
|
# --- Habilitation par groupe (D-66) -------------------------------------------
|
||||||
|
# `user_oidc` provisionne les groupes du claim `groups` — ceux que Keycloak projette
|
||||||
|
# depuis LDAP (group-ldap-mapper). Etre dans un groupe ne donne pourtant aucun
|
||||||
|
# pouvoir : l'administration de Nextcloud est le groupe interne `admin`, ou le role
|
||||||
|
# verse les membres du groupe d'habilitation.
|
||||||
|
serveur_nextcloud_oidc_groupe_claim: "groups"
|
||||||
|
serveur_nextcloud_oidc_groupe_admin: "sysadmin"
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@
|
||||||
acces:
|
acces:
|
||||||
- groupe: sysadmin
|
- groupe: sysadmin
|
||||||
accorde: admin
|
accorde: admin
|
||||||
# /!\ AUCUN mecanisme ne porte cette habilitation aujourd'hui : le role ne
|
# `user_oidc --mapping-groups --group-provisioning` importe les groupes du
|
||||||
# configure pas de mapping de groupes OIDC. L'administration passe donc par
|
# claim, puis le role verse leurs membres dans le groupe interne `admin` —
|
||||||
# le compte local de secours (voute, `occ`), ce qui contredit « une identite,
|
# etre dans un groupe projete ne donne aucun pouvoir en soi.
|
||||||
# un mot de passe ». Constate le 2026-08-07.
|
porte_par: claim-groupe
|
||||||
# `user_oidc` sait mapper un claim de groupes : c'est le cablage manquant.
|
|
||||||
porte_par: aucun
|
|
||||||
raison: >-
|
raison: >-
|
||||||
Administration : utilisateurs, quotas, applications, partages.
|
Administration : utilisateurs, quotas, applications, partages.
|
||||||
- groupe: personnel
|
- groupe: personnel
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,43 @@
|
||||||
- "--mapping-uid=preferred_username"
|
- "--mapping-uid=preferred_username"
|
||||||
- "--mapping-email=email"
|
- "--mapping-email=email"
|
||||||
- "--mapping-display-name=name"
|
- "--mapping-display-name=name"
|
||||||
|
- "--mapping-groups={{ serveur_nextcloud_oidc_groupe_claim }}"
|
||||||
|
- "--group-provisioning=1"
|
||||||
- "--unique-uid=0"
|
- "--unique-uid=0"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
# Les groupes projetes depuis LDAP arrivent dans Nextcloud, mais y etre membre
|
||||||
|
# ne donne aucun pouvoir : l'administration est un GROUPE NEXTCLOUD nomme
|
||||||
|
# `admin`. On y verse le groupe d'habilitation, une fois qu'il existe.
|
||||||
|
#
|
||||||
|
# Sans cela, `meta/acces.yml` restait `porte_par: aucun` et l'administration
|
||||||
|
# passait par le compte local de secours — ce qui contredit « une identite, un
|
||||||
|
# mot de passe » (2026-08-07).
|
||||||
|
- name: Verser le groupe d'administration dans le groupe `admin` de Nextcloud
|
||||||
|
ansible.builtin.shell:
|
||||||
|
executable: /bin/bash
|
||||||
|
cmd: |
|
||||||
|
set -eo pipefail
|
||||||
|
OCC="php {{ serveur_nextcloud_racine }}/occ"
|
||||||
|
G="{{ serveur_nextcloud_oidc_groupe_admin }}"
|
||||||
|
# Le groupe n'existe qu'apres la premiere connexion d'un membre : son
|
||||||
|
# absence n'est donc PAS une erreur au premier deploiement.
|
||||||
|
if ! $OCC group:list --output=json | grep -q "\"$G\""; then
|
||||||
|
echo "SETOPS_OK (groupe '$G' pas encore provisionne — a la premiere connexion)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
CHANGED=0
|
||||||
|
for U in $($OCC group:list --output=json | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
print(' '.join((json.load(sys.stdin).get('$G') or [])))"); do
|
||||||
|
if ! $OCC group:list --output=json | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
sys.exit(0 if '$U' in (json.load(sys.stdin).get('admin') or []) else 1)"; then
|
||||||
|
$OCC group:adduser admin "$U" >/dev/null && CHANGED=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$CHANGED" = 1 ] && echo SETOPS_CHANGED || echo SETOPS_OK
|
||||||
|
register: serveur_nextcloud_grp_admin
|
||||||
|
changed_when: "'SETOPS_CHANGED' in serveur_nextcloud_grp_admin.stdout"
|
||||||
|
when: serveur_nextcloud_oidc_groupe_admin | length > 0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue