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
|
||||
|
||||
### 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
|
||||
|
||||
`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).
|
||||
serveur_forgejo_db_sslmode: "disable"
|
||||
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:
|
||||
- groupe: sysadmin
|
||||
accorde: admin
|
||||
# /!\ AUCUN mecanisme ne porte cette habilitation aujourd'hui : le role ne
|
||||
# configure ni claim de groupe, ni mapping d'equipe. Toute personne qui
|
||||
# s'authentifie obtient le niveau par defaut, et le compte administrateur
|
||||
# reste le compte local de secours (voute).
|
||||
# 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
|
||||
# `--group-claim-name` + `--admin-group` sur la source OAuth2 : Forgejo lit le
|
||||
# claim `groups` du jeton et accorde l'administration aux membres du groupe.
|
||||
# Les reglages sont RECONCILIES a chaque passage, pas seulement poses.
|
||||
porte_par: claim-groupe
|
||||
raison: >-
|
||||
Administration de la forge : utilisateurs, organisations, reglages.
|
||||
- groupe: dev
|
||||
|
|
|
|||
|
|
@ -149,16 +149,33 @@
|
|||
cmd: |
|
||||
set -euo pipefail
|
||||
FJ="{{ serveur_forgejo_binaire }} --config {{ serveur_forgejo_config }}"
|
||||
if $FJ admin auth list 2>/dev/null | grep -qw "{{ serveur_forgejo_oidc_nom }}"; then
|
||||
echo SETOPS_OK
|
||||
else
|
||||
# Les reglages de GROUPE sont RECONCILIES, pas seulement poses a la creation :
|
||||
# `add-oauth` une fois puis plus rien laissait un cablage perime vivre
|
||||
# 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 \
|
||||
--name "{{ serveur_forgejo_oidc_nom }}" \
|
||||
--provider openidConnect \
|
||||
--key "{{ serveur_forgejo_oidc_client_id }}" \
|
||||
--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
|
||||
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
|
||||
environment:
|
||||
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.
|
||||
# Les roles doivent exister (`serveur_keycloak_realm_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:
|
||||
- serveur_keycloak_groupes_roles | length > 0
|
||||
- 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) ---
|
||||
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:
|
||||
- groupe: sysadmin
|
||||
accorde: admin
|
||||
# /!\ AUCUN mecanisme ne porte cette habilitation aujourd'hui : le role ne
|
||||
# configure pas de mapping de groupes OIDC. L'administration passe donc par
|
||||
# le compte local de secours (voute, `occ`), ce qui contredit « une identite,
|
||||
# un mot de passe ». Constate le 2026-08-07.
|
||||
# `user_oidc` sait mapper un claim de groupes : c'est le cablage manquant.
|
||||
porte_par: aucun
|
||||
# `user_oidc --mapping-groups --group-provisioning` importe les groupes du
|
||||
# claim, puis le role verse leurs membres dans le groupe interne `admin` —
|
||||
# etre dans un groupe projete ne donne aucun pouvoir en soi.
|
||||
porte_par: claim-groupe
|
||||
raison: >-
|
||||
Administration : utilisateurs, quotas, applications, partages.
|
||||
- groupe: personnel
|
||||
|
|
|
|||
|
|
@ -44,6 +44,43 @@
|
|||
- "--mapping-uid=preferred_username"
|
||||
- "--mapping-email=email"
|
||||
- "--mapping-display-name=name"
|
||||
- "--mapping-groups={{ serveur_nextcloud_oidc_groupe_claim }}"
|
||||
- "--group-provisioning=1"
|
||||
- "--unique-uid=0"
|
||||
changed_when: false
|
||||
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