icingaweb2 : le backend natif remplace un vestibule de trop
L exploitant : Icinga Web 2 permet nativement de gerer des comptes ; ce genre d intervention n est pertinente que pour integrer Icinga a Keycloak chez les tenants. Le mode locale posait un auth_basic nginx devant le backend external. Ca marchait, et ca reinventait une page de connexion devant une application qui en a une — en privant l exploitant de la gestion des comptes dans l interface. Un vestibule n a de sens que devant une application qui ne sait pas s authentifier ; le GUI de Set-OPS est dans ce cas, Icinga Web 2 non. Mode db : backend natif, groupes natifs, base a elle (les tables du moteur sont reecrites par ses migrations). Le moteur pose UN compte d amorcage et ne l ecrase jamais. D-66 redevient applicable sans annuaire, les groupes vivant en base. Deux pieges du renommage : une garde ecrite en negation a cesse de garder, et le bloc de la base pose apres le rendu des .ini a produit un echec CENSURE par no_log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
95dc225037
commit
51dae506fb
12 changed files with 285 additions and 131 deletions
67
CHANGELOG.md
67
CHANGELOG.md
|
|
@ -1,5 +1,72 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-15 (2) — Un vestibule devant une application qui savait deja s'authentifier
|
||||
|
||||
L'exploitant, en voyant la boite du navigateur sur `vigie` : *« Dans le contexte d'un
|
||||
site, cela complexifie inutilement les choses puisque Icinga Web 2 permet nativement de
|
||||
gerer des comptes. Ce genre d'intervention n'est pertinente que pour integrer Icinga a
|
||||
Keycloak chez les tenants. »*
|
||||
|
||||
Il a raison, et la correction retire du code au lieu d'en ajouter.
|
||||
|
||||
### Ce que le mode `locale` faisait, et pourquoi c'etait de trop
|
||||
|
||||
Il posait un `auth_basic` nginx devant le backend `external` : nginx demandait le mot de
|
||||
passe, Icinga Web 2 croyait le `REMOTE_USER` qu'il recevait. Ca marchait. Ca reinventait
|
||||
une page de connexion **devant une application qui en a une**, et ca privait l'exploitant
|
||||
de ce que l'application sait faire seule.
|
||||
|
||||
UN VESTIBULE N'A DE SENS QUE DEVANT UNE APPLICATION QUI NE SAIT PAS S'AUTHENTIFIER. Celle
|
||||
de Set-OPS — le GUI — est dans ce cas, et son vestibule reste. Icinga Web 2, non.
|
||||
|
||||
### Le mode `db` : le backend natif
|
||||
|
||||
authentication.ini backend = "db" resource = "icingaweb_db"
|
||||
groups.ini backend = "db" — les groupes aussi sont natifs
|
||||
nginx plus aucun auth_basic
|
||||
|
||||
CE QUE CA REND, ET QUI MANQUAIT. La gestion des comptes DANS l'interface : creer,
|
||||
desactiver, changer un mot de passe ne demande plus un deploiement ni un passage par la
|
||||
voute. Le moteur ne pose qu'UN compte — celui qui permet d'entrer la premiere fois — et
|
||||
il ne l'ecrase jamais : un mot de passe change dans l'interface appartient a celui qui
|
||||
l'a change.
|
||||
|
||||
ET D-66 REDEVIENT APPLICABLE SANS ANNUAIRE. Les groupes d'Icinga Web 2 vivent en base :
|
||||
`roles.ini` habilite donc `sysadmin`, un GROUPE, et non plus une personne nommee en dur.
|
||||
Revoquer quelqu'un redevient un clic.
|
||||
|
||||
UNE BASE A ELLE, PAS UN COIN D'`icingadb`. Les tables de comptes appartiennent a
|
||||
l'application web ; celles du moteur sont reecrites par ses migrations. Les meler ferait
|
||||
disparaitre les comptes le jour d'une mise a jour, sans que personne n'ait touche aux
|
||||
comptes.
|
||||
|
||||
LE HACHAGE EST CELUI QUE L'APPLICATION VERIFIE : `password_hash()` de PHP, la fonction
|
||||
exacte qu'Icinga Web 2 appelle a la connexion. Un hachage d'un autre outil donnerait une
|
||||
base valide et une connexion impossible.
|
||||
|
||||
### Deux pieges du renommage
|
||||
|
||||
**`when: serveur_icingaweb2_auth != 'locale'`** gardait l'appel a `resoudre_annuaire`.
|
||||
Juste tant que `locale` etait le seul mode sans annuaire ; renomme en `db`, la garde a
|
||||
cesse de garder et le role a reclame un secret de liaison LDAP qui n'existe pas. Les
|
||||
conditions nomment desormais les modes qui VEULENT un annuaire (`ldap`, `external`) :
|
||||
une condition qui dit ce qu'elle veut survit a un renommage, une qui dit ce qu'elle
|
||||
refuse, non.
|
||||
|
||||
**L'ordre des taches.** Le bloc de la base avait pris la place de l'ancien vestibule —
|
||||
c'est-a-dire APRES le rendu des `.ini`, qui nomment cette base. Le gabarit lisait des
|
||||
variables inexistantes, et l'echec etait **censure par `no_log`** : `changed: true` et
|
||||
rien d'autre. Une tache qui manipule des secrets ne peut pas dire ce qui lui manque ;
|
||||
c'est a l'ordre de ne pas la mettre dans cette situation.
|
||||
|
||||
### Eprouve
|
||||
|
||||
auth_basic dans nginx 0
|
||||
backend declare db / icingaweb_db
|
||||
compte en base icinga-admin, actif
|
||||
depuis le poste 302 -> /authentication/login -> 200
|
||||
la page « Icinga Web 2 Login », champs username et password
|
||||
|
||||
## 2026-09-15 (1) — L'edge publie : trois noms, en TLS verifie de bout en bout
|
||||
|
||||
observatoire.genese.internal http=302 tls=0 (Grafana, vers sa connexion)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 67 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 36 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 36 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (40 genere(s) exempte(s)). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (41 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ README de rôles). Cette page comble ces deux trous.
|
|||
| rôles | 68 | `roles/*/` |
|
||||
| README de rôles | 68 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
||||
| documents | 42 | `docs/*.md` |
|
||||
| pièces d'audit | 45 | `docs/audit/*` |
|
||||
| pièces d'audit | 46 | `docs/audit/*` |
|
||||
| unités de wiki | 27 | `wiki/*.md` |
|
||||
| décisions en vigueur | 85 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
||||
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ vault_postgresql_keycloak: ""
|
|||
vault_bd_keycloak: ""
|
||||
vault_bd_forgejo: ""
|
||||
vault_bd_icingadb: ""
|
||||
# La base des COMPTES de la console (mode `db`) — distincte de celle du moteur.
|
||||
vault_bd_icingaweb2: ""
|
||||
|
||||
# --- Forge (Forgejo) ---
|
||||
vault_forgejo_admin: ""
|
||||
|
|
@ -44,9 +46,10 @@ vault_redis: ""
|
|||
|
||||
# LA CONSOLE DE SUPERVISION, QUAND ELLE S'AUTHENTIFIE SEULE.
|
||||
#
|
||||
# `serveur_icingaweb2_auth: locale` — le mode d'un SITE, qui n'a ni annuaire ni Keycloak.
|
||||
# nginx demande ce mot de passe à l'entrée, Icinga Web 2 croit le nom que nginx lui passe.
|
||||
# Inutile en mode `ldap` ou `external` : le rôle ne l'exige que dans le mode qui s'en sert.
|
||||
# `serveur_icingaweb2_auth: db` — le mode d'un SITE, qui n'a ni annuaire ni Keycloak.
|
||||
# Icinga Web 2 gère ses comptes nativement ; ce mot de passe est celui du compte
|
||||
# D'AMORÇAGE, celui qui permet d'entrer la première fois pour créer les autres dans
|
||||
# l'interface. Inutile en mode `ldap` ou `external`.
|
||||
vault_icingaweb2_admin: ""
|
||||
|
||||
# LA CONSOLE D'EXPLOITATION, QUAND ELLE S'AUTHENTIFIE SEULE.
|
||||
|
|
|
|||
|
|
@ -68,18 +68,28 @@ serveur_icingaweb2_groupes_dn: "ou=groups,{{ resoudre_annuaire_base_dn | default
|
|||
# `ldap` — connexion directe a l'annuaire. Icinga Web 2 n'a pas d'OIDC natif.
|
||||
# `external` — une passerelle SSO devant (oauth2-proxy -> Keycloak) pose `REMOTE_USER`,
|
||||
# et un backend LDAP sert a resoudre les GROUPES.
|
||||
# `locale` — ni annuaire ni passerelle : nginx authentifie lui-meme, et
|
||||
# l'habilitation nomme une personne.
|
||||
# `db` — ni annuaire ni passerelle : Icinga Web 2 gere ses comptes LUI-MEME,
|
||||
# dans sa propre base, avec sa propre page de connexion.
|
||||
#
|
||||
# POURQUOI `locale` EXISTE. Un SITE n'a ni LDAP ni Keycloak — ce sont des services
|
||||
# POURQUOI `db` EXISTE. Un SITE n'a ni LDAP ni Keycloak — ce sont des services
|
||||
# d'ecosysteme, et l'hebergeur n'en est pas un. Sans ce mode, la console de supervision
|
||||
# du site etait impossible : le plan declarait `serveur_icinga` sans `serveur_icingaweb2`,
|
||||
# et 88 verdicts se calculaient sans que personne puisse les LIRE.
|
||||
#
|
||||
# C'EST LE MEME CHOIX QUE POUR GRAFANA ET FORGEJO au site — `connexion_locale`, un compte
|
||||
# d'administration, un mot de passe en voute. Nommer une personne contredit D-66 (« le
|
||||
# GROUPE, jamais des personnes ») ; la regle suppose un annuaire, et il n'y en a pas ici.
|
||||
# On l'ecrit plutot que de la contourner en silence.
|
||||
# PREMIERE ECRITURE, ET ELLE ETAIT DE TROP (corrigee le 2026-09-15). Ce mode s'appelait
|
||||
# `locale` et posait un `auth_basic` nginx devant le backend `external` : nginx demandait
|
||||
# le mot de passe et l'application croyait le nom qu'il lui passait. Ca marchait, et ca
|
||||
# reinventait une page de connexion devant une application qui en a une.
|
||||
#
|
||||
# L'exploitant l'a dit en une phrase : *« Icinga Web 2 permet nativement de gerer des
|
||||
# comptes ; ce genre d'intervention n'est pertinente que pour integrer Icinga a Keycloak
|
||||
# chez les tenants. »* Un vestibule n'a de sens que devant une application qui ne sait
|
||||
# pas s'authentifier — et celle-ci sait.
|
||||
#
|
||||
# CE QUE LE MODE NATIF DONNE EN PLUS : la gestion des comptes DANS l'interface. Creer,
|
||||
# desactiver, changer un mot de passe ne demande plus un deploiement ni un passage par la
|
||||
# voute. Et l'habilitation cesse de nommer une personne en dur dans `roles.ini` : les
|
||||
# GROUPES d'Icinga Web 2 existent aussi en base.
|
||||
serveur_icingaweb2_auth: "ldap"
|
||||
# Variable nginx d'où vient REMOTE_USER. En SSO : l'en-tête d'oauth2-proxy.
|
||||
serveur_icingaweb2_remote_user: "$remote_user"
|
||||
|
|
@ -103,15 +113,22 @@ serveur_icingaweb2_bpm_processes: {}
|
|||
# bloque — et l'absence de reponse, qui est le nginx local tombe.
|
||||
serveur_icingaweb2_sonde_codes: "HTTP/1.1 200,HTTP/1.1 302,HTTP/1.1 401,HTTP/1.1 403"
|
||||
|
||||
# --- MODE `locale` : ce que nginx demande a l'entree ---------------------------------
|
||||
# --- MODE `db` : le compte d'amorcage de la console ------------------------------------
|
||||
#
|
||||
# LE MOT DE PASSE VIENT DE LA VOUTE, comme partout. Vide, le role REFUSE de deployer en
|
||||
# mode `locale` — degrader, jamais deviner, et surtout jamais un mot de passe par defaut
|
||||
# sur une console qui montre l'etat de toute une fabric.
|
||||
# UN SEUL COMPTE EST POSE PAR LE MOTEUR, et c'est voulu : celui qui permet d'entrer la
|
||||
# premiere fois. Les suivants se creent DANS l'interface, par quelqu'un qui a une tete et
|
||||
# un contexte — pas par un deploiement.
|
||||
#
|
||||
# LE MOT DE PASSE VIENT DE LA VOUTE. Vide, le role REFUSE de deployer en mode `db` :
|
||||
# degrader, jamais deviner, et surtout jamais un mot de passe par defaut sur une console
|
||||
# qui montre l'etat de toute une fabric.
|
||||
serveur_icingaweb2_admin_utilisateur: "icinga-admin"
|
||||
serveur_icingaweb2_admin_motdepasse: "{{ vault_icingaweb2_admin | default('') }}"
|
||||
serveur_icingaweb2_htpasswd: "/etc/nginx/.icingaweb2.htpasswd"
|
||||
serveur_icingaweb2_realm: "Supervision"
|
||||
|
||||
# LE SCHEMA DES TABLES DE COMPTES, livre par le paquet. On le charge UNE FOIS — la
|
||||
# presence de `icingaweb_user` sert de temoin, parce que rejouer ce fichier sur une base
|
||||
# deja peuplee echouerait sur les objets existants.
|
||||
serveur_icingaweb2_schema: "/usr/share/icingaweb2/schema/pgsql.schema.sql"
|
||||
|
||||
# TLS VERS POSTGRESQL, DERIVE DE CE QUE SERT LE SERVEUR. Voir `resoudre_base` et P78.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -5,6 +5,120 @@
|
|||
vars:
|
||||
resoudre_base_groupe: "{{ serveur_icingaweb2_icinga_groupe }}"
|
||||
|
||||
# --- MODE `db` : LA BASE DES COMPTES, ET LE COMPTE D'AMORCAGE (2026-09-15) ------------
|
||||
#
|
||||
# TOUT EN HAUT, ET C'EST L'ORDRE QUI L'EXIGE. `resources.ini` nomme cette base ; il est
|
||||
# rendu quelques taches plus bas. Pose apres lui — la ou vivait l'ancien vestibule — ce
|
||||
# bloc laissait le gabarit lire des variables qui n'existaient pas encore, et l'echec
|
||||
# etait CENSURE par `no_log` : « changed: true » et rien d'autre. Une tache qui rend des
|
||||
# secrets ne peut pas dire ce qui lui manque ; c'est a l'ordre de ne pas la mettre dans
|
||||
# cette situation.
|
||||
#
|
||||
# Icinga Web 2 gere ses comptes NATIVEMENT. La premiere ecriture posait un `auth_basic`
|
||||
# nginx devant le backend `external` — ca marchait, et ca reinventait une page de
|
||||
# connexion devant une application qui en a une, en privant l'exploitant de la gestion
|
||||
# des comptes dans l'interface. Un vestibule n'a de sens que devant une application qui
|
||||
# ne sait pas s'authentifier.
|
||||
- name: Résoudre la base des comptes de la console (rôle partagé)
|
||||
ansible.builtin.include_role:
|
||||
name: resoudre_base
|
||||
vars:
|
||||
resoudre_base_groupe: "icingaweb2"
|
||||
when: serveur_icingaweb2_auth == 'db'
|
||||
|
||||
- name: Adopter la base des comptes pour Icinga Web 2
|
||||
ansible.builtin.set_fact:
|
||||
serveur_icingaweb2_db_host: "{{ resoudre_base_db_host }}"
|
||||
serveur_icingaweb2_db_port: "{{ resoudre_base_db_port }}"
|
||||
serveur_icingaweb2_db_base: "{{ resoudre_base_entree.base }}"
|
||||
serveur_icingaweb2_db_utilisateur: "{{ resoudre_base_entree.proprietaire }}"
|
||||
serveur_icingaweb2_db_motdepasse: "{{ resoudre_base_db_password }}"
|
||||
no_log: true
|
||||
when: serveur_icingaweb2_auth == 'db'
|
||||
|
||||
- name: Exiger un mot de passe pour le compte d'amorçage
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- serveur_icingaweb2_admin_motdepasse | length > 0
|
||||
fail_msg: >-
|
||||
`serveur_icingaweb2_auth: db` mais `serveur_icingaweb2_admin_motdepasse` est vide.
|
||||
Une console qui montre l'état de toute une fabric ne s'ouvre pas avec un mot de
|
||||
passe deviné : renseigner la clé dans la voûte de cet écosystème.
|
||||
when: serveur_icingaweb2_auth == 'db'
|
||||
|
||||
# LE SCHEMA NE SE CHARGE QU'UNE FOIS. Le rejouer sur une base deja peuplee echouerait sur
|
||||
# les objets existants — on lit donc d'abord si la table des comptes est la.
|
||||
- name: La base des comptes est-elle déjà en place ?
|
||||
community.postgresql.postgresql_query:
|
||||
login_host: "{{ serveur_icingaweb2_db_host }}"
|
||||
login_user: "{{ serveur_icingaweb2_db_utilisateur }}"
|
||||
login_password: "{{ serveur_icingaweb2_db_motdepasse }}"
|
||||
login_db: "{{ serveur_icingaweb2_db_base }}"
|
||||
ssl_mode: "{{ serveur_icingaweb2_db_sslmode | default('prefer', true) or 'prefer' }}"
|
||||
ca_cert: "{{ serveur_icingaweb2_db_sslrootcert if serveur_icingaweb2_db_sslmode | default('') else omit }}"
|
||||
query: "SELECT to_regclass('public.icingaweb_user') IS NOT NULL AS presente"
|
||||
register: serveur_icingaweb2_schema_etat
|
||||
changed_when: false
|
||||
no_log: true
|
||||
when:
|
||||
- serveur_icingaweb2_auth == 'db'
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Charger le schéma des comptes
|
||||
community.postgresql.postgresql_script:
|
||||
login_host: "{{ serveur_icingaweb2_db_host }}"
|
||||
login_user: "{{ serveur_icingaweb2_db_utilisateur }}"
|
||||
login_password: "{{ serveur_icingaweb2_db_motdepasse }}"
|
||||
login_db: "{{ serveur_icingaweb2_db_base }}"
|
||||
ssl_mode: "{{ serveur_icingaweb2_db_sslmode | default('prefer', true) or 'prefer' }}"
|
||||
ca_cert: "{{ serveur_icingaweb2_db_sslrootcert if serveur_icingaweb2_db_sslmode | default('') else omit }}"
|
||||
path: "{{ serveur_icingaweb2_schema }}"
|
||||
no_log: true
|
||||
when:
|
||||
- serveur_icingaweb2_auth == 'db'
|
||||
- not ansible_check_mode
|
||||
- not (serveur_icingaweb2_schema_etat.query_result[0].presente | default(false))
|
||||
|
||||
# LE HACHAGE EST CELUI QUE L'APPLICATION VERIFIE, PAS UN QU'ON CHOISIT. `password_hash()`
|
||||
# de PHP est exactement la fonction qu'Icinga Web 2 appelle a la connexion ; produire un
|
||||
# hachage d'un autre outil donnerait une base valide et une connexion impossible.
|
||||
- name: Hacher le mot de passe du compte d'amorçage
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- php
|
||||
- -r
|
||||
- 'echo password_hash($argv[1], PASSWORD_DEFAULT);'
|
||||
- "{{ serveur_icingaweb2_admin_motdepasse }}"
|
||||
register: serveur_icingaweb2_hachage
|
||||
changed_when: false
|
||||
no_log: true
|
||||
when:
|
||||
- serveur_icingaweb2_auth == 'db'
|
||||
- not ansible_check_mode
|
||||
|
||||
# CREE S'IL MANQUE, JAMAIS ECRASE. Un mot de passe change dans l'interface appartient a
|
||||
# celui qui l'a change : le moteur ne le reprend pas au deploiement suivant. Il ne pose
|
||||
# que le compte qui permet d'entrer la premiere fois.
|
||||
- name: Poser le compte d'amorçage s'il n'existe pas
|
||||
community.postgresql.postgresql_query:
|
||||
login_host: "{{ serveur_icingaweb2_db_host }}"
|
||||
login_user: "{{ serveur_icingaweb2_db_utilisateur }}"
|
||||
login_password: "{{ serveur_icingaweb2_db_motdepasse }}"
|
||||
login_db: "{{ serveur_icingaweb2_db_base }}"
|
||||
ssl_mode: "{{ serveur_icingaweb2_db_sslmode | default('prefer', true) or 'prefer' }}"
|
||||
ca_cert: "{{ serveur_icingaweb2_db_sslrootcert if serveur_icingaweb2_db_sslmode | default('') else omit }}"
|
||||
query: >-
|
||||
INSERT INTO icingaweb_user (name, active, password_hash)
|
||||
VALUES (%s, 1, %s) ON CONFLICT (name) DO NOTHING
|
||||
positional_args:
|
||||
- "{{ serveur_icingaweb2_admin_utilisateur }}"
|
||||
- "{{ serveur_icingaweb2_hachage.stdout }}"
|
||||
no_log: true
|
||||
when:
|
||||
- serveur_icingaweb2_auth == 'db'
|
||||
- not ansible_check_mode
|
||||
|
||||
|
||||
# ON NE RESOUT PAS UN ANNUAIRE QUAND ON N'EN A PAS (2026-09-14).
|
||||
#
|
||||
# `resoudre_annuaire` etait appele SANS CONDITION. Au site — qui n'a ni LDAP ni Keycloak —
|
||||
|
|
@ -21,7 +135,12 @@
|
|||
# SON compte, pas celui de l'administrateur de l'annuaire (2026-09-13).
|
||||
resoudre_annuaire_bind_cn: "icingaweb2"
|
||||
resoudre_annuaire_secret: "vault_ldap_bind_icingaweb2"
|
||||
when: serveur_icingaweb2_auth != 'locale'
|
||||
# LES MODES NOMMES, PAS UNE NEGATION. `!= 'locale'` etait juste tant que `locale`
|
||||
# etait le seul mode sans annuaire ; renomme en `db`, la garde a cesse de garder et
|
||||
# `resoudre_annuaire` s'est remis a exiger un secret de liaison qui n'existe pas.
|
||||
# Une condition qui dit ce qu'elle VEUT survit a un renommage ; une qui dit ce qu'elle
|
||||
# refuse, non.
|
||||
when: serveur_icingaweb2_auth in ('ldap', 'external')
|
||||
|
||||
- name: Adopter la connexion annuaire pour Icinga Web 2
|
||||
ansible.builtin.set_fact:
|
||||
|
|
@ -32,7 +151,12 @@
|
|||
serveur_icingaweb2_ldap_bind_dn: "{{ resoudre_annuaire_bind_dn }}"
|
||||
serveur_icingaweb2_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}"
|
||||
no_log: true
|
||||
when: serveur_icingaweb2_auth != 'locale'
|
||||
# LES MODES NOMMES, PAS UNE NEGATION. `!= 'locale'` etait juste tant que `locale`
|
||||
# etait le seul mode sans annuaire ; renomme en `db`, la garde a cesse de garder et
|
||||
# `resoudre_annuaire` s'est remis a exiger un secret de liaison qui n'existe pas.
|
||||
# Une condition qui dit ce qu'elle VEUT survit a un renommage ; une qui dit ce qu'elle
|
||||
# refuse, non.
|
||||
when: serveur_icingaweb2_auth in ('ldap', 'external')
|
||||
|
||||
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||
#
|
||||
|
|
@ -122,75 +246,6 @@
|
|||
label: "{{ item.key }}"
|
||||
when: serveur_icingaweb2_bpm_processes | length > 0
|
||||
|
||||
# --- MODE `locale` : LE VESTIBULE, AVANT LA PORTE (2026-09-14) ------------------------
|
||||
#
|
||||
# Un SITE n'a ni annuaire ni passerelle SSO — ce sont des services d'ecosysteme. La
|
||||
# console de supervision etait donc impossible chez lui, et 88 verdicts se calculaient
|
||||
# sans que personne puisse les lire.
|
||||
#
|
||||
# `htpasswd` A LA MAIN PLUTOT QUE LE MODULE `community.general.htpasswd` : ce module
|
||||
# reecrit le fichier a chaque passage quand le hachage porte un sel aleatoire, et le role
|
||||
# se declarerait `changed` pour toujours. On hache une fois, on compare, on n'ecrit que
|
||||
# si ca differe.
|
||||
- name: Exiger un mot de passe pour la console locale
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- serveur_icingaweb2_admin_motdepasse | length > 0
|
||||
fail_msg: >-
|
||||
`serveur_icingaweb2_auth: locale` mais `vault_icingaweb2_admin` est vide.
|
||||
Une console qui montre l'etat de toute une fabric ne s'ouvre pas avec un mot de
|
||||
passe devine : ajouter la cle a la voute de cet ecosysteme.
|
||||
when: serveur_icingaweb2_auth == 'locale'
|
||||
|
||||
- name: Installer de quoi hacher un mot de passe pour nginx
|
||||
ansible.builtin.apt:
|
||||
name: apache2-utils
|
||||
state: present
|
||||
when: serveur_icingaweb2_auth == 'locale'
|
||||
|
||||
# CE QUI EST DEJA POSE EST-IL LE BON MOT DE PASSE ? `htpasswd -v` le VERIFIE sans rien
|
||||
# reecrire — c'est lui qui rend la tache idempotente, pas une comparaison de fichiers
|
||||
# (le sel change a chaque hachage, deux fichiers justes different toujours).
|
||||
- name: Le mot de passe deja pose est-il le bon ?
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- htpasswd
|
||||
- -vb
|
||||
- "{{ serveur_icingaweb2_htpasswd }}"
|
||||
- "{{ serveur_icingaweb2_admin_utilisateur }}"
|
||||
- "{{ serveur_icingaweb2_admin_motdepasse }}"
|
||||
register: serveur_icingaweb2_htpasswd_verif
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
no_log: true
|
||||
when: serveur_icingaweb2_auth == 'locale'
|
||||
|
||||
- name: Poser le mot de passe de la console locale
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- htpasswd
|
||||
- -cbB
|
||||
- "{{ serveur_icingaweb2_htpasswd }}"
|
||||
- "{{ serveur_icingaweb2_admin_utilisateur }}"
|
||||
- "{{ serveur_icingaweb2_admin_motdepasse }}"
|
||||
no_log: true
|
||||
# ELLE NE S'EXECUTE QUE SI LA VERIFICATION A ECHOUE — donc quand elle tourne, elle
|
||||
# change vraiment quelque chose. `changed_when: true` n'est pas une complaisance ici :
|
||||
# c'est l'idempotence qui vit dans le `when`, pas dans un test de sortie.
|
||||
changed_when: true
|
||||
when:
|
||||
- serveur_icingaweb2_auth == 'locale'
|
||||
- (serveur_icingaweb2_htpasswd_verif.rc | default(1)) != 0
|
||||
notify: Recharger nginx
|
||||
|
||||
- name: Refermer le fichier de mots de passe sur nginx
|
||||
ansible.builtin.file:
|
||||
path: "{{ serveur_icingaweb2_htpasswd }}"
|
||||
owner: root
|
||||
group: www-data
|
||||
mode: "0640"
|
||||
when: serveur_icingaweb2_auth == 'locale'
|
||||
|
||||
- name: Déployer le vhost nginx local d'Icinga Web 2
|
||||
ansible.builtin.template:
|
||||
src: nginx.conf.j2
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
; Géré par Set-OPS (rôle serveur_icingaweb2). Ne pas éditer à la main.
|
||||
{% if serveur_icingaweb2_auth == 'locale' %}
|
||||
; MODE `locale` : nginx a DEJA authentifie, et pose `REMOTE_USER`. Icinga Web 2 fait
|
||||
; confiance a ce que le serveur web lui dit — c'est exactement le contrat du backend
|
||||
; `external`, celui qu'emploie aussi le SSO. La difference n'est pas ici, elle est devant :
|
||||
; une authentification HTTP Basic au lieu d'une passerelle OIDC.
|
||||
{% if serveur_icingaweb2_auth == 'db' %}
|
||||
; MODE `db` : Icinga Web 2 gere ses comptes LUI-MEME, dans sa propre base, avec sa propre
|
||||
; page de connexion. C'est la reponse pour un ecosysteme sans annuaire — un SITE.
|
||||
;
|
||||
; AUCUN BACKEND D'ANNUAIRE, parce qu'il n'y en a pas. Un site n'a ni LDAP ni Keycloak :
|
||||
; ce sont des services d'ecosysteme. L'habilitation se fait donc par NOM dans `roles.ini`,
|
||||
; et c'est ecrit la-bas pourquoi.
|
||||
; AUCUN VESTIBULE DEVANT. La premiere ecriture posait un `auth_basic` nginx et faisait
|
||||
; confiance a `REMOTE_USER` : ca reinventait une page de connexion devant une application
|
||||
; qui en a une, et ca privait l'exploitant de la gestion des comptes dans l'interface.
|
||||
[icingaweb2]
|
||||
backend = "external"
|
||||
backend = "db"
|
||||
resource = "icingaweb_db"
|
||||
{% elif serveur_icingaweb2_auth == 'external' %}
|
||||
; SSO : l'utilisateur est fourni par la passerelle (oauth2-proxy → Keycloak) via REMOTE_USER.
|
||||
[icingaweb2]
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@
|
|||
; Habilitation par GROUPE d'annuaire (D-66) : ajouter quelqu'un a
|
||||
; `{{ serveur_icingaweb2_groupe_admin }}` dans LDAP lui ouvre la supervision, sans
|
||||
; deploiement et sans que personne ne soit nomme ici.
|
||||
{% if serveur_icingaweb2_auth == 'locale' %}
|
||||
; MODE `locale` : AUCUN BACKEND DE GROUPES. Un backend `ldap` qui vise une ressource
|
||||
; inexistante ne rend pas une liste vide — il fait ECHOUER chaque ouverture de session,
|
||||
; et l'erreur parle d'annuaire a qui n'en a jamais declare.
|
||||
{% if serveur_icingaweb2_auth == 'db' %}
|
||||
; MODE `db` : LES GROUPES AUSSI SONT NATIFS. `icingaweb_group` et
|
||||
; `icingaweb_group_membership` viennent avec le meme schema que les comptes.
|
||||
;
|
||||
; CE QUE CA REND A D-66. « Le groupe, jamais des personnes » supposait un annuaire ; ici
|
||||
; le groupe existe en base, se peuple dans l'interface, et `roles.ini` peut donc nommer
|
||||
; un GROUPE meme sans LDAP. Revoquer quelqu'un redevient un clic, pas un deploiement.
|
||||
[db]
|
||||
backend = "db"
|
||||
resource = "icingaweb_db"
|
||||
{% else %}
|
||||
[annuaire]
|
||||
backend = "ldap"
|
||||
|
|
|
|||
|
|
@ -9,22 +9,6 @@ server {
|
|||
|
||||
root {{ serveur_icingaweb2_docroot }};
|
||||
index index.php;
|
||||
{% if serveur_icingaweb2_auth == 'locale' %}
|
||||
|
||||
# MODE `locale` : C'EST NGINX QUI AUTHENTIFIE.
|
||||
#
|
||||
# Icinga Web 2 n'a pas d'OIDC natif et un SITE n'a ni annuaire ni passerelle SSO.
|
||||
# HTTP Basic devant `external` est le contrat que l'application attend deja — le
|
||||
# serveur web dit QUI, l'application le croit. C'est exactement ce que fait
|
||||
# oauth2-proxy chez un locataire, avec un vestibule plus simple.
|
||||
#
|
||||
# AU NIVEAU DU `server`, PAS DU `location` : place dans le seul bloc PHP, la
|
||||
# protection sauterait tout ce que `try_files` sert directement — feuilles de style,
|
||||
# icones, et surtout les fichiers que `location ~ /\.ht` croit couvrir. Une
|
||||
# authentification qu'on peut contourner par un chemin voisin n'en est pas une.
|
||||
auth_basic "{{ serveur_icingaweb2_realm }}";
|
||||
auth_basic_user_file {{ serveur_icingaweb2_htpasswd }};
|
||||
{% endif %}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,26 @@ charset = "UTF8"
|
|||
ssl_mode = "{{ serveur_icingaweb2_db_sslmode }}"
|
||||
ssl_ca = "{{ serveur_icingaweb2_db_sslrootcert }}"
|
||||
{% endif %}
|
||||
{% if serveur_icingaweb2_auth != 'locale' %}
|
||||
{% if serveur_icingaweb2_auth == 'db' %}
|
||||
|
||||
; LA BASE DES COMPTES DE LA CONSOLE — distincte d'`icingadb`, qui est celle du MOTEUR.
|
||||
; Meler les deux ferait disparaitre les comptes au premier passage des migrations du
|
||||
; moteur, sans que personne n'ait touche aux comptes.
|
||||
[icingaweb_db]
|
||||
type = "db"
|
||||
db = "pgsql"
|
||||
host = "{{ serveur_icingaweb2_db_host }}"
|
||||
port = "{{ serveur_icingaweb2_db_port }}"
|
||||
dbname = "{{ serveur_icingaweb2_db_base }}"
|
||||
username = "{{ serveur_icingaweb2_db_utilisateur }}"
|
||||
password = "{{ serveur_icingaweb2_db_motdepasse }}"
|
||||
charset = "UTF8"
|
||||
{% if serveur_icingaweb2_db_sslmode | default('') %}
|
||||
ssl_mode = "{{ serveur_icingaweb2_db_sslmode }}"
|
||||
ssl_ca = "{{ serveur_icingaweb2_db_sslrootcert }}"
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if serveur_icingaweb2_auth not in ('db',) %}
|
||||
|
||||
[icingaweb_ldap]
|
||||
type = "ldap"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
; Géré par Set-OPS (rôle serveur_icingaweb2). Ne pas éditer à la main.
|
||||
[Administrators]
|
||||
{% if serveur_icingaweb2_auth == 'locale' %}
|
||||
; ICI ON NOMME QUELQU'UN, ET C'EST UNE ENTORSE ASSUMEE A D-66.
|
||||
{% if serveur_icingaweb2_auth == 'db' %}
|
||||
; LE GROUPE, MEME SANS ANNUAIRE. Les groupes d'Icinga Web 2 vivent en base : D-66
|
||||
; s'applique donc ici comme ailleurs — on habilite `{{ serveur_icingaweb2_groupe_admin }}`,
|
||||
; pas une personne, et y ajouter quelqu'un se fait dans l'interface.
|
||||
;
|
||||
; « Le groupe, jamais des personnes » suppose un annuaire ou le groupe existe. Un SITE
|
||||
; n'en a pas : ni LDAP, ni Keycloak — ce sont des services d'ecosysteme, et l'hebergeur
|
||||
; n'en est pas un. Nommer un groupe absent ne protegerait personne, il empecherait
|
||||
; seulement d'entrer.
|
||||
;
|
||||
; Le jour ou un site porte un annuaire, ce role bascule en `ldap` ou `external` et cette
|
||||
; branche cesse de s'appliquer — sans rien reecrire.
|
||||
; LE COMPTE D'AMORCAGE EST NOMME EN PLUS, et lui seul : il faut bien que quelqu'un puisse
|
||||
; entrer la premiere fois pour peupler le groupe. C'est le meme role que le compte de
|
||||
; secours de Grafana ou de Forgejo.
|
||||
groups = "{{ serveur_icingaweb2_groupe_admin }}"
|
||||
users = "{{ serveur_icingaweb2_admin_utilisateur }}"
|
||||
{% else %}
|
||||
; Le GROUPE, jamais des personnes (D-66) : revoquer quelqu'un ne demande alors
|
||||
|
|
|
|||
|
|
@ -673,13 +673,18 @@ def inventaire() -> dict:
|
|||
"serveur_postgresql_tls_actif": True,
|
||||
"serveur_grafana_oidc_actif": False,
|
||||
"serveur_grafana_connexion_locale": True,
|
||||
# LA CONSOLE DE SUPERVISION S'AUTHENTIFIE SEULE, AU SITE (2026-09-14).
|
||||
# LA CONSOLE DE SUPERVISION GERE SES COMPTES ELLE-MEME, AU SITE.
|
||||
#
|
||||
# Meme raison que pour Grafana juste au-dessus : un SITE n'a ni annuaire ni
|
||||
# Keycloak — ce sont des services d'ECOSYSTEME, et l'hebergeur n'en est pas
|
||||
# un. Les deux autres modes d'Icinga Web 2 (`ldap`, `external`) supposent
|
||||
# l'un ou l'autre ; ici, nginx authentifie et l'application le croit.
|
||||
"serveur_icingaweb2_auth": "locale",
|
||||
# l'un ou l'autre, et n'ont de sens que chez un locataire.
|
||||
#
|
||||
# `db` EST LE MODE NATIF : sa propre base, sa propre page de connexion, et la
|
||||
# gestion des comptes DANS l'interface. La premiere ecriture posait un
|
||||
# `auth_basic` nginx devant — un vestibule devant une application qui sait
|
||||
# deja s'authentifier (corrige le 2026-09-15).
|
||||
"serveur_icingaweb2_auth": "db",
|
||||
# L'EDGE SERT LE CERTIFICAT DE L'AC INTERNE, PAS LE BOUCHON DE DEBIAN.
|
||||
#
|
||||
# `serveur_nginx` a pour DEFAUT `ssl-cert-snakeoil.pem`, avec un commentaire
|
||||
|
|
|
|||
Loading…
Reference in a new issue