Set-OPS-Public/roles/serveur_icingaweb2/tasks/main.yml
Daniel Allaire b4e3520561 annuaire : un compte de service par consommateur, et la porte se ferme
Keycloak, Dovecot, Postfix et Icinga Web 2 se liaient TOUS avec cn=admin, le compte
d administration de la base. C est le rootDN : slapd lui fait contourner toutes les
ACL. Un seul secret, quatre services, tous les droits sur l arbre — pour ce qui est,
trois fois sur quatre, une simple lecture.

Et les droits livres par Debian etaient intacts : `to * by * read`. Sur ldap://, sans
s authentifier, une machine du reseau enumerait tous les comptes et toutes les
adresses. Des comptes a droits mesures n auraient rien valu tant que cette ligne
restait : on aurait ferme la porte en laissant la fenetre.

L indice etait deja dans le depot. `validatePasswordPolicy` existe parce que slapd
n applique pas ses controles de qualite au rootDN : la consequence etait compensee,
la cause intacte.

- ou=services, un compte par consommateur, secret propre en voute
- sept regles d acces posees EN ENTIER (state: exact) : l ordre est la regle, et
  inserer c est parier sur ce que le paquet aura mis avant nous
- amorcage_acces garde le compte d administration, NOMME comme l exception : il ne
  consomme pas l annuaire, il le provisionne depuis la socket locale
- la sonde passe de -x a -Y EXTERNAL : elle lisait en anonyme et aurait annonce un
  annuaire VIDE sur un annuaire parfaitement sain
- la rotation du compte d administration devient possible (elle n etait posee qu a
  l installation, par debconf : la voute et slapd divergeaient en silence)

Quatre marches payees en chemin :
1. un cinquieme appelant oublie, dont l echec etait masque par no_log — la garde
   refuse desormais SANS no_log : elle nomme la cle absente, jamais son contenu
2. la federation Keycloak ne reecrivait son bindDn que si l URL ou le mode changeaient
   — nouveau secret, ancien nom, error code 49
3. la rotation placee APRES les taches qui se lient en administrateur
4. ansible-vault et son tube : sortie non bloquante = echec silencieux, la voute
   paraissait tournee et etait identique a l octet

P72 exige que tout role incluant resoudre_annuaire NOMME son compte, et qu aucun sauf
amorcage_acces ne nomme admin. Eprouvee dans les deux sens.

Verifie sur l infrastructure : chaque compte lit ce qu il doit, aucun ne voit les
autres, la lecture anonyme rend 0 entree, et les quatre services repondent (doveadm
user, postmap -q, decouverte OIDC 200, portier SSO 200).

vault_openldap_admin et vault_ldap_bind_postfix renouveles : les deux avaient transite
en clair par une session d exploitation. Les anciennes valeurs rendent Invalid
credentials (49).

make prouver : 71 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-13 13:36:38 -04:00

144 lines
5.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 }}"
- 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"
- 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
# 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
- 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