Set-OPS-Public/roles/serveur_icingaweb2/tasks/main.yml
Daniel Allaire 5ecce4282a une console pour le site, et un repli qui ouvrait vers l Internet
Icinga Web 2 gagne un troisieme mode, locale : nginx authentifie en HTTP Basic
et pose REMOTE_USER, l application le croit. Un SITE n a ni annuaire ni Keycloak
— ce sont des services d ecosysteme. Ni backend LDAP ni backend de groupes : un
backend qui vise une ressource inexistante fait echouer chaque ouverture de
session. L habilitation nomme alors une personne, entorse a D-66 ecrite plutot
que contournee.

Deux pieges en chemin : resoudre_annuaire etait appele sans condition et tombait
sur NoneType has no len (default sans son second argument ne remplace pas None),
et le flux du role ne nommait que l edge — le site n en a pas, donc personne ne
pouvait entrer. serveur_grafana portait deja la reponse.

LE DEFAUT DU DEVIS : une sortie vers un role ABSENT de l ecosysteme retombait
sur !SETOPS_INTERNES, la forme de vers l Internet. La frontiere aurait autorise
la console a parler LDAPS a n importe quelle machine du monde, pour joindre un
annuaire qui n existe pas. C est une source vide ouvre le port, cote
destination. Trois regles du meme defaut etaient DEJA posees pour postfix.

La frontiere n est pas ecrite : elle porte la production, et le devis attend un
mot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:11:22 -04:00

246 lines
9.2 KiB
YAML

---
- name: Résoudre la base IcingaDB depuis le registre (rôle partagé)
ansible.builtin.include_role:
name: resoudre_base
vars:
resoudre_base_groupe: "{{ serveur_icingaweb2_icinga_groupe }}"
# 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 —
# il tombait sur un plan sans application d'annuaire :
#
# object of type 'NoneType' has no len()
#
# Un message qui ne nomme ni l'annuaire, ni le role qui le demandait, ni la raison. Le
# mode `locale` existe precisement pour se passer d'annuaire : il n'a rien a resoudre.
- name: Résoudre l'annuaire (rôle partagé)
ansible.builtin.include_role:
name: resoudre_annuaire
vars:
# 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'
- name: Adopter la connexion annuaire pour Icinga Web 2
ansible.builtin.set_fact:
serveur_icingaweb2_ldap_url: "{{ resoudre_annuaire_uri }}"
serveur_icingaweb2_ldap_port: "{{ resoudre_annuaire_port }}"
serveur_icingaweb2_ldap_base: "{{ resoudre_annuaire_base_dn }}"
serveur_icingaweb2_ldap_users_dn: "{{ resoudre_annuaire_users_dn }}"
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'
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
#
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
- name: Poser les paquets tiers depuis le cache du controleur
ansible.builtin.include_role:
name: paquets_tiers
vars:
paquets_tiers_noms: "{{ serveur_icingaweb2_paquets }}"
- name: Installer Icinga Web 2 + module IcingaDB + PHP-FPM + nginx
ansible.builtin.apt:
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
# hors ligne, il echouerait APRES un travail deja fait.
name: "{{ serveur_icingaweb2_paquets
| difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
state: present
update_cache: true
when: (serveur_icingaweb2_paquets
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
- name: Ajouter www-data au groupe icingaweb2 (lecture de la config)
ansible.builtin.user:
name: www-data
groups: icingaweb2
append: true
notify: Redémarrer php-fpm
- name: Assurer le répertoire du module IcingaDB
ansible.builtin.file:
path: "{{ serveur_icingaweb2_config_dir }}/modules/icingadb"
state: directory
owner: root
group: icingaweb2
mode: "0750"
- name: Déployer la configuration icingaweb2 (.ini)
ansible.builtin.template:
src: "{{ item.s }}"
dest: "{{ serveur_icingaweb2_config_dir }}/{{ item.d }}"
owner: root
group: icingaweb2
mode: "0640"
no_log: "{{ item.secret | default(false) }}"
loop:
- { s: config.ini.j2, d: config.ini }
- { s: resources.ini.j2, d: resources.ini, secret: true }
- { s: authentication.ini.j2, d: authentication.ini }
- { s: groups.ini.j2, d: groups.ini }
- { s: roles.ini.j2, d: roles.ini }
- { s: icingadb-config.ini.j2, d: modules/icingadb/config.ini }
- { s: icingadb-redis.ini.j2, d: modules/icingadb/redis.ini }
loop_control:
label: "{{ item.d }}"
notify: Redémarrer php-fpm
- name: Activer les modules icingaweb2 (IcingaDB, BPM)
ansible.builtin.command: "icingacli module enable {{ item }}"
args:
creates: "{{ serveur_icingaweb2_config_dir }}/enabledModules/{{ item }}"
loop: "{{ serveur_icingaweb2_modules }}"
when: not ansible_check_mode
- name: Assurer le répertoire des processus BPM (éditable via l'UI)
ansible.builtin.file:
path: "{{ serveur_icingaweb2_config_dir }}/modules/businessprocess/processes"
state: directory
owner: root
group: icingaweb2
mode: "2770"
when: "'businessprocess' in serveur_icingaweb2_modules"
- name: Semer les processus métier BPM (IaC, optionnel)
ansible.builtin.copy:
content: "{{ item.value }}"
dest: "{{ serveur_icingaweb2_config_dir }}/modules/businessprocess/processes/{{ item.key }}.conf"
owner: root
group: icingaweb2
mode: "0660"
loop: "{{ serveur_icingaweb2_bpm_processes | dict2items }}"
loop_control:
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
dest: /etc/nginx/sites-available/icingaweb2.conf
owner: root
group: root
mode: "0644"
notify: Recharger nginx
- name: Activer le vhost Icinga Web 2
ansible.builtin.file:
src: /etc/nginx/sites-available/icingaweb2.conf
dest: /etc/nginx/sites-enabled/icingaweb2.conf
state: link
notify: Recharger nginx
- name: Retirer le site nginx par défaut
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recharger nginx
- name: Activer et démarrer PHP-FPM et nginx
ansible.builtin.systemd:
name: "{{ item }}"
enabled: true
state: started
loop:
- "{{ serveur_icingaweb2_php_fpm_service }}"
- "{{ serveur_icingaweb2_nginx_service }}"
when: not ansible_check_mode
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
# pousse le resultat ; les greffons standard, eux, sont poses par le porteur.
#
# LA SUPERVISION SURVEILLE SA PROPRE VITRINE. Sans cette sonde, une console morte laisse
# tous les verdicts au vert — le moteur va bien, les sondes poussent, et personne ne peut
# plus rien lire.
- name: Assurer le repertoire des sondes de supervision
ansible.builtin.file:
path: /usr/local/lib/setops/sondes
state: directory
owner: root
group: root
mode: "0755"
- name: Deposer la sonde « console »
ansible.builtin.template:
src: sonde-console.sh.j2
dest: /usr/local/lib/setops/sondes/console.sh
owner: root
group: root
mode: "0750"