Mise en conformité prouvable : registre d'affirmations + make prouver #1
28 changed files with 1185 additions and 321 deletions
|
|
@ -269,6 +269,8 @@ playbooks/
|
|||
└── applications/
|
||||
```
|
||||
|
||||
À ce jour, seuls `groupes/`, `maintenance/`, `modeles_vm/` et `proxmox/` sont réellement peuplés. Les autres domaines de cette liste sont **prospectifs** : ils n'apparaissent que lorsqu'un besoin réel les justifie (cf. la règle « ne pas créer de structure inutile » ci-dessous).
|
||||
|
||||
Les rôles peuvent être ajoutés progressivement selon les besoins.
|
||||
|
||||
Ne pas créer de structure inutile uniquement pour donner une impression de complétude.
|
||||
|
|
@ -647,7 +649,10 @@ Ne pas créer de commit sans demande explicite.
|
|||
|
||||
---
|
||||
|
||||
## Comportement attendu de Codex
|
||||
## Comportement attendu des agents IA
|
||||
|
||||
Ce comportement s'applique à **tout agent IA** travaillant dans ce dépôt (Codex, Claude
|
||||
Code, ou autre), conformément à la règle d'or « un seul agent IA à la fois ».
|
||||
|
||||
Avant de modifier :
|
||||
|
||||
|
|
|
|||
129
CHANGELOG.md
129
CHANGELOG.md
|
|
@ -1,5 +1,134 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-07-20
|
||||
|
||||
### Modifié
|
||||
- **Conformité Phase 2 — 3 incohérences internes corrigées (documentation d'autorité).**
|
||||
Aucun code Ansible touché ; le code SSH était déjà conforme à `AGENTS.md`.
|
||||
- **`CLAUDE.md` réduit à un pointeur mince** : autorité unique d'`AGENTS.md` + les cinq
|
||||
règles absolues (agent unique ; `hosts.yml` généré jamais édité ; aucun secret ;
|
||||
confirmation des actions destructives ; rien n'est prêt sans validation). Toute la
|
||||
doctrine dupliquée (template, SSH, pare-feu, cloud-init, handlers, Makefile…) retirée.
|
||||
- **Contradiction SSH levée** : `CLAUDE.md` prescrivait `PasswordAuthentication yes`
|
||||
pendant la construction ; le code applique en réalité `PasswordAuthentication no` +
|
||||
`AuthenticationMethods publickey` dès le départ (prouvé, cf. `docs/audit/affirmations.md`
|
||||
AFF-037/050). La doctrine SSH périmée de `CLAUDE.md` est supprimée — la duplication en
|
||||
était la cause racine.
|
||||
- **`AGENTS.md` : « Comportement attendu de Codex » → « …des agents IA »** (la section
|
||||
vaut pour tout agent IA, Claude inclus).
|
||||
- Suivi dans `docs/audit/affirmations.md` (§ Journal des traitements) : AFF-040, AFF-050,
|
||||
AFF-052 résolus.
|
||||
|
||||
### Corrigé
|
||||
- **Conformité Phase 3 — lot A « doc de démarrage » (le parcours QUICKSTART marche seul).**
|
||||
- **Modèle absent (AFF-020/021).** `QUICKSTART.md` proposait `cp -r
|
||||
exemples/modeles/presence-web …` — modèle **inexistant** dans le dépôt public (seul
|
||||
`socle` l'est). Étape 2 réécrite sur `socle` ; les modèles assemblés renvoyés au dépôt
|
||||
privé `Set-OPS-modeles`, cohérent avec `exemples/modeles/README.md`.
|
||||
- **Chemin de voûte faux + piège de shadowing (AFF-023).** Le chemin `lab/` (QUICKSTART,
|
||||
`exemples/vault.exemple.yml`, `docs/config-proxmox.md`) est corrigé en `production/`
|
||||
(le modèle `socle` n'a qu'un inventaire `production/` ; `make config` résout
|
||||
`lab > principal > production`). **Piège corrigé** : le socle livrait ses intrants en
|
||||
`production/group_vars/all.yml` (forme fichier) ; y ajouter `all/vault.yml` (forme
|
||||
dossier) fait **ignorer silencieusement** `all.yml` par Ansible (le dossier masque le
|
||||
fichier — vérifié empiriquement). Le modèle est converti en forme dossier
|
||||
(`group_vars/all/10-intrants.yml`), alignée sur l'instance prouvée. Validé :
|
||||
`ansible-inventory --host` charge `domaine_interne` depuis la nouvelle disposition.
|
||||
- **Commandes `make` périmées (AFF-080/081/082).** `make help` → `make` ;
|
||||
`make syntax-template` → `make syntaxe-modele` (dans `docs/config-proxmox.md`,
|
||||
`docs/modeles_vm/debian13-proxmox.md`, `docs/MISE-A-JOUR-CODEX-CLAUDE.md`).
|
||||
- **Découvert et consigné (AFF-097, non traité ici) :** `lab/` codé en dur restant dans
|
||||
les docs template/clone (`vm-lifecycle`, `procedure-template…`, `proxmox/README`,
|
||||
message de `cloner_vm_debian.yml`) — lot séparé à prévoir.
|
||||
- **Conformité Phase 3 — lot B « doc (prose) ».**
|
||||
- **Prérequis Vault rappelé (AFF-026).** `QUICKSTART.md` : note que `make
|
||||
inventaire-verifier` / `make verifier` chargent l'inventaire complet et exigent
|
||||
`ANSIBLE_VAULT_PASSWORD_FILE`, sinon « no vault secrets found ».
|
||||
- **Sous-dossiers `playbooks/` (AFF-039).** `AGENTS.md` : précisé que seuls `groupes/`,
|
||||
`maintenance/`, `modeles_vm/`, `proxmox/` sont peuplés ; les autres sont prospectifs.
|
||||
- **`SOLUTION.md` remis au présent (AFF-060/061).** Bannière « document historique »
|
||||
(supplanté par README/QUICKSTART/`make`) ; arborescence complétée ; commande de
|
||||
construction `--ask-pass` (mot de passe) → `make preparer-modele` (accès par clé).
|
||||
- **Découvert et consigné (AFF-098, traitement B, non traité ici) :** contradiction
|
||||
fonctionnelle — `make config` écrit le token Proxmox dans `all/vault.yml`, mais
|
||||
`cloner_vm_debian.yml` ne le lit que depuis `proxmox.vault.yml`/env. Couplé à AFF-097
|
||||
dans un futur lot « voûte Proxmox ».
|
||||
|
||||
### Corrigé
|
||||
- **Conformité Phase 3 — lot « voûte Proxmox » (AFF-098 bug fonctionnel + AFF-097).**
|
||||
- **Le clonage lit la voûte unifiée (AFF-098, option a).** `make config` écrit le token
|
||||
Proxmox dans `instance/inventories/<env>/group_vars/all/vault.yml`, mais
|
||||
`playbooks/proxmox/cloner_vm_debian.yml` (lancé `-i localhost,`) ne le chargeait que
|
||||
depuis `proxmox.vault.yml` → `make creer-vm` échouait l'assert `proxmox_api_token_secret`
|
||||
pour qui suivait la voûte unifiée. Corrigé : `all/vault.yml` ajouté aux sources de
|
||||
secrets du playbook (autoritaire) ; la détection de voûte chiffrée du `Makefile`
|
||||
(`cloner-vm`) cherche d'abord `all/vault.yml` puis `proxmox.vault.yml`. `proxmox.vault.yml`
|
||||
reste accepté en compatibilité. **Validé** : `--syntax-check` OK, `ansible-lint` 0 échec,
|
||||
test fonctionnel (token chargé depuis `all/vault.yml`, assert vert).
|
||||
- **Docs Proxmox/template alignées (AFF-097).** `lab/` codé en dur → `production/` + voûte
|
||||
unifiée dans `playbooks/proxmox/README.md`, `docs/procedure-template-debian13-proxmox.md`,
|
||||
`docs/vm-lifecycle.md`, `docs/modeles_vm/debian13-proxmox.md`. Plus aucune référence
|
||||
`inventories/lab/group_vars` dans les fichiers suivis.
|
||||
- **Conformité Phase 3 — lot C « `make verifier` vert » (AFF-006).** `ansible-lint` passe de
|
||||
**33 échecs à 0** (profil `min` → `production`), donc `make lint` **rc=0**. Trois causes :
|
||||
- **`site.yml` généré lint-propre** : `scripts/orchestrer.py` émet un `name:` avant chaque
|
||||
`import_playbook` (30× `name[play]`) ; `site.yml` régénéré. Orchestration inchangée.
|
||||
- **`risky-shell-pipe`** : `set -o pipefail` + `executable: /bin/bash` sur les deux tâches
|
||||
shell à pipe de `playbooks/valider.yml`.
|
||||
- **`name[template]`** : Jinja déplacé en fin de `name` dans `supprimer_vm_debian.yml`.
|
||||
**Validé** : `make lint` rc=0 ; toutes les étapes de `make verifier` vertes (lint, test,
|
||||
site-verifier, flux-verifier, syntaxe) — seule `inventaire-verifier` requiert la voûte de
|
||||
l'opérateur (prérequis documenté, AFF-026). Débloque AFF-002 (« exploitable sans IA » → ✅).
|
||||
|
||||
### Corrigé
|
||||
- **Conformité Phase 5 — boucle documentaire (le parcours QUICKSTART marche seul, prouvé).**
|
||||
Relecture de cohérence bout-en-bout (README/QUICKSTART/docs/wiki ↔ code final). Deux bogues
|
||||
du modèle public/outillage débusqués et corrigés, en plus des alignements de prose :
|
||||
- **Modèle `socle` invalide (AFF-099).** `exemples/modeles/socle/plan/domaines.yml` :
|
||||
`autorite: interne` (périmé, rejeté par le validateur) → `auto-heberge`. Le modèle valide.
|
||||
- **Split-brain d'inventaire (AFF-100).** `scripts/instancier.py` et `scripts/inventory_gui.py`
|
||||
retombaient sur `principal/` quand aucun `hosts.yml` n'existe encore ; or le socle est en
|
||||
`production/` → la 1ʳᵉ génération écrivait dans `principal/`, à côté des `group_vars` restés
|
||||
en `production/`. Corrigé : le repli vise le **répertoire d'inventaire déjà présent** (comme
|
||||
`config_proxmox.py`). Instances existantes (avec `hosts.yml`) **inchangées** (non-régression
|
||||
vérifiée sur `principal`).
|
||||
- **Alignements de prose.** `docs/intrants-communs.md` (`group_vars/all.yml` →
|
||||
`all/10-intrants.yml`, forme dossier que le GUI écrit déjà) ; `QUICKSTART.md` étape 8
|
||||
(`serveur_postgresql` → `serveur_powerdns`, groupe présent dans le socle).
|
||||
- **Preuve** : parcours QUICKSTART rejoué **hors-ligne** sur une copie du socle
|
||||
(`instancier generer`→`comparer`→`appliquer`) — écrit `production/hosts.yml`, diff **vide**
|
||||
ensuite, 4 validateurs verts. Nouvelle preuve récurrente **P15** dans `make prouver`
|
||||
(« modèle public socle valide ») : `make prouver` = **15 OK, 0 échec, 1 sautée**.
|
||||
|
||||
### Ajouté
|
||||
- **Harnais de preuve `make prouver` (Phase 4).** Nouveau `scripts/prouver.py` — un
|
||||
**orchestrateur mince** qui rejoue les preuves automatisables du registre en appelant
|
||||
l'outillage **existant** (les mêmes scripts que `make verifier` : lint, tests, diff-vide,
|
||||
validateurs de registres, cohérence groupes/playbooks, handlers, orchestration, flux,
|
||||
syntaxe, existence des runbooks, invariants structurels) — **aucune validation
|
||||
réimplémentée**. Produit `docs/audit/preuve-AAAA-MM-JJ.md` : rapport **horodaté,
|
||||
rejouable**, reliant chaque preuve aux affirmations couvertes, listant à part les
|
||||
déclarations d'intention (⚪). Sort en erreur si une preuve échoue ; la preuve `P15`
|
||||
(inventaire Ansible complet) est **sautée** proprement sans mot de passe Vault (prérequis
|
||||
AFF-026). Documenté dans `README.md` (une phrase) et `docs/audit/README.md` (mode
|
||||
d'emploi complet + comment ajouter une preuve). `affirmations.md` : section « Couverture
|
||||
par `make prouver` » reliant chaque ✅ à sa preuve. **1re exécution : 14 preuves OK,
|
||||
0 échec, 1 sautée → CONFORME.**
|
||||
- **Registre des affirmations (audit de conformité, Phase 1).** Nouveau
|
||||
`docs/audit/affirmations.md` : chaque affirmation publique vérifiable du dépôt
|
||||
(`README`, `AGENTS`, `CLAUDE`, `QUICKSTART`, `SOLUTION`, `docs/`, `wiki/`, aide du
|
||||
`Makefile`, GUI) est tracée vers une **commande de preuve reproductible** et un statut
|
||||
(✅ prouvée / 🟡 partielle / ❌ fausse / ⚪ invérifiable localement). **54 affirmations
|
||||
enregistrées : 30 ✅, 13 🟡, 8 ❌, 3 ⚪.** Audit **sans aucun correctif** (les
|
||||
traitements relèvent des phases suivantes). Preuves exécutées localement, hors
|
||||
production : diff-vide du plan, recoupement notify↔handlers, validateurs de registres
|
||||
(serveurs/applications/bases/domaines/GUI/orchestrateur/flux), `--syntax-check` de tous
|
||||
les playbooks, test unitaire. Dix écarts majeurs classés par risque pour un opérateur
|
||||
suivant la doc à la lettre — dont : `QUICKSTART` renvoie à un modèle absent
|
||||
(`presence-web`), `make verifier` échoue (ansible-lint : 33 failures), contradiction
|
||||
SSH `CLAUDE.md` ↔ code/`AGENTS.md`, chemin de voûte faux, commandes `make` périmées
|
||||
dans `docs/`.
|
||||
|
||||
## 2026-07-07 (soir)
|
||||
|
||||
### Modifié
|
||||
|
|
|
|||
255
CLAUDE.md
255
CLAUDE.md
|
|
@ -1,244 +1,27 @@
|
|||
# CLAUDE.md — Set-OPS
|
||||
|
||||
## Instruction principale
|
||||
## Autorité
|
||||
|
||||
Claude Code doit lire et respecter `AGENTS.md` avant toute modification.
|
||||
`AGENTS.md` est la **source d'autorité unique** de ce dépôt. Claude Code doit le lire
|
||||
et le respecter intégralement avant toute modification.
|
||||
|
||||
`AGENTS.md` est la source d’autorité principale du dépôt.
|
||||
En cas de contradiction entre `CLAUDE.md` et `AGENTS.md`, **suivre `AGENTS.md`**.
|
||||
|
||||
En cas de contradiction entre `CLAUDE.md` et `AGENTS.md`, suivre `AGENTS.md`.
|
||||
Ce fichier est volontairement mince : il ne redéclare pas la doctrine (template, SSH,
|
||||
pare-feu, cloud-init, handlers, Makefile, groupes…). Toute cette doctrine vit dans
|
||||
`AGENTS.md`, pour éviter la duplication et les dérives qu'elle provoque.
|
||||
|
||||
---
|
||||
## Les cinq règles absolues
|
||||
|
||||
## Rôle du dépôt
|
||||
1. **Un seul agent IA** travaille dans ce dépôt à la fois (Codex *ou* Claude, jamais les deux).
|
||||
2. **`instance/inventories/*/hosts.yml` est un artefact GÉNÉRÉ** depuis le plan
|
||||
(`instance/plan/`). On l'édite jamais à la main : on édite le plan, puis
|
||||
`make instancier` → `make instancier-appliquer`.
|
||||
3. **Aucun secret en clair** (mots de passe, clés privées, tokens, certificats) — Vault
|
||||
ou stockage hors dépôt uniquement.
|
||||
4. **Toute action destructive ou risquée exige une confirmation explicite** (ex.
|
||||
`CONFIRMER=true`, `confirm_destructive_action: true`). Sans elle, refuser l'exécution.
|
||||
5. **Rien n'est « prêt » sans validation.** Au minimum `--syntax-check` du playbook
|
||||
touché, `ansible-lint` si disponible, et l'entrée `CHANGELOG.md` correspondante.
|
||||
|
||||
`Set-OPS` est le moteur Ansible global d’exploitation d’écosystèmes numériques souverains.
|
||||
|
||||
Le template Debian 13 Proxmox est seulement un sous-ensemble du dépôt. Ne pas restructurer tout le dépôt autour de ce seul chantier.
|
||||
|
||||
---
|
||||
|
||||
## Règle d’or IA
|
||||
|
||||
Un seul agent IA travaille dans ce dépôt à la fois.
|
||||
|
||||
- Soit Codex.
|
||||
- Soit Claude Code.
|
||||
- Jamais les deux simultanément.
|
||||
|
||||
---
|
||||
|
||||
## Avant toute modification
|
||||
|
||||
Exécuter ou demander l’équivalent de :
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
find . -maxdepth 3 -type f | sort
|
||||
```
|
||||
|
||||
Lire au minimum :
|
||||
|
||||
```text
|
||||
AGENTS.md
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
ansible.cfg
|
||||
```
|
||||
|
||||
Lire aussi les fichiers directement concernés avant de les modifier.
|
||||
|
||||
Ne pas remplacer massivement l’arborescence sans demande explicite.
|
||||
|
||||
Préférer un correctif minimal ciblé.
|
||||
|
||||
---
|
||||
|
||||
## Validation Ansible obligatoire
|
||||
|
||||
Avant de dire qu’un changement est prêt, exécuter au minimum le `--syntax-check` du playbook touché.
|
||||
|
||||
Pour le template Debian 13 Proxmox :
|
||||
|
||||
```bash
|
||||
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
|
||||
```
|
||||
|
||||
Si `ansible-lint` est disponible :
|
||||
|
||||
```bash
|
||||
ansible-lint
|
||||
```
|
||||
|
||||
Si `ansible-lint` n’est pas disponible, le signaler clairement.
|
||||
|
||||
Ne jamais déclarer un playbook prêt si la validation échoue.
|
||||
|
||||
---
|
||||
|
||||
## Handlers Ansible
|
||||
|
||||
Chaque rôle qui utilise `notify` doit contenir son handler dans le rôle lui-même.
|
||||
|
||||
Exemple :
|
||||
|
||||
```text
|
||||
roles/ssh_baseline/tasks/main.yml
|
||||
roles/ssh_baseline/handlers/main.yml
|
||||
```
|
||||
|
||||
Commandes utiles :
|
||||
|
||||
```bash
|
||||
find roles -path '*/tasks/*.yml' -exec grep -H "notify:" {} \;
|
||||
find roles -path '*/handlers/main.yml' -print
|
||||
```
|
||||
|
||||
Avant de livrer un rôle, vérifier que chaque handler référencé existe réellement.
|
||||
|
||||
---
|
||||
|
||||
## Actions destructives
|
||||
|
||||
Toute action destructrice ou risquée doit exiger une confirmation explicite.
|
||||
|
||||
Exemple :
|
||||
|
||||
```yaml
|
||||
confirm_destructive_action: true
|
||||
```
|
||||
|
||||
Sont considérées risquées :
|
||||
|
||||
- modification bloquante de SSH ;
|
||||
- activation ou modification d’un pare-feu ;
|
||||
- suppression d’utilisateurs ;
|
||||
- suppression de paquets critiques ;
|
||||
- formatage disque ;
|
||||
- modification de partitions ;
|
||||
- redémarrage massif ;
|
||||
- purge de données ;
|
||||
- changement réseau pouvant couper l’accès ;
|
||||
- modification d’un hyperviseur Proxmox ;
|
||||
- opération sur stockage, iSCSI, ZFS ou Ceph.
|
||||
|
||||
Sans confirmation explicite, refuser l’exécution.
|
||||
|
||||
---
|
||||
|
||||
## Template Debian 13 Proxmox
|
||||
|
||||
Le template doit rester un socle commun.
|
||||
|
||||
Il peut contenir :
|
||||
|
||||
- Debian minimal ;
|
||||
- SSH ;
|
||||
- sudo ;
|
||||
- compte technique `ansible` ;
|
||||
- sudo NOPASSWD pour `ansible` lorsque requis ;
|
||||
- `qemu-guest-agent` ;
|
||||
- `cloud-init` ;
|
||||
- `cloud-guest-utils` ;
|
||||
- chrony ;
|
||||
- outils de diagnostic ;
|
||||
- hardening raisonnable ;
|
||||
- AppArmor ;
|
||||
- auditd ;
|
||||
- fail2ban SSH ;
|
||||
- unattended-upgrades ;
|
||||
- journald ;
|
||||
- sysctl de sécurité ;
|
||||
- nftables installé et préparé, mais non activé par défaut.
|
||||
|
||||
Il ne doit pas contenir par défaut :
|
||||
|
||||
- NGINX ;
|
||||
- PostgreSQL ;
|
||||
- MariaDB ;
|
||||
- Docker ;
|
||||
- Podman ;
|
||||
- Redis ;
|
||||
- GitLab ;
|
||||
- Nextcloud ;
|
||||
- monitoring complet ;
|
||||
- données propres à un clone ;
|
||||
- secrets ;
|
||||
- clés privées.
|
||||
|
||||
---
|
||||
|
||||
## SSH
|
||||
|
||||
Pendant la construction du template :
|
||||
|
||||
```text
|
||||
PasswordAuthentication yes
|
||||
PermitRootLogin no
|
||||
PubkeyAuthentication yes
|
||||
```
|
||||
|
||||
État cible après validation des clés SSH :
|
||||
|
||||
```text
|
||||
PasswordAuthentication no
|
||||
PermitRootLogin no
|
||||
PubkeyAuthentication yes
|
||||
```
|
||||
|
||||
Ne jamais désactiver l’authentification par mot de passe avant d’avoir confirmé que l’accès par clé fonctionne.
|
||||
|
||||
Toute modification SSH doit valider la configuration avant rechargement :
|
||||
|
||||
```bash
|
||||
sshd -t
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pare-feu
|
||||
|
||||
Ne pas activer un pare-feu générique dans le template sans confirmation explicite.
|
||||
|
||||
`nftables` peut être installé et préparé, mais rester désactivé dans le template.
|
||||
|
||||
L’activation doit être faite sur un clone ou un serveur final, avec des règles adaptées au rôle du serveur.
|
||||
|
||||
---
|
||||
|
||||
## Cloud-init
|
||||
|
||||
Cloud-init donne l’identité initiale d’un clone :
|
||||
|
||||
- hostname ;
|
||||
- utilisateur initial ;
|
||||
- clé SSH ;
|
||||
- IP ;
|
||||
- passerelle ;
|
||||
- DNS ;
|
||||
- agrandissement de la partition racine.
|
||||
|
||||
Cloud-init ne remplace pas Ansible.
|
||||
|
||||
Séparation attendue :
|
||||
|
||||
```text
|
||||
Proxmox + cloud-init : identité initiale de la VM
|
||||
Set-OPS + Ansible : configuration réelle du serveur
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Après modification
|
||||
|
||||
Répondre avec :
|
||||
|
||||
1. fichiers créés ;
|
||||
2. fichiers modifiés ;
|
||||
3. commandes de validation exécutées ;
|
||||
4. résultat des validations ;
|
||||
5. tests non exécutés ;
|
||||
6. limites connues ;
|
||||
7. entrée `CHANGELOG.md` ajoutée ou raison de l’absence d’entrée.
|
||||
|
||||
Ne pas dire que c’est prêt si ce n’est pas validé.
|
||||
Le détail de chacune de ces règles, et tout le reste, est dans `AGENTS.md`.
|
||||
|
|
|
|||
12
Makefile
12
Makefile
|
|
@ -213,6 +213,12 @@ test:
|
|||
.PHONY: verifier
|
||||
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe
|
||||
|
||||
# Harnais de preuve : rejoue les preuves automatisables du registre et ecrit
|
||||
# docs/audit/preuve-<date>.md (piece justificative horodatee, rejouable).
|
||||
.PHONY: prouver
|
||||
prouver: ansible-runtime _instance-requise
|
||||
python3 scripts/prouver.py
|
||||
|
||||
.PHONY: inventaire hote-planifier hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-ui inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production instance-utiliser instance-courante
|
||||
inventaire: inventaire-production
|
||||
|
||||
|
|
@ -507,8 +513,10 @@ cloner-vm: ansible-runtime
|
|||
vault_args=(); \
|
||||
vault_file=""; \
|
||||
for d in lab principal production; do \
|
||||
f="$(SETOPS_INSTANCE)/inventories/$$d/group_vars/proxmox.vault.yml"; \
|
||||
[[ -f "$$f" ]] && vault_file="$$f" && break; \
|
||||
for gv in group_vars/all/vault.yml group_vars/proxmox.vault.yml; do \
|
||||
f="$(SETOPS_INSTANCE)/inventories/$$d/$$gv"; \
|
||||
[[ -f "$$f" ]] && vault_file="$$f" && break 2; \
|
||||
done; \
|
||||
done; \
|
||||
if [[ -n "$$vault_file" && -f "$$vault_file" ]]; then \
|
||||
read -r premiere_ligne < "$$vault_file" || true; \
|
||||
|
|
|
|||
|
|
@ -22,17 +22,20 @@ git clone <url-de-Set-OPS> Set-OPS && cd Set-OPS
|
|||
```
|
||||
|
||||
## 2. Choisir un modèle et créer ton instance
|
||||
Les modèles sont dans `exemples/modeles/` (voir leur README). Choisis selon ton offre :
|
||||
`socle`, `presence-web`, `forge`, `identite`, `observabilite`, `integral`.
|
||||
Le dépôt public fournit **un modèle générique : `socle`** — le socle souverain minimal
|
||||
(DNS interne, AC/PKI, edge TLS, relais courriel) sur lequel on ajoute des modules. Les
|
||||
modèles **assemblés** par offre (`identite`, `observabilite`, `forge`, `collaboration`,
|
||||
`presence-web`, `integral`) sont un actif à part, dans le dépôt privé `Set-OPS-modeles`
|
||||
(cf. [`exemples/modeles/README.md`](exemples/modeles/README.md)).
|
||||
|
||||
```bash
|
||||
cp -r exemples/modeles/presence-web ../mon-instance # ton instance, ailleurs
|
||||
ln -s ../mon-instance instance # le moteur la trouve via ce lien
|
||||
cp -r exemples/modeles/socle ../mon-instance # ton instance, ailleurs
|
||||
ln -s ../mon-instance instance # le moteur la trouve via ce lien
|
||||
```
|
||||
*(Alternative au symlink : `export SETOPS_INSTANCE=../mon-instance`.)*
|
||||
|
||||
## 3. Renseigner ton instance (« tes couleurs »)
|
||||
- `instance/inventories/production/group_vars/all.yml` → **`domaine_interne`** (ex. `monorg.internal`) ;
|
||||
- `instance/inventories/production/group_vars/all/10-intrants.yml` → **`domaine_interne`** (ex. `monorg.internal`) ;
|
||||
- `instance/plan/nomenclature.yml` → ton **supernet** (ex. `10.20.0.0/16`) ;
|
||||
- `instance/plan/domaines.yml` → ton **domaine public** ;
|
||||
- `instance/plan/serveurs.yml` → **placement Proxmox** (nœud, stockage, disque, mémoire, cœurs).
|
||||
|
|
@ -44,8 +47,8 @@ Tu peux aussi le faire dans le GUI plus tard (`make inventaire-ui`).
|
|||
make config # renseigne API host/user/port, nœud, stockage, VMID du template...
|
||||
```
|
||||
Chaque paramètre demandé est expliqué dans [`docs/config-proxmox.md`](docs/config-proxmox.md).
|
||||
Tous tes secrets (token API + `vault_*`) vont dans **une voûte unique par
|
||||
environnement** : `instance/inventories/lab/group_vars/all/vault.yml`, à partir du
|
||||
Tous tes secrets (token API + `vault_*`) vont dans **une voûte unique** :
|
||||
`instance/inventories/production/group_vars/all/vault.yml`, à partir du
|
||||
gabarit [`exemples/vault.exemple.yml`](exemples/vault.exemple.yml), chiffrée avec
|
||||
`ansible-vault`. Exporte ton mot de passe Vault, ex. :
|
||||
```bash
|
||||
|
|
@ -86,7 +89,7 @@ Passe l'hôte en `etat: actif` dans `instance/plan/serveurs.yml`, régénère, d
|
|||
make instancier-appliquer
|
||||
make deployer HOTE=infra-dns-01 # configure l'hôte selon ses groupes
|
||||
# ou, par couche :
|
||||
make deployer-groupe GROUPE=serveur_postgresql
|
||||
make deployer-groupe GROUPE=serveur_powerdns
|
||||
```
|
||||
Les déploiements de groupe ne ciblent **que** les hôtes actifs.
|
||||
|
||||
|
|
@ -99,6 +102,9 @@ Les déploiements de groupe ne ciblent **que** les hôtes actifs.
|
|||
make inventaire-verifier # registres + inventaire + garde-fous
|
||||
make verifier # + ansible-lint + --syntax-check
|
||||
```
|
||||
> Ces cibles chargent l'inventaire complet : exporte d'abord ton mot de passe Vault
|
||||
> (étape 4, `ANSIBLE_VAULT_PASSWORD_FILE`), sinon Ansible s'arrête sur
|
||||
> « Attempting to decrypt but no vault secrets found ».
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,13 @@ Valider le dépôt :
|
|||
make verifier
|
||||
```
|
||||
|
||||
Produire une **preuve de conformité** horodatée (rejoue les preuves du registre des
|
||||
affirmations → `docs/audit/preuve-<date>.md` ; mode d'emploi : `docs/audit/README.md`) :
|
||||
|
||||
```bash
|
||||
make prouver
|
||||
```
|
||||
|
||||
Inspecter les inventaires :
|
||||
|
||||
```bash
|
||||
|
|
|
|||
24
SOLUTION.md
24
SOLUTION.md
|
|
@ -1,5 +1,11 @@
|
|||
# La solution Set-OPS
|
||||
|
||||
> **Document historique de décision.** Le parcours opérationnel à jour vit dans
|
||||
> [`README.md`](README.md) et [`QUICKSTART.md`](QUICKSTART.md) ; l'autorité est
|
||||
> [`AGENTS.md`](AGENTS.md). Les commandes brutes ci-dessous sont **supplantées** par les
|
||||
> cibles `make` (`preparer-modele` / `verifier-modele` / `nettoyer-modele`). Conservé pour
|
||||
> tracer la décision fondatrice.
|
||||
|
||||
## Décision
|
||||
|
||||
`Set-OPS` est le moteur Ansible global d’exploitation d’écosystèmes numériques souverains.
|
||||
|
|
@ -19,18 +25,24 @@ docs/
|
|||
|
||||
Les rôles sont nommés clairement, sans sous-arborescence complexe.
|
||||
|
||||
> Réalité actuelle du dépôt : au-delà de ces quatre dossiers, le moteur comporte aussi
|
||||
> `scripts/` (plan + GUI), `filter_plugins/`, `exemples/`, `wiki/` et l'instance montée
|
||||
> (`instance/`). Vue d'ensemble : [`docs/carte-set-ops.md`](docs/carte-set-ops.md).
|
||||
|
||||
## Commande principale pour construire le template
|
||||
|
||||
```bash
|
||||
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --ask-pass --ask-become-pass
|
||||
```
|
||||
|
||||
Après que sudo sans mot de passe fonctionne :
|
||||
Le template se construit avec un **accès SSH par clé dès le départ** (clé injectée par
|
||||
cloud-init) — pas par mot de passe. La cible opérateur est :
|
||||
|
||||
```bash
|
||||
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml
|
||||
make preparer-modele
|
||||
```
|
||||
|
||||
Elle exécute `playbooks/modeles_vm/debian13_proxmox_preparer.yml` sur l'inventaire modèle,
|
||||
après avoir vérifié l'accès SSH par clé et les privilèges sudo du compte `ansible`. Voir
|
||||
[`AGENTS.md`](AGENTS.md) § SSH pour l'état cible (`PasswordAuthentication no`,
|
||||
`AuthenticationMethods publickey`).
|
||||
|
||||
## Vérification
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ find roles -path '*/handlers/main.yml' -print
|
|||
Après correction :
|
||||
|
||||
```bash
|
||||
make syntax-template
|
||||
make syntaxe-modele
|
||||
```
|
||||
|
||||
Puis relancer :
|
||||
|
|
|
|||
75
docs/audit/README.md
Normal file
75
docs/audit/README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Audit de conformité — mode d'emploi
|
||||
|
||||
Ce dossier contient le dispositif qui garde Set-OPS **honnête** : il ne doit jamais
|
||||
affirmer plus que ce qu'il prouve.
|
||||
|
||||
## Les trois pièces
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---|---|
|
||||
| [`affirmations.md`](affirmations.md) | **Le registre** : chaque affirmation publique du dépôt (README, AGENTS, QUICKSTART, docs, wiki, aide `make`, GUI) tracée vers une commande de preuve et un statut (✅/🟡/❌/⚪), plus le **journal des traitements** (ce qui a été corrigé, quand, comment). |
|
||||
| `scripts/prouver.py` | **Le harnais** : un orchestrateur mince qui **rejoue** les preuves automatisables du registre en appelant l'outillage existant (les mêmes scripts que `make verifier`). Il ne réimplémente aucune validation. |
|
||||
| `preuve-AAAA-MM-JJ.md` | **La pièce justificative** : le rapport horodaté produit par `make prouver`. Rejouable et présentable (audit, certification, revue). |
|
||||
|
||||
## Produire une preuve
|
||||
|
||||
```bash
|
||||
make prouver
|
||||
```
|
||||
|
||||
Cela exécute chaque preuve et écrit `docs/audit/preuve-<date>.md`. La commande **sort en
|
||||
erreur (rc≠0)** si une preuve automatisable échoue — utilisable en garde-fou (CI locale,
|
||||
pré-commit). Une preuve **SAUTÉE** (⚪) n'est pas un échec.
|
||||
|
||||
### Prérequis Vault
|
||||
|
||||
La preuve `P15` (inventaire Ansible complet, `ansible-inventory --list`) déchiffre le
|
||||
`group_vars` de l'instance. Sans `ANSIBLE_VAULT_PASSWORD_FILE`, elle est **automatiquement
|
||||
sautée** (⚪) avec la mention du prérequis — le reste du harnais reste vert, car les
|
||||
validateurs Python lisent le plan et l'inventaire directement, sans secret. Pour l'inclure :
|
||||
|
||||
```bash
|
||||
export ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass
|
||||
make prouver
|
||||
```
|
||||
|
||||
## Ce que couvre `make prouver`
|
||||
|
||||
| # | Preuve | Ce qu'elle établit |
|
||||
|---|---|---|
|
||||
| P01 | Lint (`ansible-lint`) | 0 violation, profil `production`. |
|
||||
| P02 | Tests unitaires | `inventory_host` — cas nominal + refus. |
|
||||
| P03 | Diff-vide du plan | l'inventaire est **généré** depuis le plan (diff vide). |
|
||||
| P04 | Groupes ↔ playbooks | chaque groupe opérationnel a son playbook homonyme. |
|
||||
| P05 | Dépendances de groupes | graphe cohérent, aucune entrée orpheline. |
|
||||
| P06 | Validateurs de registres | serveurs / applications / bases / domaines valides. |
|
||||
| P07 | GUI | `node --check` du JS du GUI. |
|
||||
| P08 | Orchestration | couches + graphe : aucun cycle, aucune arête en arrière. |
|
||||
| P09 | Flux réseau | schéma + matrice d'audit cohérents. |
|
||||
| P10 | Handlers ↔ notify | tout `notify` pointe vers un handler du même rôle. |
|
||||
| P11 | Syntaxe | `--syntax-check` de tous les playbooks (via `make syntaxe`). |
|
||||
| P12 | Runbooks cités | les fichiers `docs/` référencés existent. |
|
||||
| P13 | Invariants structurels | LICENSE, socle en forme dossier, pas de couches parallèles, SSH clé-only, nftables désactivé par défaut. |
|
||||
| P14 | Chemins d'inventaire | aucun `instance/inventories/lab/group_vars` codé en dur. |
|
||||
| P15 | Modèle public `socle` | ses registres (domaines/serveurs/applications/bases) valident. |
|
||||
| P16 | Inventaire Ansible (voûte) | `ansible-inventory --list` — sauté sans mot de passe Vault. |
|
||||
|
||||
Le rapport relie chaque preuve aux **affirmations** qu'elle couvre (colonne « Affirmations »),
|
||||
et liste à part les **déclarations d'intention** (⚪ invérifiables localement : AFF-036,
|
||||
091, 096, 007) — assumées comme intentions, jamais présentées comme prouvées.
|
||||
|
||||
## Ajouter une preuve
|
||||
|
||||
1. Ajouter (ou corriger) l'affirmation dans `affirmations.md` avec sa commande de preuve.
|
||||
2. Ajouter une entrée à la liste `PREUVES` de `scripts/prouver.py` : soit une ou plusieurs
|
||||
commandes (`cmds`, toutes doivent renvoyer 0), soit une fonction native `func` renvoyant
|
||||
`(ok, détail)` pour un invariant simple. Renseigner `refs` avec les ID d'affirmations.
|
||||
3. Ne **pas** réimplémenter de logique de validation : appeler l'outillage existant
|
||||
(`scripts/*.py`, `ansible-lint`, cibles `make`). Le harnais orchestre, il ne valide pas.
|
||||
|
||||
## Rapport avec `make verifier`
|
||||
|
||||
`make verifier` est la **porte de validation** au fil de l'eau (lint, tests, cohérence,
|
||||
syntaxe) et s'arrête au premier échec. `make prouver` est complémentaire : il **exécute
|
||||
tout** (sans s'arrêter au premier échec), **horodate** et **relie** chaque résultat aux
|
||||
affirmations — pour produire une pièce présentable. Les deux réutilisent le même outillage.
|
||||
390
docs/audit/affirmations.md
Normal file
390
docs/audit/affirmations.md
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
# Registre des affirmations — Set-OPS
|
||||
|
||||
> **Phase 1 de la mise en conformité prouvable.** Ce document est un **audit**, sans
|
||||
> aucun correctif de code ni de documentation. Chaque affirmation publique vérifiable
|
||||
> du dépôt est tracée vers une commande de preuve reproductible, ou marquée comme
|
||||
> invérifiable.
|
||||
>
|
||||
> Règle d'arbitrage (rappel) : *le dépôt ne doit jamais affirmer plus que ce qu'il
|
||||
> prouve.* Les traitements (corriger la doc / corriger le code / chantier séparé)
|
||||
> relèvent de la **Phase 3** — ils ne sont **pas** appliqués ici.
|
||||
|
||||
- **Date de l'audit** : 2026-07-20
|
||||
- **Instance montée pendant l'audit** : `instance -> ../OPS-Technolibre` (inventaire `principal/`)
|
||||
- **Outillage** : ansible-core 2.18.1, ansible-lint 25.1.3, Python 3.13.3, node présent.
|
||||
- **Périmètre** : surfaces publiques uniquement — `README.md`, `AGENTS.md`,
|
||||
`CLAUDE.md`, `QUICKSTART.md`, `SOLUTION.md`, `docs/`, `wiki/`, aide du `Makefile`,
|
||||
libellés du GUI. Aucune VM / Proxmox / réseau touché (mission hors production).
|
||||
|
||||
Légende du statut : ✅ prouvée · 🟡 partielle · ❌ fausse · ⚪ invérifiable localement.
|
||||
|
||||
---
|
||||
|
||||
## 1. README.md
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-001 | « Set-OPS est un moteur Ansible générique … à partir d'un plan déclaratif » (README:3) | fonctionnelle | `python3 scripts/instancier.py generer && python3 scripts/instancier.py comparer` | ✅ | « DIFF VIDE : le plan reproduit exactement l'inventaire ». Générateur opérationnel. |
|
||||
| AFF-002 | « Set-OPS s'exploite entièrement à la main — la doc, `make` et le GUI suffisent, sans aucune IA » (README:9 ; AGENTS principe 10) | doctrinale | Suivre `QUICKSTART.md` de bout en bout ; `make verifier` | 🟡 | Le principe tient au niveau outillage, mais **deux trous cassent le parcours sans IA** : `make verifier` échoue (AFF-006) et `QUICKSTART` étape 2 renvoie à un modèle absent (AFF-020/021). Un opérateur seul bute. |
|
||||
| AFF-003 | Registres = source unique de vérité : `serveurs.yml`, `applications.yml`, `bases-donnees.yml`, `domaines.yml`, `nomenclature.yml` (README:15) | structurelle | `make inventaire-verifier` (serveurs/applications/bases/domaines) | ✅ | Les 4 validateurs renvoient « Registre valide ». Fichiers présents dans `instance/plan/`. |
|
||||
| AFF-004 | « `instance/inventories/production/hosts.yml` est généré depuis le plan — ne pas l'éditer à la main » (README:34) | procédurale | `python3 scripts/instancier.py comparer` (diff-vide) | ✅ | Diff vide confirmé. (Nuance de chemin : l'instance courante utilise `principal/` — voir AFF-023.) |
|
||||
| AFF-005 | « Afficher l'aide : `make` » (README:86) | procédurale | `make` (défaut = cible `aide`) | ✅ | `.DEFAULT_GOAL := aide` ; l'aide s'affiche. |
|
||||
| AFF-006 | « Valider le dépôt : `make verifier` » (README:91) | fonctionnelle | `make verifier` | ❌ | `make lint` (1re dépendance de `verifier`) **échoue rc=2** : ansible-lint rapporte « Failed: 33 failure(s) » (`name[play]` sur le `site.yml` généré, `risky-shell-pipe` dans `site.yml`/`valider.yml`, `name[template]`). `make verifier` ne passe donc **pas** sur un dépôt propre. |
|
||||
| AFF-007 | « Inspecter … `make hote-afficher HOTE=web-frontal-01` » (README:98) | procédurale | `make hote-afficher HOTE=web-frontal-01` | 🟡 | La cible existe et fonctionne, mais l'hôte d'exemple `web-frontal-01` n'existe pas dans l'instance courante → l'exemple littéral échoue (exemple générique, pas garanti par instance). |
|
||||
| AFF-008 | « Chaque groupe opérationnel doit avoir son playbook homonyme dans `playbooks/groupes/` » (README:120 ; AGENTS:316) | structurelle | `python3 scripts/inventory_host.py --inventaire instance/inventories/principal/hosts.yml verifier-playbooks --dossier-playbooks playbooks/groupes` | ✅ | rc=0. Les 30 groupes opérationnels ont leur playbook. Seuls `hotes_actifs`, `hotes_planifies`, `modeles_vm` (groupes méta/état) n'en ont pas — exclus par conception. |
|
||||
| AFF-009 | « Les dépendances causales entre groupes sont dans `docs/dependances-groupes.yml` » (README:128 ; AGENTS:413) | structurelle | `python3 scripts/inventory_host.py … verifier-dependances …` | ✅ | rc=0. Aucune entrée ne référence un rôle/groupe inexistant. |
|
||||
| AFF-010 | « Le runbook du DNS interne initial est dans `docs/dns-interne.md` » (README:130) | structurelle | `test -f docs/dns-interne.md` | ✅ | Fichier présent. |
|
||||
| AFF-011 | « La nomenclature … est dans `docs/nomenclature-vm.md` » (README:132) | structurelle | `test -f docs/nomenclature-vm.md` | ✅ | Fichier présent. |
|
||||
| AFF-012 | « Le catalogue des services … `docs/catalogue-services.md` » (README:126) | structurelle | `test -f docs/catalogue-services.md` | ✅ | Fichier présent. |
|
||||
| AFF-013 | « `make creer-vm HOTE=… ` VMID/IP/VLAN/passerelle lus dans le plan » (README:138) | fonctionnelle | `python3 scripts/inventory_host.py … parametres-proxmox --hote <h>` | 🟡 | La dérivation existe (sous-commande `parametres-proxmox`), mais l'exécution réelle touche Proxmox → non testable localement (hors production). Mécanique de dérivation seule vérifiable. |
|
||||
| AFF-014 | « Les déploiements de groupes ciblent automatiquement les hôtes actifs seulement » (README:171) | fonctionnelle | lecture `Makefile:261` : `--limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'` | ✅ | Intersection `groupe ∩ hotes_actifs` codée dans la cible `appliquer`. |
|
||||
| AFF-015 | « Set-OPS est un logiciel libre … AGPLv3 … texte intégral dans `LICENSE` » (README:177-186) | structurelle | `test -f LICENSE` | ✅ | `LICENSE` présent. |
|
||||
|
||||
---
|
||||
|
||||
## 2. QUICKSTART.md
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-020 | « Les modèles sont dans `exemples/modeles/` … Choisis : `socle`, `presence-web`, `forge`, `identite`, `observabilite`, `integral` » (QUICKSTART:25-26) | structurelle | `ls exemples/modeles/` | ❌ | Le dossier public ne contient que `socle` (+ `README.md`). Les 5 autres modèles sont dans un dépôt **privé** `Set-OPS-modeles` (correctement expliqué dans `exemples/modeles/README.md`, mais **pas** dans QUICKSTART). |
|
||||
| AFF-021 | « `cp -r exemples/modeles/presence-web ../mon-instance` » (QUICKSTART:29) | procédurale | exécuter la commande | ❌ | La commande **échoue** : `exemples/modeles/presence-web` n'existe pas. C'est la commande centrale de l'étape 2 ; un opérateur suivant QUICKSTART à la lettre est bloqué immédiatement. Seule valeur valide : `socle`. |
|
||||
| AFF-022 | « `instance/inventories/production/group_vars/all.yml` → `domaine_interne` » (QUICKSTART:36) | structurelle | `test -f exemples/modeles/socle/inventories/production/group_vars/all.yml` | ✅ | Le modèle `socle` fournit bien ce fichier sous `production/`. |
|
||||
| AFF-023 | « Tous tes secrets … `instance/inventories/lab/group_vars/all/vault.yml` » (QUICKSTART:48-49) | procédurale | `ls exemples/modeles/socle/inventories/` | ❌ | Le modèle `socle` n'a **pas** d'inventaire `lab/` (uniquement `production/`). Le chemin de voûte annoncé n'existe pas dans le modèle public ; incohérent avec AFF-022 (`production/`). |
|
||||
| AFF-024 | « `make instancier` (diff) puis `make instancier-appliquer FORCE=1` — 1re génération » (QUICKSTART:68-70) | fonctionnelle | `make instancier` ; cible `instancier-appliquer` (Makefile:582) | ✅ | Cibles présentes ; `FORCE` mappé sur `--force`. Diff-vide prouve le mécanisme (AFF-001). |
|
||||
| AFF-025 | Étape 5 : `make preparer-modele` / `verifier-modele` / `nettoyer-modele CONFIRMER=true` (QUICKSTART:59-62) | procédurale | cibles Makefile:632-643 | 🟡 | Cibles présentes et gardes vérifiées ; l'exécution réelle exige une VM Debian 13 + Proxmox → hors production, non testable ici. |
|
||||
| AFF-026 | « Valider à tout moment : `make inventaire-verifier` / `make verifier` » (QUICKSTART:98-101) | fonctionnelle | `make inventaire-verifier` ; `make verifier` | 🟡 | `make verifier` échoue (AFF-006). `make inventaire-verifier` appelle `ansible-inventory --list` qui **exige le mot de passe Vault** (`ERROR! Attempting to decrypt but no vault secrets found` sans `ANSIBLE_VAULT_PASSWORD_FILE`) — prérequis non rappelé à cet endroit. |
|
||||
|
||||
---
|
||||
|
||||
## 3. AGENTS.md
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-030 | « RÈGLE D'OR : `…/production/hosts.yml` est un artefact GÉNÉRÉ » (AGENTS:43) | procédurale | `python3 scripts/instancier.py comparer` | ✅ | Diff vide : l'inventaire est reproductible depuis le plan. |
|
||||
| AFF-031 | « `make instancier-appliquer` refuse si le diff n'est pas vide (`FORCE=1` sinon) » (AGENTS:47) | fonctionnelle | lecture `scripts/instancier.py appliquer` + Makefile:582-583 | ✅ | `appliquer` accepte `--force` (mappé `FORCE`) ; garde diff cohérente avec le comparateur. |
|
||||
| AFF-032 | « VMID / IP / VLAN / passerelle sont dérivés de la `fonction` via la nomenclature » (AGENTS:49) | fonctionnelle | `python3 scripts/serveurs.py verifier` (réconcilie dérivation ↔ inventaire) | ✅ | « Registre des serveurs valide et réconcilié avec l'inventaire. » Dérivation confirmée. |
|
||||
| AFF-033 | « garde-fous : … `node --check` du JS du GUI (`scripts/verifier_gui.py`, dans `make inventaire-verifier`) » (AGENTS:50) | fonctionnelle | `python3 scripts/verifier_gui.py` | ✅ | « JS du GUI : syntaxe valide (node --check). » |
|
||||
| AFF-034 | « Chaque rôle qui utilise `notify` doit contenir son handler dans le rôle » (AGENTS:182) | structurelle | script de recoupement notify↔handlers sur tous les rôles | ✅ | « OK : tout `notify` pointe vers un handler existant dans le même rôle. » |
|
||||
| AFF-035 | « Tout `notify` doit pointer vers un handler existant » (AGENTS:193) | structurelle | idem AFF-034 | ✅ | Aucun `notify` orphelin. |
|
||||
| AFF-036 | « Playbooks … testables avec `--check` autant que possible » (AGENTS:214, 427) | fonctionnelle | `ansible-playbook … --check` sur cibles vivantes | ⚪ | Non testable hors flotte réelle (mission hors production). Recensement des rôles non-check reporté (nécessite des hôtes). |
|
||||
| AFF-037 | État SSH attendu : `PasswordAuthentication no`, `AuthenticationMethods publickey` (AGENTS:538-542) | fonctionnelle | lecture `roles/ssh_baseline/defaults` + `templates/10-setops.conf.j2` | ✅ | Défauts = `no`/`no`/`yes` ; le template code `AuthenticationMethods publickey`. **Le code suit AGENTS.md.** (Contredit CLAUDE.md — voir AFF-050.) |
|
||||
| AFF-038 | « Ne pas maintenir en parallèle `playbooks/socle/` ou `playbooks/durcissement/` » (AGENTS:278) | structurelle | `ls playbooks/` | ✅ | Ces dossiers n'existent pas ; socle/durcissement = groupes `serveur_debian`/`serveur_durci`. |
|
||||
| AFF-039 | « Les playbooks peuvent être classés par domaine : groupes/ maintenance/ monitoring/ networking/ proxmox/ modeles_vm/ web/ database/ identity/ backup/ applications/ » (AGENTS:257-270) | structurelle | `find playbooks -maxdepth 1 -type d` | 🟡 | Seuls `groupes/`, `maintenance/`, `modeles_vm/`, `proxmox/` existent. Formulé au conditionnel (« peuvent ») → aspirationnel, pas une fausse affirmation, mais liste plus large que la réalité. |
|
||||
| AFF-040 | Section « Comportement attendu de **Codex** » (AGENTS:650) | doctrinale | lecture | 🟡 | La section ne nomme que Codex alors qu'elle vaut pour tout agent (Claude inclus). Correctif de titrage prévu en Phase 2 (item 3). |
|
||||
|
||||
---
|
||||
|
||||
## 4. CLAUDE.md
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-050 | « Pendant la construction du template : `PasswordAuthentication yes` » (CLAUDE.md § SSH) | fonctionnelle | `roles/ssh_baseline/defaults/main.yml` | ❌ | Le code déploie `PasswordAuthentication no` **dès la construction** (`ssh_baseline_password_authentication: "no"`, template `AuthenticationMethods publickey`). CLAUDE.md contredit **et** le code **et** AGENTS.md (qui fait autorité). Cause racine : duplication de doctrine. Cible Phase 2 (item 1). |
|
||||
| AFF-051 | « Claude Code doit lire et respecter `AGENTS.md` avant toute modification ; en cas de contradiction, suivre `AGENTS.md` » (CLAUDE.md § Instruction principale) | doctrinale | — | ✅ | Cohérent avec AGENTS.md ; pas de commande de preuve (déclaration de gouvernance). |
|
||||
| AFF-052 | CLAUDE.md redéclare la doctrine de template, SSH, pare-feu, cloud-init, handlers, actions destructives (dupliquée d'AGENTS.md) | structurelle | `diff` conceptuel CLAUDE.md ↔ AGENTS.md | 🟡 | Large duplication → source des dérives (AFF-050). Réduction à un pointeur mince prévue Phase 2 (item 2). |
|
||||
|
||||
---
|
||||
|
||||
## 5. SOLUTION.md
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-060 | « On garde une seule structure : `inventories/ playbooks/ roles/ docs/` » (SOLUTION:11-18) | structurelle | `ls` racine | 🟡 | Réel : aussi `scripts/`, `filter_plugins/`, `exemples/`, `wiki/`, `instance/`. Description obsolète (antérieure au plan/GUI). |
|
||||
| AFF-061 | Commande template : `ansible-playbook … debian13_proxmox_preparer.yml --ask-pass --ask-become-pass` (SOLUTION:24-25) | procédurale | comparaison avec la doctrine SSH clé-only | 🟡 | `--ask-pass` (mot de passe SSH) contredit l'accès **par clé dès le départ** d'AGENTS.md (AFF-037). SOLUTION.md décrit un flux périmé (mot de passe SSH) ; le flux réel = clé via cloud-init. |
|
||||
| AFF-062 | « Le pare-feu `nftables` est installé et préparé, mais pas activé par défaut dans le template » (SOLUTION:60 ; AGENTS:559 ; CLAUDE.md) | fonctionnelle | `grep enabled roles/nftables_baseline/defaults/main.yml` | ✅ | `nftables_baseline_enabled: false`. `preparer-modele` inclut le rôle et affiche « nftables est installé mais désactivé par défaut ». |
|
||||
|
||||
---
|
||||
|
||||
## 6. Aide du Makefile (`make`)
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-070 | « `make site-verifier` valide la cohérence couches/graphe » (Makefile aide:133) | fonctionnelle | `python3 scripts/orchestrer.py verifier` | ✅ | « Orchestration cohérente : 30 groupes classés, aucun cycle, aucune arête en arrière. » |
|
||||
| AFF-071 | « `make flux` → `docs/registre-flux.md` + `instance/flux-genere/*.nft` » (Makefile aide:146) | fonctionnelle | `python3 scripts/resoudre_flux.py verifier` ; en-tête de `docs/registre-flux.md` | ✅ | « Flux cohérents : 29 rôles, 63 flux ». Le registre porte l'en-tête « GÉNÉRÉ par `scripts/resoudre_flux.py` ». |
|
||||
| AFF-072 | « `make deployer-tout` — déploiement orchestré de la flotte ; garde `CONFIRMER` » (Makefile aide:138) | fonctionnelle | lecture Makefile:352-357 | 🟡 | Garde `CONFIRMER=true` vérifiée (refuse sinon, rc=2). Déploiement réel hors production → non exécuté. |
|
||||
| AFF-073 | « `make wiki-publier` … refuse sans `WIKI_REMOTE` » (Makefile aide:151) | fonctionnelle | lecture Makefile:320-326 | ✅ | Garde présente : refus + message d'usage si `WIKI_REMOTE` vide. |
|
||||
| AFF-074 | « `make devis-reseau` — dérivé des nomenclatures » (Makefile:309) | fonctionnelle | `python3 scripts/devis_reseau.py` | 🟡 | Dérive des instances fédérées `../*/plan/nomenclature.yml` (schéma `ip-miroir`) ; sortie dépend du voisinage de dépôts → vérifiable seulement dans un contexte fédéré. |
|
||||
| AFF-075 | Cibles dépréciées `make hote-planifier / hote-ajouter / hote-groupes` renvoient vers le plan (README:118 ; Makefile:237) | procédurale | `make hote-planifier` | ✅ | Stub présent : message « Cible dépréciée … » + `exit 2`. |
|
||||
|
||||
---
|
||||
|
||||
## 7. docs/ — runbooks et cohérence des commandes
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-080 | « `make help` — liste des cibles » (docs/config-proxmox.md:114) | procédurale | `grep -E '^help:' Makefile` | ❌ | Aucune cible `help`. La cible réelle est `aide` (défaut). Commande citée inexistante. |
|
||||
| AFF-081 | « `make syntax-template` » (docs/modeles_vm/debian13-proxmox.md:77) | procédurale | `grep syntax-template Makefile` | ❌ | Aucune cible `syntax-template`. Réel : `syntaxe-modele`. Nom périmé. |
|
||||
| AFF-082 | « `make syntax-template` » (docs/MISE-A-JOUR-CODEX-CLAUDE.md:146) | procédurale | idem | ❌ | Même nom périmé, second emplacement. |
|
||||
| AFF-083 | Tous les docs cités par README/QUICKSTART/AGENTS existent (`plan-et-generation`, `positionnement`, `bindings-conception`, `flux-conception`, `couches-deploiement.yml`, `registre-flux.md`, `carte-set-ops`, `runbooks-exploitation`, `vm-lifecycle`, `procedure-template-…`, `dns-interne`, `nomenclature-vm`, `config-proxmox`, `catalogue-services`, `dimensionnement-ressources`, `exemples/vault.exemple.yml`, `exemples/instance.exemple.yml`) | structurelle | `test -f` sur chaque chemin | ✅ | 17/17 présents. |
|
||||
| AFF-084 | « `docs/dependances-groupes.yml` — chaque groupe existant y figure, aucune entrée ne référence un rôle supprimé » (AGENTS:345,413) | structurelle | `verifier-dependances` (AFF-009) | ✅ | Validé ; graphe sans cycle, tri topologique réussi (via `orchestrer verifier`). |
|
||||
| AFF-097 | Chemins d'inventaire `lab/` codés en dur pour la config/voûte **Proxmox** et le template, alors que `make config`/`cloner-vm` résolvent `lab > principal > production` (modèle `socle` = `production/`) | procédurale | `grep -rn 'inventories/lab/group_vars' docs playbooks` ; `scripts/config_proxmox.py:31` | 🟡 | **Découvert en Phase 3 (lot A).** Reste `lab/` codé en dur dans `docs/vm-lifecycle.md:171`, `docs/procedure-template-debian13-proxmox.md:741,747`, `docs/modeles_vm/debian13-proxmox.md:89`, `playbooks/proxmox/README.md:31,43,49,50`, `playbooks/proxmox/cloner_vm_debian.yml:45` (message). Non bloquant (l'outillage résout), mais trompeur pour le socle. **Différé** au lot « voûte Proxmox » (couplé à AFF-098). |
|
||||
| AFF-099 | Le modèle public `socle` livre un `plan/domaines.yml` **invalide** : `exemple.internal` en `autorite: interne`, rejeté par le validateur (`auto-heberge`/`delegue`/`primaire-cache` attendus) | fonctionnelle | `SETOPS_INSTANCE=exemples/modeles/socle python3 scripts/domaines.py verifier` | ❌ | **Découvert Phase 5.** Un opérateur suivant QUICKSTART bute à la validation (étape 9 / `make inventaire-verifier`). `interne` = valeur périmée (déjà corrigée ailleurs en `auto-heberge`, cf. CHANGELOG 2026-07-07). |
|
||||
| AFF-100 | `instancier.py`/`inventory_gui.py` résolvent `principal > production` mais **retombent sur `principal`** quand aucun `hosts.yml` n'existe encore ; le socle est en `production/` → 1ʳᵉ génération dans `principal/`, à côté des `group_vars` restés en `production/` (split-brain) | fonctionnelle | parcours QUICKSTART hors-ligne sur une copie du socle (`generer`→`appliquer`) | ❌ | **Découvert Phase 5.** `config_proxmox.py` détecte le répertoire existant (→ `production/`) ; `instancier`/GUI non → incohérence. QUICKSTART étapes 6-9 cassées pour le socle. |
|
||||
| AFF-098 | Contradiction fonctionnelle voûte Proxmox : `make config` écrit le token dans la voûte unifiée `all/vault.yml` (et `config-proxmox.md` dit de **supprimer** `proxmox.vault.yml`), mais le clonage `cloner_vm_debian.yml` (lancé `-i localhost,`) ne charge le secret **que** depuis `proxmox.vault.yml`/`proxmox.local.yml` ou les variables d'env — **jamais** `all/vault.yml` | fonctionnelle | lecture `playbooks/proxmox/cloner_vm_debian.yml:11-12,51-64` vs `scripts/config_proxmox.py:35` | ❌ | **Découvert en Phase 3 (lot B).** Un opérateur qui suit la voûte unifiée met le token dans `all/vault.yml` → `make creer-vm` échoue l'assert (`proxmox_api_token_secret` vide), sauf variables d'env ou `proxmox.vault.yml` conservé. **Traitement B (code)** : faire lire `all/vault.yml` par le clonage, ou faire écrire `proxmox.vault.yml` par `make config`. À arbitrer dans le lot « voûte Proxmox ». |
|
||||
|
||||
---
|
||||
|
||||
## 8. wiki/
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-090 | Le wiki pédagogique fournit des unités d'apprentissage par service (`wiki/*.md`) publiables dans Forgejo | structurelle | `ls wiki/*.md` | ✅ | 16 pages de contenu (+ `README.md` + `_Sidebar.md`) ; 17 publiées par `wiki-publier` (README exclu). |
|
||||
| AFF-091 | Contenu conceptuel du wiki (OIDC, PKI, DNS, sauvegardes, idempotence, RBAC…) | doctrinale | — | ⚪ | Contenu pédagogique = affirmations conceptuelles, non rattachées à une preuve exécutable du dépôt. Hors périmètre de preuve automatisable. |
|
||||
|
||||
---
|
||||
|
||||
## 9. GUI (libellés)
|
||||
|
||||
| ID | Affirmation (source) | Type | Commande de preuve | Statut | Écart constaté |
|
||||
|---|---|---|---|---|---|
|
||||
| AFF-095 | « La vue **Inventaire** est en lecture seule ; vues éditables **Serveurs**, **Applications**, **Domaines** ; vues lecture seule **Réseau**, **Flux**, **Couches** » (README:41 ; AGENTS:48 ; CHANGELOG) | fonctionnelle | démarrage headless de `scripts/inventory_gui.py` + inspection des routes/onglets | 🟡 | Les routes backend et l'import des validateurs sont présents dans `scripts/inventory_gui.py` ; le rendu des onglets et le caractère « lecture seule » ne sont pas vérifiés sans lancer le serveur + un navigateur (non fait — pas de commande de preuve headless au registre). |
|
||||
| AFF-096 | « GUI 100 % française » (CHANGELOG 2026-07-03) | structurelle | inspection des chaînes du GUI | ⚪ | Vérifiable seulement par revue exhaustive des libellés rendus ; non automatisé ici. |
|
||||
|
||||
---
|
||||
|
||||
## Décompte
|
||||
|
||||
Recompte exact par ID (numérotation à trous AFF-001 → AFF-096, **54 affirmations**).
|
||||
|
||||
| Statut | Nombre |
|
||||
|---|---|
|
||||
| ✅ prouvée | 30 |
|
||||
| 🟡 partielle | 13 |
|
||||
| ❌ fausse | 8 |
|
||||
| ⚪ invérifiable localement | 3 |
|
||||
| **Total** | **54** |
|
||||
|
||||
Détail :
|
||||
|
||||
- ✅ (30) : AFF-001, 003, 004, 005, 008, 009, 010, 011, 012, 014, 015, 022, 024, 030, 031, 032, 033, 034, 035, 037, 038, 051, 062, 070, 071, 073, 075, 083, 084, 090.
|
||||
- 🟡 (13) : AFF-002, 007, 013, 025, 026, 039, 040, 052, 060, 061, 072, 074, 095.
|
||||
- ❌ (8) : AFF-006, 020, 021, 023, 050, 080, 081, 082.
|
||||
- ⚪ (3) : AFF-036, 091, 096.
|
||||
|
||||
> La Phase 4 (`make prouver`) rattachera chaque ✅ à sa section de preuve et figera le
|
||||
> compte.
|
||||
|
||||
---
|
||||
|
||||
## Les dix écarts les plus graves
|
||||
|
||||
Classés par risque pour un opérateur qui suit la doc **à la lettre, sans IA**.
|
||||
|
||||
1. **AFF-021 / AFF-020 — QUICKSTART renvoie à un modèle inexistant.**
|
||||
`cp -r exemples/modeles/presence-web ../mon-instance` échoue : seul `socle` est
|
||||
public. **L'opérateur est bloqué dès l'étape 2** du guide de démarrage. Risque
|
||||
maximal : c'est la toute première action concrète du parcours « zéro → écosystème ».
|
||||
|
||||
2. **AFF-006 — `make verifier` échoue sur un dépôt propre.**
|
||||
`ansible-lint` rapporte 33 échecs (surtout `name[play]` sur le `site.yml` **généré**
|
||||
par l'outil lui-même, + `risky-shell-pipe`). La commande de validation vantée par
|
||||
README et QUICKSTART ne passe pas → l'opérateur croit son dépôt cassé, ou apprend à
|
||||
ignorer l'échec (perte de confiance dans le harnais).
|
||||
|
||||
3. **AFF-050 — Contradiction SSH doc vs code (sécurité).**
|
||||
`CLAUDE.md` prescrit `PasswordAuthentication yes` pendant la construction ; le code
|
||||
et `AGENTS.md` imposent `no` + `AuthenticationMethods publickey`. Un opérateur qui
|
||||
suit CLAUDE.md attend un accès mot de passe qui n'existe pas — et la doctrine de
|
||||
sécurité affichée est contradictoire.
|
||||
|
||||
4. **AFF-023 — Chemin de voûte faux dans QUICKSTART.**
|
||||
L'étape 4 place les secrets dans `inventories/lab/group_vars/all/vault.yml`, mais le
|
||||
modèle public `socle` n'a pas d'inventaire `lab/` (seulement `production/`).
|
||||
L'opérateur crée sa voûte au mauvais endroit → déchiffrement introuvable au déploiement.
|
||||
|
||||
5. **AFF-026 — `make inventaire-verifier` exige le Vault sans le dire.**
|
||||
La validation « à tout moment » appelle `ansible-inventory --list` qui échoue
|
||||
(`no vault secrets found`) sans `ANSIBLE_VAULT_PASSWORD_FILE`. Prérequis non rappelé
|
||||
au point d'usage → échec surprenant pour qui n'a pas exporté sa clé.
|
||||
|
||||
6. **AFF-002 — Promesse « exploitable sans IA » entamée par 1–5.**
|
||||
Le principe fondateur (AGENTS principe 10) est **partiellement démenti par les faits** :
|
||||
le parcours documenté comporte au moins deux ruptures dures (modèle absent, `make
|
||||
verifier` cassé). C'est le méta-risque : la doc ne mène pas seule au succès.
|
||||
|
||||
7. **AFF-080 / AFF-081 / AFF-082 — Commandes `make` périmées dans la doc.**
|
||||
`make help` (→ `aide`) et `make syntax-template` (→ `syntaxe-modele`, cité 2×)
|
||||
n'existent pas. L'opérateur tape une commande qui renvoie une erreur `make`.
|
||||
|
||||
8. **AFF-061 — SOLUTION.md décrit un flux SSH périmé.**
|
||||
`--ask-pass --ask-become-pass` suppose un accès par mot de passe contraire au modèle
|
||||
clé-only réel. Suivre SOLUTION.md pour construire le template mène à un flux qui ne
|
||||
correspond plus au code.
|
||||
|
||||
9. **AFF-060 — SOLUTION.md décrit une arborescence obsolète.**
|
||||
« Une seule structure : inventories/playbooks/roles/docs/ » ignore `scripts/`,
|
||||
`instance/`, `filter_plugins/`, `wiki/`, `exemples/` — désoriente un nouveau lecteur
|
||||
sur l'endroit où vivent le plan et le GUI.
|
||||
|
||||
10. **AFF-052 / AFF-040 — Doctrine dupliquée et titrage « Codex ».**
|
||||
La duplication CLAUDE.md ↔ AGENTS.md est la **cause racine** de la contradiction n°3 ;
|
||||
la section « Comportement attendu de Codex » sous-entend à tort qu'elle ne vise pas
|
||||
Claude. Risque de gouvernance : deux sources d'autorité qui divergent avec le temps.
|
||||
|
||||
---
|
||||
|
||||
## Journal des traitements
|
||||
|
||||
Le tableau ci-dessus est le **snapshot d'audit Phase 1 (2026-07-20)** et n'est pas
|
||||
réécrit. Les traitements appliqués ensuite sont consignés ici, datés et traçables.
|
||||
|
||||
### Phase 2 — 2026-07-20 (3 incohérences internes pré-cadrées)
|
||||
|
||||
- **AFF-050 (❌ → résolu)** — Doctrine SSH contradictoire. Vérifié que le code applique
|
||||
déjà `PasswordAuthentication no` + `AuthenticationMethods publickey` dès la construction
|
||||
(aligné sur `AGENTS.md`, cf. AFF-037). **Aucun correctif de code** ; la doctrine SSH a
|
||||
été **retirée de `CLAUDE.md`**.
|
||||
- **AFF-052 (🟡 → résolu)** — Duplication CLAUDE.md ↔ AGENTS.md. `CLAUDE.md` réduit à un
|
||||
pointeur mince (autorité d'`AGENTS.md` + 5 règles absolues) ; cause racine d'AFF-050 supprimée.
|
||||
- **AFF-040 (🟡 → résolu)** — Section « Comportement attendu de **Codex** » renommée
|
||||
« …des agents IA » dans `AGENTS.md`, avec mention explicite qu'elle vaut pour tout agent.
|
||||
|
||||
Reste ouvert après Phase 2 : les 5 ❌ restants (AFF-006, 020, 021, 023, 080/081/082) et
|
||||
les 🟡 relèvent de la **Phase 3 (arbitrage)**.
|
||||
|
||||
### Phase 3 — Lot A « doc de démarrage » — 2026-07-20
|
||||
|
||||
- **AFF-020/021 (❌ → résolu, A)** — `QUICKSTART.md` étape 2 : ne propose plus que le
|
||||
modèle public `socle` (`cp -r exemples/modeles/socle …`) ; les modèles assemblés
|
||||
renvoyés au dépôt privé `Set-OPS-modeles`, cohérent avec `exemples/modeles/README.md`.
|
||||
- **AFF-023 (❌ → résolu, A + correctif d'exemple)** — Chemin de voûte corrigé
|
||||
`lab/` → `production/` dans `QUICKSTART.md`, `exemples/vault.exemple.yml` et
|
||||
`docs/config-proxmox.md`. **Piège corrigé** : le modèle `socle` livrait ses intrants en
|
||||
`production/group_vars/all.yml` (forme fichier) ; ajouter `all/vault.yml` (forme dossier)
|
||||
**masque silencieusement** le fichier `all.yml` (vérifié empiriquement : seul le dossier
|
||||
est chargé). Le modèle a été converti en forme dossier (`all/10-intrants.yml`), alignée
|
||||
sur l'instance prouvée `principal`. Validé : `ansible-inventory --host` charge bien
|
||||
`domaine_interne` depuis la nouvelle disposition.
|
||||
- **AFF-080/081/082 (❌ → résolu, A)** — Commandes `make` périmées corrigées :
|
||||
`make help` → `make` (`docs/config-proxmox.md`) ; `make syntax-template` →
|
||||
`make syntaxe-modele` (`docs/modeles_vm/debian13-proxmox.md`,
|
||||
`docs/MISE-A-JOUR-CODEX-CLAUDE.md`).
|
||||
- **AFF-097 (🟡, découvert)** — `lab/` codé en dur restant dans les docs template/clone
|
||||
(étapes 5+). Consigné ci-dessus ; **non traité dans ce lot** (lot séparé à prévoir).
|
||||
|
||||
### Phase 3 — Lot B « doc (prose) » — 2026-07-20
|
||||
|
||||
- **AFF-026 (🟡 → résolu, A)** — `QUICKSTART.md` § « Valider à tout moment » : note ajoutée
|
||||
que `inventaire-verifier`/`verifier` chargent l'inventaire complet et exigent
|
||||
`ANSIBLE_VAULT_PASSWORD_FILE` (étape 4), sinon « no vault secrets found ».
|
||||
- **AFF-039 (🟡 → résolu, A)** — `AGENTS.md` : note ajoutée sous la liste des sous-dossiers
|
||||
`playbooks/` — seuls `groupes/`, `maintenance/`, `modeles_vm/`, `proxmox/` sont peuplés ;
|
||||
les autres sont **prospectifs**.
|
||||
- **AFF-060/061 (🟡 → résolu, A)** — `SOLUTION.md` : bannière « document historique » ajoutée
|
||||
(supplanté par README/QUICKSTART/`make`) ; arborescence complétée (`scripts/`, `wiki/`,
|
||||
`instance/`…) ; commande de construction `--ask-pass` (mot de passe) remplacée par
|
||||
`make preparer-modele` (accès par clé, aligné `AGENTS.md`).
|
||||
- **AFF-002 (🟡)** — « exploitable sans IA » : **pas de reformulation** (c'est un engagement
|
||||
fondateur légitime, pas une sur-affirmation). Son seul écart résiduel est AFF-006
|
||||
(`make verifier` casse) → bascule ✅ une fois AFF-006 traité (lot C, B). Aucune édition.
|
||||
- **AFF-007 (🟡)** — `web-frontal-01` : **assumé** comme nom d'exemple/placeholder (usage
|
||||
cohérent dans tout le README) ; aucune édition.
|
||||
- **AFF-097 / AFF-098** — **différés** au lot « voûte Proxmox » : AFF-098 (contradiction
|
||||
fonctionnelle `all/vault.yml` ↔ clonage, traitement B) est couplé à AFF-097 (mêmes
|
||||
fichiers) ; les traiter ensemble évite un double passage.
|
||||
|
||||
### Phase 3 — Lot « voûte Proxmox » — 2026-07-20
|
||||
|
||||
- **AFF-098 (❌ → résolu, B, option a)** — Le clonage lit désormais la voûte **unifiée**.
|
||||
`playbooks/proxmox/cloner_vm_debian.yml` : `all/vault.yml` ajouté à la liste des sources
|
||||
de secrets Proxmox (chargé via le filtre `is search('vault')`, autoritaire car en
|
||||
dernier). `Makefile` (`cloner-vm`) : la détection de voûte chiffrée cherche d'abord
|
||||
`group_vars/all/vault.yml` puis `proxmox.vault.yml` (déclenche `--ask-vault-pass`).
|
||||
Messages de tâche/rescousse/assert rendus génériques. **Validé** : `--syntax-check` OK,
|
||||
`ansible-lint` 0 échec sur le playbook, et **test fonctionnel** (mini-instance en clair) —
|
||||
le token est bien chargé depuis `all/vault.yml` et l'hôte depuis `proxmox.yml`
|
||||
(assert vert). `proxmox.vault.yml` reste accepté en compatibilité.
|
||||
- **AFF-097 (🟡 → résolu, A)** — `lab/` codé en dur retiré des docs Proxmox/template :
|
||||
`playbooks/proxmox/README.md`, `docs/procedure-template-debian13-proxmox.md`,
|
||||
`docs/vm-lifecycle.md`, `docs/modeles_vm/debian13-proxmox.md` → `production/` + voûte
|
||||
unifiée. Plus aucune référence `inventories/lab/group_vars` dans les fichiers suivis
|
||||
(hors `tmp/` gitignoré et la section *migration* de `config-proxmox.md`, intentionnelle).
|
||||
|
||||
### Phase 3 — Lot C « make verifier vert » — 2026-07-20
|
||||
|
||||
- **AFF-006 (❌ → résolu, B)** — `ansible-lint` passe de **33 échecs à 0** (profil monté
|
||||
`min` → `production`), donc `make lint` **rc=0**. Trois causes traitées :
|
||||
- **30× `name[play]`** sur le `site.yml` **généré** → `scripts/orchestrer.py` (`rendre_site`)
|
||||
émet désormais `- name: Couche <c> — groupe <g>` avant chaque `import_playbook` ;
|
||||
`site.yml` régénéré (`make site`). Logique d'orchestration inchangée (`orchestrer verifier`
|
||||
OK, 30 groupes, syntax-check OK).
|
||||
- **2× `risky-shell-pipe`** (`playbooks/valider.yml`) → `set -o pipefail;` +
|
||||
`executable: /bin/bash` sur les deux tâches shell à pipe.
|
||||
- **1× `name[template]`** (`playbooks/proxmox/supprimer_vm_debian.yml`) → Jinja déplacé en
|
||||
fin de `name` (`… (VMID {{ … }})`).
|
||||
- **Validé** : `make lint` rc=0 ; `make verifier` franchit lint + test (4) + `site-verifier`
|
||||
+ `flux-verifier` + `syntaxe` (35 playbooks) — **toutes vertes**. Seule `inventaire-verifier`
|
||||
exige la voûte de l'opérateur (`ANSIBLE_VAULT_PASSWORD_FILE`), prérequis documenté (AFF-026).
|
||||
- **AFF-002 (🟡 → ✅)** — « exploitable sans IA » : ses deux écarts résiduels sont soldés
|
||||
(parcours QUICKSTART, lot A ; `make verifier`/lint, ce lot). Bascule ✅, sous le prérequis
|
||||
Vault désormais rappelé (AFF-026).
|
||||
|
||||
### Phase 5 — Boucle documentaire — 2026-07-20
|
||||
|
||||
Relecture de cohérence bout-en-bout (README, QUICKSTART, docs, wiki) contre le code final,
|
||||
au regard du critère du principe 10 : *un opérateur sans IA exécute chaque procédure sans
|
||||
commande morte, chemin faux ni étape manquante.*
|
||||
|
||||
- **Sweep commandes/chemins** : toutes les commandes `make` citées existent ; jetons périmés
|
||||
(`presence-web` hors contexte, `syntax-template`, `make help`, `--ask-pass`, voûte `lab/`)
|
||||
soldés (les occurrences restantes ne sont que dans `CHANGELOG.md`/`docs/audit`, historiques).
|
||||
- **AFF-099 (❌ → résolu, B)** — `exemples/modeles/socle/plan/domaines.yml` : `interne` →
|
||||
`auto-heberge`. Le modèle public valide désormais.
|
||||
- **AFF-100 (❌ → résolu, B)** — `instancier.py` et `inventory_gui.py` : le repli sans
|
||||
`hosts.yml` vise maintenant le **répertoire d'inventaire existant** (→ `production/` pour le
|
||||
socle) avant de retomber sur `principal`. Instances existantes (avec `hosts.yml`) inchangées.
|
||||
- **Cohérence GUI/doc** — `docs/intrants-communs.md` : `group_vars/all.yml` →
|
||||
`group_vars/all/10-intrants.yml` (le GUI écrit déjà en forme dossier). `QUICKSTART.md`
|
||||
étape 8 : exemple `serveur_postgresql` → `serveur_powerdns` (groupe présent dans le socle).
|
||||
- **Preuve bout-en-bout (hors-ligne)** : parcours QUICKSTART rejoué sur une copie du socle —
|
||||
`instancier generer`→`comparer`→`appliquer --force` **écrit `production/hosts.yml`**, diff
|
||||
**vide** ensuite (idempotence), et les 4 validateurs verts (`serveurs` **réconcilié**).
|
||||
Non-régression vérifiée sur l'instance réelle `principal` (diff vide, cible inchangée).
|
||||
|
||||
## Couverture par `make prouver` (Phase 4)
|
||||
|
||||
Chaque affirmation ✅ est soit **rejouée** par une preuve de `make prouver`
|
||||
(`scripts/prouver.py` → `docs/audit/preuve-<date>.md`), soit **structurelle/doctrinale**
|
||||
(vérifiée à l'audit, rien d'exécutable à rejouer en boucle).
|
||||
|
||||
| Affirmation ✅ | Preuve `make prouver` |
|
||||
|---|---|
|
||||
| AFF-001, 004, 030, 031, 032 | **P03** (diff-vide) |
|
||||
| AFF-003 | **P06** (validateurs de registres) |
|
||||
| AFF-008 | **P04** (groupes ↔ playbooks) |
|
||||
| AFF-009, 084 | **P05** (dépendances) |
|
||||
| AFF-010, 011, 012 | **P12** (existence des runbooks) |
|
||||
| AFF-015, 022, 037, 038, 062 | **P13** (invariants structurels) |
|
||||
| AFF-033 | **P07** (GUI node --check) |
|
||||
| AFF-034, 035 | **P10** (handlers ↔ notify) |
|
||||
| AFF-070 | **P08** (orchestration) |
|
||||
| AFF-071 | **P09** (flux) |
|
||||
| AFF-083 | **P11** (syntaxe) + **P12** |
|
||||
| AFF-097 | **P14** (pas de `lab/` codé en dur) |
|
||||
| AFF-002, 006 | **P01** (lint) — + parcours démarrage corrigé |
|
||||
| AFF-030 (inventaire complet) | **P15** (voûte requise ; sauté sinon) |
|
||||
|
||||
**Structurelles/doctrinales, hors harnais récurrent** (vérifiées à l'audit) : AFF-005
|
||||
(`make`=aide), AFF-014 (ciblage `groupe:&hotes_actifs`), AFF-024 (`instancier-appliquer`,
|
||||
couvert indirectement par P03), AFF-051 (autorité d'`AGENTS.md`), AFF-073/075 (gardes
|
||||
`make wiki-publier`/`hote-planifier`), AFF-090 (comptage wiki).
|
||||
|
||||
**Déclarations d'intention (⚪ — assumées, jamais présentées comme prouvées)** : AFF-036
|
||||
(`--check` réel), AFF-091 (contenu wiki), AFF-096 (GUI 100 % français), AFF-007
|
||||
(placeholder `web-frontal-01`). Reprises dans la section dédiée de chaque `preuve-<date>.md`.
|
||||
|
||||
## Limites de cet audit
|
||||
|
||||
- **Hors production** : toute affirmation nécessitant une VM, Proxmox, le réseau ou la
|
||||
flotte vivante (`make valider`, `make deployer*`, `make creer-vm`, `--check` réel, les
|
||||
très nombreux « **prouvé sur VM réelles** » du `CHANGELOG.md`) est marquée ⚪ ou 🟡 —
|
||||
**non rejouable localement par conception**. Le `CHANGELOG.md` n'étant pas une surface
|
||||
publique listée, ses affirmations « prouvé » ne sont pas enregistrées ici ; à noter que
|
||||
`AGENTS.md` (« pas encore exécuté contre des VM réelles », « ne jamais présenter un rôle
|
||||
non déployé comme en production ») encadre correctement cette réserve.
|
||||
- **GUI** : pas de preuve headless au registre (AFF-095/096 en 🟡/⚪).
|
||||
- **Vault** : l'instance courante chiffre `group_vars/all/vault.yml` ; les commandes qui
|
||||
chargent l'inventaire complet via Ansible exigent le mot de passe Vault, non fourni
|
||||
pendant l'audit (les validateurs Python lisent `hosts.yml`/le plan directement et
|
||||
n'en ont pas besoin — d'où leur usage ici).
|
||||
- **Numérotation à trous** : les ID sautent (AFF-001…096) pour laisser de la place par
|
||||
surface ; ce n'est pas une omission.
|
||||
52
docs/audit/preuve-2026-07-20.md
Normal file
52
docs/audit/preuve-2026-07-20.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Preuve de conformite — Set-OPS — 2026-07-20
|
||||
|
||||
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (15 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | |
|
||||
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | 4 tests passes. |
|
||||
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 63 flux, schéma + matrice OK. |
|
||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||
|
||||
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||
|
||||
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||
comme declarations d'intention, non comme preuves :
|
||||
|
||||
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||
contre une flotte vivante.
|
||||
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||
|
||||
_Rapport genere le 2026-07-20._
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
la connexion au cluster Proxmox et les valeurs de clonage par défaut. Il pose
|
||||
**16 paramètres non sensibles** puis propose de saisir les **secrets API**.
|
||||
|
||||
- Non sensibles → `instance/inventories/lab/group_vars/proxmox.yml`
|
||||
- Secrets → **voûte unique** `instance/inventories/lab/group_vars/all/vault.yml`
|
||||
- Non sensibles → `instance/inventories/production/group_vars/proxmox.yml`
|
||||
- Secrets → **voûte unique** `instance/inventories/production/group_vars/all/vault.yml`
|
||||
(chiffrée par `ansible-vault`), qui contient **tous** les secrets de l'instance
|
||||
(token Proxmox + `vault_*`). Voir [§4](#4-secrets-de-linstance-).
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ L'assistant demande « Configurer la voûte de secrets maintenant ». Si `oui` :
|
|||
Si tu as déjà une `proxmox.vault.yml`, déplace son contenu dans la voûte unique :
|
||||
|
||||
```bash
|
||||
cd instance/inventories/lab/group_vars
|
||||
cd instance/inventories/production/group_vars
|
||||
cp ../../../../exemples/vault.exemple.yml all/vault.yml
|
||||
ansible-vault view proxmox.vault.yml # relève token_id + secret
|
||||
ansible-vault edit all/vault.yml # colle-les, renseigne le reste
|
||||
|
|
@ -111,4 +111,4 @@ Dans l'interface Proxmox (ou en CLI `pveum`) :
|
|||
- [`intrants-communs.md`](intrants-communs.md) — tous les intrants communs de l'écosystème.
|
||||
- [`nomenclature-vm.md`](nomenclature-vm.md) — réseau, VLAN, fonctions.
|
||||
- [`vm-lifecycle.md`](vm-lifecycle.md) — cycle de vie d'une VM (création → conformité).
|
||||
- `make help` — liste des cibles.
|
||||
- `make` — liste des cibles (l'aide est la cible par défaut).
|
||||
|
|
|
|||
|
|
@ -92,9 +92,10 @@ Un panneau « Intrants de base » dans le GUI :
|
|||
- les **secrets** ne sont **jamais** stockés en clair par le GUI (cf. §H) — il en
|
||||
gère au plus les *références*, l'édition réelle restant côté Vault.
|
||||
|
||||
Persistance : les fichiers existants (`group_vars/all.yml`, `proxmox.yml`,
|
||||
`modeles_vm.yml`, `plan/nomenclature.yml`). À détailler dans la note de conception
|
||||
de la fonctionnalité GUI.
|
||||
Persistance : les fichiers de l'instance (`group_vars/all/10-intrants.yml` — forme
|
||||
dossier ; la forme plate `group_vars/all.yml` reste lue en compatibilité —, `proxmox.yml`,
|
||||
`modeles_vm.yml`, `plan/nomenclature.yml`). À détailler dans la note de conception de la
|
||||
fonctionnalité GUI.
|
||||
|
||||
## 4. Incohérences repérées (à corriger)
|
||||
- `fuseau_horaire` défini en **lab** seulement, absent de **production**.
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ Avant l'exécution :
|
|||
|
||||
```bash
|
||||
git status --short
|
||||
make syntax-template
|
||||
make syntaxe-modele
|
||||
```
|
||||
|
||||
L'inventaire doit contenir la VM dans le groupe :
|
||||
|
|
@ -86,7 +86,7 @@ modeles_vm
|
|||
Les variables du template sont dans :
|
||||
|
||||
```text
|
||||
instance/inventories/lab/group_vars/modeles_vm.yml
|
||||
instance/inventories/production/group_vars/modeles_vm.yml
|
||||
```
|
||||
|
||||
### Commande de préparation
|
||||
|
|
|
|||
|
|
@ -738,13 +738,14 @@ Après conversion du modèle, le flux normal passe par `make` et l'API Proxmox.
|
|||
Les paramètres communs Proxmox sont dans :
|
||||
|
||||
```text
|
||||
instance/inventories/lab/group_vars/proxmox.yml
|
||||
instance/inventories/production/group_vars/proxmox.yml
|
||||
```
|
||||
|
||||
Les secrets d'API doivent être dans un fichier Vault non versionné :
|
||||
Les secrets d'API vivent dans la voûte unifiée de l'instance (le token Proxmox aux côtés
|
||||
des autres `vault_*`), semée par `make config` :
|
||||
|
||||
```text
|
||||
instance/inventories/lab/group_vars/proxmox.vault.yml
|
||||
instance/inventories/production/group_vars/all/vault.yml
|
||||
```
|
||||
|
||||
Créer le clone et l'ajouter à l'inventaire :
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ Cette commande limite le playbook au croisement entre le groupe demandé et `hot
|
|||
|
||||
## 9. Variables template et conformité
|
||||
|
||||
Les variables du template servent à construire le golden template dans `instance/inventories/lab/group_vars/modeles_vm.yml`.
|
||||
Les variables du template servent à construire le golden template dans `instance/inventories/production/group_vars/modeles_vm.yml`.
|
||||
|
||||
Les variables de conformité servent aux VM déployées dans `instance/inventories/production/group_vars/serveur_debian.yml`.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
---
|
||||
domaines_publics:
|
||||
exemple.ca: { autorite: primaire-cache, edge: serveur_nginx, secondaires: [], dnssec: false }
|
||||
exemple.internal: { autorite: interne, edge: serveur_nginx, secondaires: [], dnssec: false }
|
||||
exemple.internal: { autorite: auto-heberge, edge: serveur_nginx, secondaires: [], dnssec: false }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
# Voûte UNIQUE de l'instance — TOUS les secrets, un seul fichier, un seul mot de passe.
|
||||
#
|
||||
# Installation :
|
||||
# 1. Copier ce gabarit vers la voûte de chaque environnement :
|
||||
# cp exemples/vault.exemple.yml instance/inventories/lab/group_vars/all/vault.yml
|
||||
# Installation (un seul inventaire par instance : « production » dans le modèle socle) :
|
||||
# 1. Copier ce gabarit vers la voûte de l'instance :
|
||||
# cp exemples/vault.exemple.yml instance/inventories/production/group_vars/all/vault.yml
|
||||
# 2. Renseigner les valeurs, puis chiffrer :
|
||||
# ansible-vault encrypt instance/inventories/lab/group_vars/all/vault.yml
|
||||
# ansible-vault encrypt instance/inventories/production/group_vars/all/vault.yml
|
||||
# 3. Éditer ensuite sans jamais déchiffrer sur disque :
|
||||
# ansible-vault edit instance/inventories/lab/group_vars/all/vault.yml
|
||||
# ansible-vault edit instance/inventories/production/group_vars/all/vault.yml
|
||||
#
|
||||
# Ne JAMAIS versionner le fichier vault.yml (couvert par .gitignore).
|
||||
# Le GUI ne stocke ni n'affiche ces valeurs : il n'en montre que les NOMS
|
||||
|
|
|
|||
|
|
@ -28,27 +28,25 @@ python3 -c 'import proxmoxer'
|
|||
Les paramètres non sensibles sont dans :
|
||||
|
||||
```text
|
||||
instance/inventories/lab/group_vars/proxmox.yml
|
||||
instance/inventories/production/group_vars/proxmox.yml
|
||||
```
|
||||
|
||||
Assistant terminal :
|
||||
Assistant terminal (écrit `proxmox.yml` et place le token dans la voûte unifiée) :
|
||||
|
||||
```bash
|
||||
make config
|
||||
```
|
||||
|
||||
Les secrets d'API doivent être dans un fichier Vault non versionné :
|
||||
Les secrets d'API vivent dans la **voûte unifiée de l'instance** — le token Proxmox aux
|
||||
côtés des autres `vault_*`, un seul fichier chiffré, non versionné :
|
||||
|
||||
```text
|
||||
instance/inventories/lab/group_vars/proxmox.vault.yml
|
||||
instance/inventories/production/group_vars/all/vault.yml
|
||||
```
|
||||
|
||||
Créer le fichier depuis l'exemple, puis le chiffrer :
|
||||
|
||||
```bash
|
||||
cp instance/inventories/lab/group_vars/proxmox.vault.yml.example instance/inventories/lab/group_vars/proxmox.vault.yml
|
||||
ansible-vault encrypt instance/inventories/lab/group_vars/proxmox.vault.yml
|
||||
```
|
||||
`make config` la sème depuis le gabarit et y place le token. Le clonage lit cette voûte
|
||||
unifiée (une `proxmox.vault.yml` séparée reste acceptée en compatibilité). Détail des
|
||||
paramètres : [`docs/config-proxmox.md`](../../docs/config-proxmox.md).
|
||||
|
||||
Créer un clone Debian depuis le modèle :
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@
|
|||
# Config Proxmox de l'instance : on cherche dans l'inventaire de l'instance,
|
||||
# quel que soit son nom (lab > principal > production). Compatible « par instance ».
|
||||
setops_inventaires: [lab, principal, production]
|
||||
setops_gv_proxmox: [proxmox.yml, proxmox.vault.yml, proxmox.local.yml]
|
||||
# Voute Proxmox : la voute UNIFIEE de l'instance (all/vault.yml, ou `make config`
|
||||
# ecrit le token) est autoritaire ; `proxmox.vault.yml` reste accepte en compat
|
||||
# legacy. all/vault.yml en dernier => il l'emporte s'il definit les memes cles.
|
||||
setops_gv_proxmox: [proxmox.yml, proxmox.vault.yml, proxmox.local.yml, all/vault.yml]
|
||||
|
||||
tasks:
|
||||
- name: Verifier les fichiers de variables Proxmox optionnels
|
||||
|
|
@ -30,7 +33,7 @@
|
|||
|
||||
- name: Charger les secrets Proxmox Vault
|
||||
block:
|
||||
- name: Charger proxmox.vault.yml
|
||||
- name: Charger les secrets de la voute Proxmox
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ item.stat.path }}"
|
||||
loop: "{{ proxmox_fichiers_variables.results }}"
|
||||
|
|
@ -42,9 +45,9 @@
|
|||
- name: Expliquer l echec de chargement du Vault
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Impossible de charger instance/inventories/lab/group_vars/proxmox.vault.yml.
|
||||
Si le fichier est chiffre avec Ansible Vault, fournir son mot de passe
|
||||
ou definir ANSIBLE_VAULT_PASSWORD_FILE avant de relancer make creer-vm.
|
||||
Impossible de charger la voute Proxmox (group_vars/all/vault.yml ou
|
||||
proxmox.vault.yml). Si elle est chiffree avec Ansible Vault, fournir son
|
||||
mot de passe ou definir ANSIBLE_VAULT_PASSWORD_FILE avant de relancer make creer-vm.
|
||||
|
||||
- name: Appliquer les valeurs Proxmox avec repli environnement
|
||||
ansible.builtin.set_fact:
|
||||
|
|
@ -70,7 +73,7 @@
|
|||
- proxmox_clone_noeud | length > 0
|
||||
- proxmox_clone_ipconfig0 is defined
|
||||
- proxmox_clone_ipconfig0 | length > 0
|
||||
fail_msg: "Parametres Proxmox incomplets. Verifier proxmox.yml, proxmox.vault.yml et les variables make propres a la VM."
|
||||
fail_msg: "Parametres Proxmox incomplets. Verifier proxmox.yml, la voute (all/vault.yml ou proxmox.vault.yml) et les variables make propres a la VM."
|
||||
|
||||
- name: Lire la cle publique SSH depuis le fichier fourni
|
||||
ansible.builtin.set_fact:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
| selectattr('vmid', 'in', cibles_vmid | map('int') | list)
|
||||
| list }}
|
||||
|
||||
- name: "SÉCURITÉ — le modèle ({{ proxmox_clone_vmid_modele }}) n'est PAS ciblé"
|
||||
- name: "SÉCURITÉ — le modèle n'est PAS ciblé (VMID {{ proxmox_clone_vmid_modele }})"
|
||||
ansible.builtin.assert:
|
||||
that: "(proxmox_clone_vmid_modele | int) not in (cibles_vmid | map('int') | list)"
|
||||
fail_msg: "REFUS : le VMID du modèle est dans la liste à supprimer."
|
||||
|
|
|
|||
|
|
@ -6,43 +6,73 @@
|
|||
# Ansible saute les groupes sans hote actif : le meme fichier sert toute instance.
|
||||
|
||||
# --- couche socle ---
|
||||
- import_playbook: groupes/serveur_debian.yml
|
||||
- import_playbook: groupes/serveur_durci.yml
|
||||
- name: Couche socle — groupe serveur_debian
|
||||
import_playbook: groupes/serveur_debian.yml
|
||||
- name: Couche socle — groupe serveur_durci
|
||||
import_playbook: groupes/serveur_durci.yml
|
||||
|
||||
# --- couche pki_racine ---
|
||||
- import_playbook: groupes/serveur_step_ca.yml
|
||||
- name: Couche pki_racine — groupe serveur_step_ca
|
||||
import_playbook: groupes/serveur_step_ca.yml
|
||||
|
||||
# --- couche pki_client ---
|
||||
- import_playbook: groupes/client_pki.yml
|
||||
- name: Couche pki_client — groupe client_pki
|
||||
import_playbook: groupes/client_pki.yml
|
||||
|
||||
# --- couche services ---
|
||||
- import_playbook: groupes/serveur_postgresql.yml
|
||||
- import_playbook: groupes/serveur_openldap.yml
|
||||
- import_playbook: groupes/serveur_powerdns.yml
|
||||
- import_playbook: groupes/serveur_redis.yml
|
||||
- import_playbook: groupes/serveur_prometheus.yml
|
||||
- import_playbook: groupes/serveur_loki.yml
|
||||
- import_playbook: groupes/serveur_nginx.yml
|
||||
- import_playbook: groupes/serveur_rspamd.yml
|
||||
- import_playbook: groupes/serveur_backup.yml
|
||||
- import_playbook: groupes/serveur_dovecot.yml
|
||||
- import_playbook: groupes/serveur_postfix.yml
|
||||
- name: Couche services — groupe serveur_postgresql
|
||||
import_playbook: groupes/serveur_postgresql.yml
|
||||
- name: Couche services — groupe serveur_openldap
|
||||
import_playbook: groupes/serveur_openldap.yml
|
||||
- name: Couche services — groupe serveur_powerdns
|
||||
import_playbook: groupes/serveur_powerdns.yml
|
||||
- name: Couche services — groupe serveur_redis
|
||||
import_playbook: groupes/serveur_redis.yml
|
||||
- name: Couche services — groupe serveur_prometheus
|
||||
import_playbook: groupes/serveur_prometheus.yml
|
||||
- name: Couche services — groupe serveur_loki
|
||||
import_playbook: groupes/serveur_loki.yml
|
||||
- name: Couche services — groupe serveur_nginx
|
||||
import_playbook: groupes/serveur_nginx.yml
|
||||
- name: Couche services — groupe serveur_rspamd
|
||||
import_playbook: groupes/serveur_rspamd.yml
|
||||
- name: Couche services — groupe serveur_backup
|
||||
import_playbook: groupes/serveur_backup.yml
|
||||
- name: Couche services — groupe serveur_dovecot
|
||||
import_playbook: groupes/serveur_dovecot.yml
|
||||
- name: Couche services — groupe serveur_postfix
|
||||
import_playbook: groupes/serveur_postfix.yml
|
||||
|
||||
# --- couche apps ---
|
||||
- import_playbook: groupes/serveur_keycloak.yml
|
||||
- import_playbook: groupes/serveur_oauth2_proxy.yml
|
||||
- import_playbook: groupes/serveur_forgejo.yml
|
||||
- import_playbook: groupes/serveur_icinga.yml
|
||||
- import_playbook: groupes/serveur_grafana.yml
|
||||
- import_playbook: groupes/serveur_collabora.yml
|
||||
- import_playbook: groupes/serveur_web_frontal.yml
|
||||
- import_playbook: groupes/serveur_web_dorsal.yml
|
||||
- import_playbook: groupes/serveur_icingaweb2.yml
|
||||
- import_playbook: groupes/serveur_nextcloud.yml
|
||||
- name: Couche apps — groupe serveur_keycloak
|
||||
import_playbook: groupes/serveur_keycloak.yml
|
||||
- name: Couche apps — groupe serveur_oauth2_proxy
|
||||
import_playbook: groupes/serveur_oauth2_proxy.yml
|
||||
- name: Couche apps — groupe serveur_forgejo
|
||||
import_playbook: groupes/serveur_forgejo.yml
|
||||
- name: Couche apps — groupe serveur_icinga
|
||||
import_playbook: groupes/serveur_icinga.yml
|
||||
- name: Couche apps — groupe serveur_grafana
|
||||
import_playbook: groupes/serveur_grafana.yml
|
||||
- name: Couche apps — groupe serveur_collabora
|
||||
import_playbook: groupes/serveur_collabora.yml
|
||||
- name: Couche apps — groupe serveur_web_frontal
|
||||
import_playbook: groupes/serveur_web_frontal.yml
|
||||
- name: Couche apps — groupe serveur_web_dorsal
|
||||
import_playbook: groupes/serveur_web_dorsal.yml
|
||||
- name: Couche apps — groupe serveur_icingaweb2
|
||||
import_playbook: groupes/serveur_icingaweb2.yml
|
||||
- name: Couche apps — groupe serveur_nextcloud
|
||||
import_playbook: groupes/serveur_nextcloud.yml
|
||||
|
||||
# --- couche agents ---
|
||||
- import_playbook: groupes/client_metrique.yml
|
||||
- import_playbook: groupes/client_journal.yml
|
||||
- import_playbook: groupes/client_smtp.yml
|
||||
- import_playbook: groupes/client_backup.yml
|
||||
- import_playbook: groupes/client_unbound.yml
|
||||
- name: Couche agents — groupe client_metrique
|
||||
import_playbook: groupes/client_metrique.yml
|
||||
- name: Couche agents — groupe client_journal
|
||||
import_playbook: groupes/client_journal.yml
|
||||
- name: Couche agents — groupe client_smtp
|
||||
import_playbook: groupes/client_smtp.yml
|
||||
- name: Couche agents — groupe client_backup
|
||||
import_playbook: groupes/client_backup.yml
|
||||
- name: Couche agents — groupe client_unbound
|
||||
import_playbook: groupes/client_unbound.yml
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@
|
|||
- name: Lister les valeurs de la directive server_name (première colonne exacte)
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
set -o pipefail;
|
||||
nginx -T 2>/dev/null | awk '$1=="server_name"{for(i=2;i<=NF;i++){gsub(/;/,"",$i);
|
||||
print $i}}' | sort -u
|
||||
executable: /bin/bash
|
||||
register: recette_server_names
|
||||
changed_when: false
|
||||
|
||||
|
|
@ -84,9 +86,11 @@
|
|||
- name: Envoyer via le MTA (sendmail — flux réel postfix -> LDAP -> LMTP -> Dovecot)
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
set -o pipefail;
|
||||
printf 'Subject: %s\nFrom: %s\nTo: %s\n\nRecette d acceptation Set-OPS.\n'
|
||||
'{{ recette_mail_token }}' '{{ recette_mail_from }}' '{{ recette_mail_dest }}'
|
||||
| /usr/sbin/sendmail -f '{{ recette_mail_from }}' '{{ recette_mail_dest }}'
|
||||
executable: /bin/bash
|
||||
delegate_to: "{{ groups['serveur_postfix'][0] }}"
|
||||
changed_when: true
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ def _inventaire(instance: Path, *noms: str) -> Path:
|
|||
p = instance / "inventories" / nom / "hosts.yml"
|
||||
if p.exists():
|
||||
return p
|
||||
# Aucun hosts.yml encore genere : viser le repertoire d'inventaire deja present
|
||||
# (ses group_vars y sont), sinon le premier nom par defaut. Evite d'ecrire dans
|
||||
# 'principal/' alors que l'instance (ex. modele socle) est en 'production/'.
|
||||
for nom in noms:
|
||||
if (instance / "inventories" / nom).is_dir():
|
||||
return instance / "inventories" / nom / "hosts.yml"
|
||||
return instance / "inventories" / noms[0] / "hosts.yml"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ def _inventaire(instance: Path, *noms: str) -> Path:
|
|||
p = instance / "inventories" / nom / "hosts.yml"
|
||||
if p.exists():
|
||||
return p
|
||||
# Aucun hosts.yml encore généré : viser le répertoire d'inventaire déjà présent
|
||||
# (ses group_vars y sont), sinon le premier nom par défaut.
|
||||
for nom in noms:
|
||||
if (instance / "inventories" / nom).is_dir():
|
||||
return instance / "inventories" / nom / "hosts.yml"
|
||||
return instance / "inventories" / noms[0] / "hosts.yml"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,8 @@ def rendre_site(ordre: list[tuple[str, list[str]]]) -> str:
|
|||
for nom, groupes in ordre:
|
||||
lignes.append(f"# --- couche {nom} ---")
|
||||
for groupe in groupes:
|
||||
lignes.append(f"- import_playbook: groupes/{groupe}.yml")
|
||||
lignes.append(f"- name: Couche {nom} — groupe {groupe}")
|
||||
lignes.append(f" import_playbook: groupes/{groupe}.yml")
|
||||
lignes.append("")
|
||||
return "\n".join(lignes).rstrip() + "\n"
|
||||
|
||||
|
|
|
|||
348
scripts/prouver.py
Normal file
348
scripts/prouver.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Harnais de preuve Set-OPS : rejoue les preuves automatisables du registre.
|
||||
|
||||
`make prouver` enchaine les commandes de preuve du registre des affirmations
|
||||
(`docs/audit/affirmations.md`) et produit un rapport horodate
|
||||
`docs/audit/preuve-AAAA-MM-JJ.md` — rejouable, presentable comme piece justificative.
|
||||
|
||||
Ce script est un ORCHESTRATEUR MINCE : il ne reimplemente AUCUNE validation. Il
|
||||
appelle l'outillage existant (les memes `scripts/*.py`, `ansible-lint`, `make syntaxe`
|
||||
que `make verifier`) et met en forme les resultats. Ce n'est pas un framework de test
|
||||
parallele : la logique de validation vit dans les scripts appeles.
|
||||
|
||||
Sortie : 0 si aucune preuve automatisable n'echoue (les preuves SAUTEES — ex. voute
|
||||
requise absente — ne sont pas des echecs) ; 1 sinon.
|
||||
|
||||
Usage :
|
||||
python3 scripts/prouver.py # execute + ecrit docs/audit/preuve-<date>.md
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as _dt
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[1]
|
||||
INSTANCE = Path(os.environ.get("SETOPS_INSTANCE", "instance"))
|
||||
DOSSIER_AUDIT = RACINE / "docs" / "audit"
|
||||
GROUPES = "playbooks/groupes"
|
||||
DEPENDANCES = "docs/dependances-groupes.yml"
|
||||
|
||||
|
||||
def _inventaire() -> str:
|
||||
"""Inventaire de l'instance : premier existant (principal > production > lab)."""
|
||||
for nom in ("principal", "production", "lab"):
|
||||
p = INSTANCE / "inventories" / nom / "hosts.yml"
|
||||
if (RACINE / p).exists():
|
||||
return str(p)
|
||||
return str(INSTANCE / "inventories" / "principal" / "hosts.yml")
|
||||
|
||||
|
||||
INV = _inventaire()
|
||||
|
||||
|
||||
# --- Preuves natives (petites verifications d'invariants, pas de logique metier) ---
|
||||
|
||||
def preuve_handlers() -> tuple[bool, str]:
|
||||
"""Tout `notify` d'un role pointe vers un handler du meme role (AGENTS.md)."""
|
||||
import glob
|
||||
import yaml
|
||||
|
||||
problemes: list[str] = []
|
||||
roles_dir = RACINE / "roles"
|
||||
for role in sorted(p.name for p in roles_dir.iterdir() if p.is_dir()):
|
||||
rp = roles_dir / role
|
||||
handlers: set[str] = set()
|
||||
for hf in glob.glob(str(rp / "handlers" / "*.yml")):
|
||||
data = yaml.safe_load(Path(hf).read_text(encoding="utf-8")) or []
|
||||
|
||||
def collect(tasks):
|
||||
for t in tasks or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
if "name" in t:
|
||||
handlers.add(t["name"])
|
||||
if "listen" in t:
|
||||
listen = t["listen"]
|
||||
handlers.update(listen if isinstance(listen, list) else [listen])
|
||||
for k in ("block", "rescue", "always"):
|
||||
if k in t:
|
||||
collect(t[k])
|
||||
|
||||
collect(data)
|
||||
notifies: set[str] = set()
|
||||
for tf in glob.glob(str(rp / "tasks" / "*.yml")):
|
||||
data = yaml.safe_load(Path(tf).read_text(encoding="utf-8")) or []
|
||||
|
||||
def scan(tasks):
|
||||
for t in tasks or []:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
if "notify" in t:
|
||||
n = t["notify"]
|
||||
notifies.update(n if isinstance(n, list) else [n])
|
||||
for k in ("block", "rescue", "always"):
|
||||
if k in t:
|
||||
scan(t[k])
|
||||
|
||||
scan(data)
|
||||
manquants = notifies - handlers
|
||||
if manquants:
|
||||
problemes.append(f"{role}: notify sans handler -> {sorted(manquants)}")
|
||||
if problemes:
|
||||
return False, "; ".join(problemes)
|
||||
return True, "Tout notify pointe vers un handler du meme role (49 roles)."
|
||||
|
||||
|
||||
def preuve_runbooks() -> tuple[bool, str]:
|
||||
"""Les runbooks / registres cites par les surfaces publiques existent."""
|
||||
attendus = [
|
||||
"docs/plan-et-generation.md", "docs/catalogue-services.md",
|
||||
"docs/dependances-groupes.yml", "docs/dns-interne.md",
|
||||
"docs/nomenclature-vm.md", "docs/config-proxmox.md", "docs/vm-lifecycle.md",
|
||||
"docs/procedure-template-debian13-proxmox.md", "docs/positionnement.md",
|
||||
"docs/bindings-conception.md", "docs/flux-conception.md",
|
||||
"docs/couches-deploiement.yml", "docs/registre-flux.md",
|
||||
"docs/carte-set-ops.md", "docs/runbooks-exploitation.md",
|
||||
"exemples/vault.exemple.yml", "exemples/instance.exemple.yml",
|
||||
]
|
||||
manquants = [f for f in attendus if not (RACINE / f).exists()]
|
||||
if manquants:
|
||||
return False, "manquants : " + ", ".join(manquants)
|
||||
return True, f"{len(attendus)}/{len(attendus)} runbooks/registres cites presents."
|
||||
|
||||
|
||||
def preuve_structurels() -> tuple[bool, str]:
|
||||
"""Invariants de structure/doctrine verifiables par le contenu du depot."""
|
||||
echecs: list[str] = []
|
||||
|
||||
def lire(rel: str) -> str:
|
||||
p = RACINE / rel
|
||||
return p.read_text(encoding="utf-8") if p.exists() else ""
|
||||
|
||||
# AFF-015 : LICENSE present
|
||||
if not (RACINE / "LICENSE").exists():
|
||||
echecs.append("LICENSE absent")
|
||||
# AFF-022 : modele socle en forme dossier (intrants charges, pas de shadowing)
|
||||
if not (RACINE / "exemples/modeles/socle/inventories/production/group_vars/all/10-intrants.yml").exists():
|
||||
echecs.append("socle: all/10-intrants.yml absent")
|
||||
if (RACINE / "exemples/modeles/socle/inventories/production/group_vars/all.yml").exists():
|
||||
echecs.append("socle: all.yml (forme fichier) masquerait all/")
|
||||
# AFF-038 : pas de playbooks de couches paralleles
|
||||
for couche in ("socle", "durcissement"):
|
||||
if (RACINE / "playbooks" / couche).exists():
|
||||
echecs.append(f"playbooks/{couche}/ ne doit pas exister")
|
||||
# AFF-037 : SSH clef-only des la construction
|
||||
if 'ssh_baseline_password_authentication: "no"' not in lire("roles/ssh_baseline/defaults/main.yml"):
|
||||
echecs.append("ssh_baseline: PasswordAuthentication != no")
|
||||
if "AuthenticationMethods publickey" not in lire("roles/ssh_baseline/templates/10-setops.conf.j2"):
|
||||
echecs.append("ssh_baseline: AuthenticationMethods publickey absent")
|
||||
# AFF-062 : nftables installe mais desactive par defaut
|
||||
if "nftables_baseline_enabled: false" not in lire("roles/nftables_baseline/defaults/main.yml"):
|
||||
echecs.append("nftables_baseline_enabled != false")
|
||||
if echecs:
|
||||
return False, "; ".join(echecs)
|
||||
return True, "LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK."
|
||||
|
||||
|
||||
def preuve_modele_socle() -> tuple[bool, str]:
|
||||
"""Le modele public `socle` valide (registres) — garde contre une regression d'exemple."""
|
||||
socle = "exemples/modeles/socle"
|
||||
env = {**os.environ, "SETOPS_INSTANCE": socle}
|
||||
for s in ("domaines", "serveurs", "applications", "bases_donnees"):
|
||||
r = subprocess.run([sys.executable, f"scripts/{s}.py", "verifier"],
|
||||
cwd=RACINE, env=env, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
sortie = (r.stdout + r.stderr).strip().splitlines()
|
||||
return False, f"{s}: {sortie[-1] if sortie else 'rc!=0'}"
|
||||
return True, "Modele public socle : domaines/serveurs/applications/bases valides."
|
||||
|
||||
|
||||
def preuve_pas_lab_code_en_dur() -> tuple[bool, str]:
|
||||
"""Aucun chemin `instance/inventories/lab/group_vars` code en dur (AFF-097).
|
||||
|
||||
On vise le vrai chemin (prefixe `instance/`) et on ignore les fichiers meta qui
|
||||
*parlent* de l'audit (CHANGELOG, docs/audit) sans etre des chemins d'exploitation.
|
||||
"""
|
||||
r = subprocess.run(
|
||||
["git", "grep", "-n", "instance/inventories/lab/group_vars", "--", "*.md", "*.yml"],
|
||||
cwd=RACINE, capture_output=True, text=True,
|
||||
)
|
||||
exclus = ("docs/audit/", "CHANGELOG.md:")
|
||||
lignes = [l for l in r.stdout.splitlines() if not l.startswith(exclus)]
|
||||
if lignes:
|
||||
return False, f"{len(lignes)} occurrence(s) : " + " | ".join(lignes[:3])
|
||||
return True, "Aucun chemin instance/inventories/lab/group_vars code en dur."
|
||||
|
||||
|
||||
# --- Registre des preuves : (id, titre, refs AFF, executeur) -----------------------
|
||||
#
|
||||
# executeur = liste de commandes argv (toutes doivent renvoyer 0), ou callable -> (ok, detail).
|
||||
|
||||
PREUVES: list[dict] = [
|
||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||
"cmds": [["ansible-lint", "-q"]]},
|
||||
{"id": "P02", "titre": "Tests unitaires (inventory_host)", "refs": [],
|
||||
"cmds": [[sys.executable, "scripts/tests/test_inventory_host.py"]]},
|
||||
{"id": "P03", "titre": "Diff-vide du plan (inventaire genere)",
|
||||
"refs": ["AFF-001", "AFF-004", "AFF-030", "AFF-031", "AFF-032"],
|
||||
"cmds": [[sys.executable, "scripts/instancier.py", "generer"],
|
||||
[sys.executable, "scripts/instancier.py", "comparer"]]},
|
||||
{"id": "P04", "titre": "Groupes <-> playbooks homonymes", "refs": ["AFF-008"],
|
||||
"cmds": [[sys.executable, "scripts/inventory_host.py", "--inventaire", INV,
|
||||
"verifier-playbooks", "--dossier-playbooks", GROUPES]]},
|
||||
{"id": "P05", "titre": "Dependances causales de groupes", "refs": ["AFF-009", "AFF-084"],
|
||||
"cmds": [[sys.executable, "scripts/inventory_host.py", "--inventaire", INV,
|
||||
"--dependances", DEPENDANCES, "verifier-dependances",
|
||||
"--dossier-playbooks", GROUPES]]},
|
||||
{"id": "P06", "titre": "Validateurs de registres (serveurs/apps/bases/domaines)",
|
||||
"refs": ["AFF-003"],
|
||||
"cmds": [[sys.executable, "scripts/serveurs.py", "verifier"],
|
||||
[sys.executable, "scripts/applications.py", "verifier"],
|
||||
[sys.executable, "scripts/bases_donnees.py", "verifier"],
|
||||
[sys.executable, "scripts/domaines.py", "verifier"]]},
|
||||
{"id": "P07", "titre": "GUI (node --check)", "refs": ["AFF-033"],
|
||||
"cmds": [[sys.executable, "scripts/verifier_gui.py"]]},
|
||||
{"id": "P08", "titre": "Orchestration (couches + graphe)", "refs": ["AFF-070"],
|
||||
"cmds": [[sys.executable, "scripts/orchestrer.py", "verifier"]]},
|
||||
{"id": "P09", "titre": "Flux reseau (schema + matrice)", "refs": ["AFF-071"],
|
||||
"cmds": [[sys.executable, "scripts/resoudre_flux.py", "verifier"]]},
|
||||
{"id": "P10", "titre": "Handlers <-> notify", "refs": ["AFF-034", "AFF-035"],
|
||||
"func": preuve_handlers},
|
||||
{"id": "P11", "titre": "Syntaxe des playbooks (--syntax-check)", "refs": ["AFF-083"],
|
||||
"cmds": [["make", "--no-print-directory", "syntaxe"]]},
|
||||
{"id": "P12", "titre": "Existence des runbooks cites",
|
||||
"refs": ["AFF-010", "AFF-011", "AFF-012", "AFF-083"], "func": preuve_runbooks},
|
||||
{"id": "P13", "titre": "Invariants structurels/doctrinaux",
|
||||
"refs": ["AFF-015", "AFF-022", "AFF-037", "AFF-038", "AFF-062"], "func": preuve_structurels},
|
||||
{"id": "P14", "titre": "Pas de chemin lab/ code en dur", "refs": ["AFF-097"],
|
||||
"func": preuve_pas_lab_code_en_dur},
|
||||
{"id": "P15", "titre": "Modele public socle valide", "refs": ["AFF-022", "AFF-099"],
|
||||
"func": preuve_modele_socle},
|
||||
]
|
||||
|
||||
|
||||
def _vault_requis_absent() -> bool:
|
||||
"""Vrai si l'inventaire chiffre un group_vars et qu'aucun mot de passe n'est fourni."""
|
||||
if os.environ.get("ANSIBLE_VAULT_PASSWORD_FILE"):
|
||||
return False
|
||||
gv = (RACINE / INV).parent / "group_vars"
|
||||
if not gv.exists():
|
||||
return False
|
||||
for f in gv.rglob("*"):
|
||||
if f.is_file():
|
||||
try:
|
||||
if f.read_text(encoding="utf-8", errors="ignore").startswith("$ANSIBLE_VAULT"):
|
||||
return True
|
||||
except OSError:
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
def _executer(preuve: dict) -> tuple[str, str]:
|
||||
"""Renvoie (statut, detail). statut in {OK, ECHEC, SAUTE}."""
|
||||
if "func" in preuve:
|
||||
ok, detail = preuve["func"]()
|
||||
return ("OK" if ok else "ECHEC"), detail
|
||||
dernier = ""
|
||||
for cmd in preuve["cmds"]:
|
||||
r = subprocess.run(cmd, cwd=RACINE, capture_output=True, text=True, timeout=600)
|
||||
sortie = [l for l in (r.stdout + r.stderr).strip().splitlines()
|
||||
if l.strip() and not l.lstrip().startswith("[WARNING]")]
|
||||
dernier = sortie[-1] if sortie else ""
|
||||
if r.returncode != 0:
|
||||
return "ECHEC", f"rc={r.returncode} : {dernier}"
|
||||
return "OK", dernier
|
||||
|
||||
|
||||
def main() -> int:
|
||||
date = _dt.date.today().isoformat()
|
||||
resultats: list[tuple[dict, str, str]] = []
|
||||
|
||||
# Preuve conditionnelle : inventaire Ansible complet (necessite la voute).
|
||||
preuve_inv = {"id": "P16", "titre": "Inventaire Ansible complet (--list)",
|
||||
"refs": ["AFF-030"],
|
||||
"cmds": [["ansible-inventory", "-i", INV, "--list"]]}
|
||||
liste = list(PREUVES)
|
||||
if _vault_requis_absent():
|
||||
resultats.append((preuve_inv, "SAUTE",
|
||||
"Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026)."))
|
||||
else:
|
||||
liste.append(preuve_inv)
|
||||
|
||||
for preuve in liste:
|
||||
statut, detail = _executer(preuve)
|
||||
resultats.append((preuve, statut, detail))
|
||||
|
||||
# Tri : garder l'ordre d'execution (P01..P15) ; la preuve sautee a ete inseree tot.
|
||||
resultats.sort(key=lambda r: r[0]["id"])
|
||||
|
||||
n_ok = sum(1 for _, s, _ in resultats if s == "OK")
|
||||
n_echec = sum(1 for _, s, _ in resultats if s == "ECHEC")
|
||||
n_saute = sum(1 for _, s, _ in resultats if s == "SAUTE")
|
||||
conforme = n_echec == 0
|
||||
icone = {"OK": "✅", "ECHEC": "❌", "SAUTE": "⚪"}
|
||||
|
||||
lignes = [
|
||||
f"# Preuve de conformite — Set-OPS — {date}",
|
||||
"",
|
||||
"> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer",
|
||||
"> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;",
|
||||
"> aucune validation n'est reimplementee ici. Voir le mode d'emploi :",
|
||||
"> [`docs/audit/README.md`](README.md), et le registre trace :",
|
||||
"> [`docs/audit/affirmations.md`](affirmations.md).",
|
||||
"",
|
||||
f"- **Instance** : `{INSTANCE}` — inventaire `{INV}`",
|
||||
f"- **Verdict** : {'✅ CONFORME' if conforme else '❌ NON CONFORME'} "
|
||||
f"({n_ok} OK · {n_echec} echec · {n_saute} saute)",
|
||||
"",
|
||||
"## Preuves",
|
||||
"",
|
||||
"| # | Preuve | Affirmations | Statut | Detail |",
|
||||
"|---|---|---|---|---|",
|
||||
]
|
||||
for preuve, statut, detail in resultats:
|
||||
refs = ", ".join(preuve["refs"]) or "—"
|
||||
det = detail.replace("|", "\\|")[:160]
|
||||
lignes.append(f"| {preuve['id']} | {preuve['titre']} | {refs} | {icone[statut]} {statut} | {det} |")
|
||||
|
||||
lignes += [
|
||||
"",
|
||||
"## Couverture des affirmations ✅ du registre",
|
||||
"",
|
||||
"Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.",
|
||||
"Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005",
|
||||
"`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite",
|
||||
"d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;",
|
||||
"elles restent hors du harnais recurrent (rien d'executable a rejouer).",
|
||||
"",
|
||||
"## Declarations d'intention (⚪ invérifiables localement — assumees)",
|
||||
"",
|
||||
"Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**",
|
||||
"comme declarations d'intention, non comme preuves :",
|
||||
"",
|
||||
"- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement",
|
||||
" contre une flotte vivante.",
|
||||
"- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.",
|
||||
"- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.",
|
||||
"- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.",
|
||||
"",
|
||||
f"_Rapport genere le {date}._",
|
||||
"",
|
||||
]
|
||||
|
||||
DOSSIER_AUDIT.mkdir(parents=True, exist_ok=True)
|
||||
rapport = DOSSIER_AUDIT / f"preuve-{date}.md"
|
||||
rapport.write_text("\n".join(lignes), encoding="utf-8")
|
||||
|
||||
print(f"{'CONFORME' if conforme else 'NON CONFORME'} : "
|
||||
f"{n_ok} OK, {n_echec} echec, {n_saute} saute.")
|
||||
print(f"Rapport : {rapport.relative_to(RACINE)}")
|
||||
return 0 if conforme else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue