Set-OPS-Public/roles/hosts_statiques/tasks/main.yml
Daniel Allaire 43666cff6e site : un SITE a bien un plan — et huit defauts que lui seul pouvait reveler
J'ai repete toute la journee qu'un SITE n'est pas un plan. C'etait faux : j'en
reconstruisais un morceau par morceau dans underlay.yml sans le nommer. Ce qui est vrai,
c'est qu'un site ne DERIVE pas — il declare ses adresses parce qu'il EST le terrain — et
ne passe donc pas par `instancier`. Mais ne pas deriver n'est pas ne pas avoir de plan.

SITE-Chezlepro/plan/{10-intrants,serveurs,applications}.yml ; underlay.yml ne garde que
la fabric. Ce qui est MESURE d'un cote, ce qui est VOULU de l'autre. Les groupes viennent
du registre des applications, les gardes ont suivi le plan (4 controles negatifs).

Huit defauts, tous invisibles chez un tenant parce qu'il a toujours tout :
  - client_pki codait `infra-pki-01` EN DUR — vrai par coincidence de nomenclature ;
  - aucun moyen pour un service non-root de lire la cle (Forgejo tourne en `git`) ;
  - le certificat, PUBLIC par nature, restait en 0600 ;
  - l'unite Forgejo n'avait pas d'ExecReload : un cert renouvele aurait ete servi perime ;
  - le role ne savait pas servir TLS lui-meme (il y avait toujours un edge) ;
  - le cert ne couvrait pas le nom de SERVICE, faute d'`expose:` ;
  - les registres se chargeaient en tout-ou-rien : sans domaines.yml, plancher vide ;
  - le flux declarait 3000 en dur.

Le message accusait presque toujours autre chose : le DNS quand c'etait un nom faux, une
permission de fichier quand c'etait un port privilegie, rien du tout quand le plancher
s'ecrivait vide.

ROOT_URL est gravee dans les URL de clonage : la forge sert desormais sur 443, avec
CAP_NET_BIND_SERVICE, et son URL n'a plus de port.

Verifie depuis le reseau : Verify return code 0, https://forge.genese.internal/ -> 200.

42 preuves vertes, flux coherents (34 roles, 92 flux).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:15:34 -04:00

87 lines
3.8 KiB
YAML

---
# CLOUD-INIT REECRIT /etc/hosts A CHAQUE DEMARRAGE (2026-08-24).
#
# Le gabarit dore laisse `manage_etc_hosts: true`. A chaque boot, cloud-init regenere
# /etc/hosts depuis son propre modele — et efface le plancher de resolution que ce role
# vient de poser. La machine perd alors la seule facon qu'elle a de nommer ses voisines
# sans DNS, et personne ne le voit : rien n'echoue tant qu'on ne demande pas un nom.
#
# CONSTATE SUR infra-dns-01 : redemarre a 18h53 lors d'un deplacement de disque, il a
# perdu ses six entrees de flotte. Le defaut est apparu deux jours plus tard, sous la
# forme d'un `apt update` qui n'arrivait plus a resoudre le cache d'artefacts — un
# message qui ne parlait pas du tout du vrai probleme.
#
# On le desactive par un fragment plutot qu'en editant cloud.cfg : le gabarit reste
# intact, et `apt purge cloud-init` ou une remise a zero rend la machine a son etat
# d'origine.
- name: Empêcher cloud-init de réécrire le plancher à chaque démarrage
ansible.builtin.copy:
dest: /etc/cloud/cloud.cfg.d/99-setops-hosts.cfg
content: |
# GÉNÉRÉ par Set-OPS (rôle hosts_statiques). NE PAS éditer à la main.
# /etc/hosts est le PLANCHER DE RÉSOLUTION de l'écosystème, dérivé de l'inventaire.
# Laisser cloud-init le régénérer au démarrage l'effacerait en silence.
manage_etc_hosts: false
owner: root
group: root
mode: "0644"
when: hosts_statiques_actif | bool
# Alias d'exposition (best-effort) : dérivés du plan si celui-ci est disponible.
- name: Vérifier la présence des registres du plan (sur le nœud de contrôle)
ansible.builtin.stat:
path: "{{ item }}"
register: hosts_statiques_plan
delegate_to: localhost # les registres du plan vivent sur le nœud de contrôle
become: false # lire un fichier ne nécessite pas de privilèges
loop:
- "{{ setops_plan_dir }}/applications.yml"
- "{{ setops_plan_dir }}/domaines.yml"
loop_control:
label: "{{ item | basename }}"
when:
- hosts_statiques_actif | bool
- hosts_statiques_publier_expositions | bool
- setops_plan_dir is defined
# CHAQUE REGISTRE EST CHARGÉ POUR LUI-MÊME (2026-08-25).
#
# La condition exigeait que les DEUX fichiers existent (`length == 2`). Un tout-ou-rien
# sans raison : `domaines.yml` déclare les domaines PUBLICS, et un écosystème peut
# légitimement n'en avoir aucun — c'est le cas du SITE, dont tous les services sont
# internes.
#
# Conséquence : `applications.yml` n'était pas chargé non plus, la dérivation rendait une
# liste vide, et le plancher s'écrivait SANS AUCUN ALIAS — sans erreur. Le certificat de
# la forge portait `forge.genese.internal` et ce nom ne résolvait nulle part.
#
# On charge donc ce qui existe, fichier par fichier. Un registre absent n'est pas une
# faute ; il est simplement absent.
- name: Charger les registres applications et domaines (si présents)
ansible.builtin.include_vars:
file: "{{ item.item }}"
loop: "{{ hosts_statiques_plan.results | default([]) }}"
loop_control:
label: "{{ item.item | basename }}"
when:
- hosts_statiques_actif | bool
- hosts_statiques_publier_expositions | bool
- item.stat.exists | default(false)
- name: Dériver les alias d'exposition (FQDN exposés → edge qui les sert)
ansible.builtin.set_fact:
hosts_statiques_expositions: >-
{{ {'applications': applications | default({})}
| expositions_des_applications({'domaines_publics': domaines_publics | default({})}) }}
when:
- hosts_statiques_actif | bool
- hosts_statiques_publier_expositions | bool
- name: Générer /etc/hosts depuis l'inventaire (plancher de résolution, indépendant du DNS)
ansible.builtin.template:
src: hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: "0644"
when: hosts_statiques_actif | bool