authentification : chaque rôle déclare sa position, gardé par P29
Une règle qu'aucune garde ne vérifie finit par ne plus être vraie — c'est ce qui était arrivé aux 28 lignes d'intégration recopiées. Chaque rôle serveur_* porte un meta/authentification.yml, confronté à son code par P29. web-sso 5, socle-identite 2 (keycloak/openldap : ils SONT la chaîne d'identité), ldap-direct 2, interne-sans-auth 2, sans-auth-humaine 12. La preuve refuse l'oubli ET le mensonge. Éprouvée par sabotage sur sept cas : déclaration supprimée, portée inventée, secours retiré, posture de formulaire retirée, raison retirée, ldap-direct mensonger, réglage retiré des defaults. Les deux derniers passaient dans la première version : - le mensonge passait à cause d'un commentaire. Je cherchais le mot « ldap » dans le rôle, et serveur_grafana/defaults/main.yml contient « désactiver quelqu'un dans LDAP » : de la prose validait une déclaration fausse. La preuve exige maintenant un indice nommé — variable <rôle>_oidc / <rôle>_ldap, ou URI ldap:// - le réglage retiré passait parce que le gabarit citait encore la variable alors que plus rien ne lui donnait de valeur. La preuve lit defaults/main.yml en YAML et exige que la clé y soit définie, pas mentionnée. Elle a aussi forcé une valeur : oauth2-proxy était déclaré « formulaire local fermé » alors qu'il n'a aucun compte local. D'où formulaire_local: aucun, qui distingue « il n'y en a jamais eu » de « il y en a un, il est fermé ». Correction d'une note de la veille : Prometheus et Loki ne sont PAS exposés publiquement (aucun expose au plan). Seuls six groupes le sont. Le risque est intra-tenant, pas frontalier. Les deux lacunes sont comptées à chaque exécution, pas masquées. AFF-111, D-42. 29 preuves OK, ansible-lint (production) sur 375 fichiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6a62f0a4c7
commit
22ef279464
29 changed files with 404 additions and 11 deletions
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -1,5 +1,57 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-03 (suite 16) — la directive d'authentification est gardée (P29)
|
||||
|
||||
Une règle qu'aucune garde ne vérifie finit par ne plus être vraie : c'est exactement ce
|
||||
qui était arrivé aux 28 lignes d'intégration recopiées. Chaque rôle `serveur_*` porte
|
||||
désormais un `meta/authentification.yml`, et **P29** le confronte à son code.
|
||||
|
||||
```
|
||||
web-sso 5 grafana, forgejo, nextcloud, icingaweb2, oauth2-proxy
|
||||
socle-identite 2 keycloak, openldap — ils SONT la chaîne d'identité
|
||||
ldap-direct 2 dovecot, postfix
|
||||
interne-sans-auth 2 prometheus, loki — lacunes nommées
|
||||
sans-auth-humaine 12
|
||||
```
|
||||
|
||||
### Elle refuse l'oubli **et** le mensonge
|
||||
|
||||
Éprouvée par sabotage, sept cas : déclaration supprimée, portée inventée, secours retiré,
|
||||
posture de formulaire retirée, raison retirée, `ldap-direct` mensonger, réglage retiré des
|
||||
`defaults`. Les sept sont attrapés.
|
||||
|
||||
Les deux derniers **passaient** dans ma première version.
|
||||
|
||||
**Le mensonge passait à cause d'un de mes propres commentaires.** Je cherchais le mot
|
||||
« ldap » dans le rôle — or `serveur_grafana/defaults/main.yml` contient la phrase
|
||||
« désactiver quelqu'un dans LDAP ». De la prose suffisait à valider une déclaration fausse.
|
||||
La preuve exige maintenant un indice **nommé** : une variable du namespace du rôle
|
||||
(`<rôle>_oidc`, `<rôle>_ldap`) ou une URI `ldap://`.
|
||||
|
||||
**Le réglage retiré passait** parce que le gabarit citait encore la variable alors que plus
|
||||
rien ne lui donnait de valeur. La preuve lit désormais `defaults/main.yml` **en YAML** et
|
||||
exige que la clé y soit *définie*, pas mentionnée.
|
||||
|
||||
### Une valeur que la preuve a forcé à inventer
|
||||
|
||||
Elle a d'abord échoué sur `oauth2-proxy` : je l'avais déclaré « formulaire local fermé »
|
||||
alors qu'il n'a **aucun compte local** — c'est une passerelle. D'où `formulaire_local:
|
||||
aucun`, qui distingue « il n'y en a jamais eu » de « il y en a un, il est fermé ». Écrire
|
||||
« fermé » aurait laissé croire qu'une porte avait été close.
|
||||
|
||||
### Correction : Prometheus et Loki ne sont pas exposés
|
||||
|
||||
Contrairement à ce que la note de la veille affirmait, ils n'ont **aucun `expose` au plan**.
|
||||
Seuls six groupes sont exposés : collabora, forgejo, grafana, keycloak, nextcloud,
|
||||
oauth2-proxy. Le risque est intra-tenant, pas frontalier — plus petit qu'annoncé, réel
|
||||
quand même.
|
||||
|
||||
Ces deux lacunes sont **comptées, pas masquées** : `interne-sans-auth` ne fait pas échouer
|
||||
la preuve, mais figure dans chaque rapport. La refuser bloquerait le harnais sur une
|
||||
décision déjà prise ; la taire la ferait oublier.
|
||||
|
||||
**AFF-111**, décision **D-42**. 29 preuves OK, 0 échec, 0 sautée.
|
||||
|
||||
## 2026-08-03 (suite 15) — la porte de secours cesse d'être annoncée
|
||||
|
||||
Client OIDC **Nextcloud** ajouté aux deux tenants — quatre clients chacun désormais, sur
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ Légende du statut : ✅ prouvée · 🟡 partielle · ❌ fausse · ⚪ invéri
|
|||
| AFF-108 | « Une intégration universelle est déclarée par le rôle, pas recopiée par serveur : aucun hôte n'y échappe et le plan ne la redit pas » (`docs/decisions-architecture.md` D-33) | structurelle | **P26** (`scripts/prouver.py`) | ✅ | L'exemption se dérive du **service rendu** (`sauf_role`), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. |
|
||||
| AFF-109 | « Ce qui appartient à l'hébergeur (cluster, fabric, frontière) vit dans son dépôt, jamais recopié chez un tenant » (`docs/decisions-architecture.md` D-35) | structurelle | **P27** (`scripts/prouver.py`) | ✅ | Le chemin se dérive du symlink `underlay.yml` : l'hébergeur n'est toujours pas déclaré (D-17). Sautée si aucun underlay n'est monté. |
|
||||
| AFF-110 | « Chaque tenant a son pool Proxmox, dérivé, sans collision de nom ni de VMID » (`docs/decisions-architecture.md` D-37) | structurelle | **P28** (`scripts/devis_proxmox_pools.py --verifier`) | ✅ | Le nom dérive du dossier d'instance et de l'`index`, déjà unique par P21 — aucun registre de plus. Le devis ne lit pas le cluster : il affirme la cohérence du **plan**, pas l'état appliqué. |
|
||||
| AFF-111 | « Chaque rôle `serveur_*` déclare sa position d'authentification, et la déclaration est cohérente avec son code » (`docs/authentification.md`, D-38 à D-41) | structurelle | **P29** (`scripts/prouver.py`) | ✅ | 23 rôles déclarés. La preuve refuse l'oubli **et** le mensonge : une déclaration `web-sso natif` sans variable `<rôle>_oidc`, ou `ldap-direct` sans variable `<rôle>_ldap`, échoue. Les indices sont **nommés** — une première version cherchait les mots « ldap »/« oidc » et le mot LDAP dans un commentaire suffisait à valider un mensonge. Les 2 portées `interne-sans-auth` (Prometheus, Loki) sont des lacunes **comptées, pas masquées**. |
|
||||
| AFF-106 | « Le panneau couvre le schéma du plan : aucun champ n'oblige à éditer le YAML » (`docs/intrants-base-gui-conception.md`) | fonctionnelle | **P19** (`scripts/couverture_gui.py`) | 🟡 | Vrai des registres du plan. **Exceptions assumées** : `reseaux` et `hotes` de l'underlay (listes de tables) restent hors du panneau ; les ports physiques et le nœud de sortie EVPN se nomment à la main. |
|
||||
|
||||
**Non enregistré volontairement.** Les devis eux-mêmes (`make devis-reseau`,
|
||||
|
|
@ -399,6 +400,7 @@ Chaque affirmation ✅ est soit **rejouée** par une preuve de `make prouver`
|
|||
| AFF-108 | **P26** (intégrations universelles : aucun hôte laissé de côté) |
|
||||
| AFF-109 | **P27** (propriété des intrants : hébergeur et tenant séparés) |
|
||||
| AFF-110 | **P28** (pools Proxmox : un par tenant) |
|
||||
| AFF-111 | **P29** (authentification : chaque rôle déclare sa position) |
|
||||
|
||||
**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`,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (27 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (29 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -28,9 +28,9 @@
|
|||
| 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). |
|
||||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 29 groupes (inventaire dechiffre et parse). |
|
||||
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 21 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 21 secret(s) exige(s), tous presents. Voute reelle : 24 cle(s), aucun manque. |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 27 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 3 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 7 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -85,12 +85,43 @@ Un accès de secours qu'on ne sait pas emprunter n'existe pas : les commandes ex
|
|||
dans le tableau ci-dessus, et les mots de passe locaux vivent dans la voûte unique de
|
||||
l'instance.
|
||||
|
||||
## 5. Ce qui reste à faire
|
||||
## 5. La garde : chaque rôle déclare sa position
|
||||
|
||||
- **Prometheus et Loki** exposent une interface sans SSO. `oauth2-proxy` est déjà éprouvé
|
||||
devant Icinga Web 2 : le patron existe, il reste à l'appliquer.
|
||||
- **Aucune preuve ne garde cette directive.** C'est le même risque que les intégrations
|
||||
universelles avant leur inversion : une règle qu'aucune garde ne vérifie finit par ne
|
||||
plus être vraie. Le remède symétrique serait qu'un rôle **déclare sa position**
|
||||
(`web-sso` / `ldap-direct` / `sans-auth-humaine`, plus son accès de secours) et qu'une
|
||||
preuve refuse une interface humaine sans SSO ni secours déclaré.
|
||||
Une directive qu'aucune garde ne vérifie finit par ne plus être vraie. Chaque rôle
|
||||
`serveur_*` porte donc un `meta/authentification.yml`, et **P29** le confronte à son code.
|
||||
|
||||
| Portée | Sens | Rôles |
|
||||
|---|---|---|
|
||||
| `web-sso` | authentification humaine web → Keycloak (`mecanisme: natif` ou `oauth2-proxy`) | 5 |
|
||||
| `socle-identite` | **est** la chaîne d'identité, ne peut pas se déléguer à elle-même | keycloak, openldap |
|
||||
| `ldap-direct` | protocole non-OIDC lié à LDAP | dovecot, postfix |
|
||||
| `interne-sans-auth` | interface joignable **dans** le tenant sans authentification, non exposée | prometheus, loki |
|
||||
| `sans-auth-humaine` | aucun point d'authentification humaine | 12 |
|
||||
|
||||
La preuve refuse **l'oubli et le mensonge** : un rôle sans déclaration, une portée
|
||||
inventée, un `web-sso` sans accès de secours ou sans posture de formulaire, un `web-sso
|
||||
natif` sans réglage `<rôle>_connexion_locale` **défini dans `defaults`**, et une
|
||||
déclaration que le code contredit.
|
||||
|
||||
> **Les indices doivent être nommés.** Une première version cherchait les mots « ldap » et
|
||||
> « oidc » dans le rôle. Le mot *LDAP*, présent dans un commentaire de `serveur_grafana`,
|
||||
> suffisait alors à valider une déclaration `ldap-direct` mensongère. La preuve exige
|
||||
> désormais une **variable du namespace du rôle** (`<rôle>_oidc`, `<rôle>_ldap`) ou une URI
|
||||
> `ldap://` — ce qu'une phrase en prose ne produit pas.
|
||||
|
||||
Une valeur mérite d'être expliquée : `formulaire_local: aucun`. Elle distingue un service
|
||||
qui **n'a pas de compte local du tout** — `oauth2-proxy` est une passerelle — de celui qui
|
||||
en a un et l'a fermé. Sans elle, on écrirait « fermé », ce qui laisserait croire qu'une
|
||||
porte a été close alors qu'il n'y en a jamais eu.
|
||||
|
||||
### Les lacunes sont comptées, pas masquées
|
||||
|
||||
`interne-sans-auth` ne fait pas échouer la preuve : c'est une lacune **assumée et nommée**.
|
||||
Mais elle est comptée à chaque exécution, donc visible dans chaque rapport. La refuser
|
||||
bloquerait le harnais sur une décision déjà prise ; la taire la ferait oublier.
|
||||
|
||||
Prometheus et Loki sont dans ce cas. À noter, contre une idée reçue de la veille : **ils ne
|
||||
sont pas exposés publiquement** — aucun `expose` au plan. Seuls six groupes le sont
|
||||
(collabora, forgejo, grafana, keycloak, nextcloud, oauth2-proxy). Le risque est donc
|
||||
intra-tenant, pas frontalier — plus petit qu'annoncé, réel quand même. `oauth2-proxy` est
|
||||
déjà éprouvé devant Icinga Web 2 : le patron existe, il reste à l'appliquer.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ sont les seules vérifiables.
|
|||
| **D-40** | L'accès de secours passe par **`sudo` sur l'hôte**, pas par un compte web permanent | `service → Keycloak → LDAP` est en série : sans secours, une panne exclut tout le monde, y compris pour réparer | `authentification.md` §4 | — |
|
||||
| **D-41** | Le formulaire de connexion locale n'est **pas proposé au repos** (`<rôle>_connexion_locale: false`) | il contourne la politique de mot de passe, le MFA et surtout la **révocation centrale** ; `sudo` est le mécanisme de réouverture | `authentification.md` §3 | — |
|
||||
|
||||
| **D-42** | Chaque rôle **déclare sa position** d'authentification (`meta/authentification.yml`), gardée par une preuve | une directive qu'aucune garde ne vérifie finit par ne plus être vraie — c'était le cas des 28 lignes d'intégration recopiées | `authentification.md` §5 | P29 |
|
||||
|
||||
## 3. Les secrets
|
||||
|
||||
| # | Décision | Pourquoi | Détail | Garde |
|
||||
|
|
|
|||
8
roles/serveur_backup/meta/authentification.yml
Normal file
8
roles/serveur_backup/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Depots restic joints par cle SSH ; aucun humain ne s'y connecte.
|
||||
9
roles/serveur_collabora/meta/authentification.yml
Normal file
9
roles/serveur_collabora/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Expose, mais aucun compte : l'acces est autorise par un jeton WOPI emis par
|
||||
Nextcloud, qui a deja authentifie l'humain.
|
||||
8
roles/serveur_debian/meta/authentification.yml
Normal file
8
roles/serveur_debian/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Socle du systeme : SSH par cle, aucune interface d'authentification propre.
|
||||
9
roles/serveur_dovecot/meta/authentification.yml
Normal file
9
roles/serveur_dovecot/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: ldap-direct
|
||||
raison: >-
|
||||
IMAP ne parle pas OIDC. Authentification LDAP directe : le chemin differe, la
|
||||
source est la meme.
|
||||
12
roles/serveur_forgejo/meta/authentification.yml
Normal file
12
roles/serveur_forgejo/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: web-sso
|
||||
mecanisme: natif
|
||||
formulaire_local: ferme
|
||||
secours: "forgejo admin user change-password"
|
||||
raison: >-
|
||||
Interface humaine exposee ; OAuth2 natif. ENABLE_INTERNAL_SIGNIN ferme aussi
|
||||
l'API en Basic (Forgejo >= 10).
|
||||
11
roles/serveur_grafana/meta/authentification.yml
Normal file
11
roles/serveur_grafana/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: web-sso
|
||||
mecanisme: natif
|
||||
formulaire_local: ferme
|
||||
secours: "grafana-cli admin reset-admin-password"
|
||||
raison: >-
|
||||
Interface humaine exposee ; OIDC natif (generic_oauth) vers Keycloak.
|
||||
8
roles/serveur_icinga/meta/authentification.yml
Normal file
8
roles/serveur_icinga/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Coeur de supervision, sans interface : l'UI est serveur_icingaweb2.
|
||||
12
roles/serveur_icingaweb2/meta/authentification.yml
Normal file
12
roles/serveur_icingaweb2/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: web-sso
|
||||
mecanisme: oauth2-proxy
|
||||
formulaire_local: ferme
|
||||
secours: "icingacli user ... (via sudo sur l'hote)"
|
||||
raison: >-
|
||||
Pas d'OIDC natif : la passerelle oauth2-proxy porte l'authentification et
|
||||
transmet l'identite en en-tete. L'hote n'est pas expose directement.
|
||||
11
roles/serveur_keycloak/meta/authentification.yml
Normal file
11
roles/serveur_keycloak/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: socle-identite
|
||||
secours: "kcadm.sh / bin/kc.sh (sudo)"
|
||||
raison: >-
|
||||
EST le fournisseur d'identite : il ne peut pas deleguer son authentification a
|
||||
lui-meme. Son administrateur local est, par construction, l'avant-dernier
|
||||
maillon avant sudo.
|
||||
9
roles/serveur_loki/meta/authentification.yml
Normal file
9
roles/serveur_loki/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: interne-sans-auth
|
||||
raison: >-
|
||||
API HTTP sans authentification, joignable DANS le tenant. Non exposee au plan.
|
||||
Consommee par Grafana, pas par un humain — mais rien ne l'empeche.
|
||||
12
roles/serveur_nextcloud/meta/authentification.yml
Normal file
12
roles/serveur_nextcloud/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: web-sso
|
||||
mecanisme: natif
|
||||
formulaire_local: masque
|
||||
secours: "occ user:resetpassword <utilisateur>"
|
||||
raison: >-
|
||||
Interface humaine exposee ; app user_oidc. `hide_login_form` MASQUE seulement
|
||||
: `?direct=1` reste le chemin de secours documente par l'amont.
|
||||
8
roles/serveur_nginx/meta/authentification.yml
Normal file
8
roles/serveur_nginx/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Frontal HTTP : il sert et relaie, il n'authentifie pas d'humain.
|
||||
12
roles/serveur_oauth2_proxy/meta/authentification.yml
Normal file
12
roles/serveur_oauth2_proxy/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: web-sso
|
||||
mecanisme: natif
|
||||
formulaire_local: aucun
|
||||
secours: "systemctl stop oauth2-proxy, puis joindre l'amont en local (sudo)"
|
||||
raison: >-
|
||||
C'EST la passerelle SSO generique. Elle n'a pas de compte propre : sans elle,
|
||||
l'amont n'est joignable qu'en local.
|
||||
10
roles/serveur_openldap/meta/authentification.yml
Normal file
10
roles/serveur_openldap/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: socle-identite
|
||||
secours: "ldapmodify sur cn=config / cn=admin (sudo)"
|
||||
raison: >-
|
||||
EST la source de verite des comptes. Son cn=admin est la racine de la chaine
|
||||
de secours : si LDAP tombe, plus rien n'authentifie.
|
||||
8
roles/serveur_postfix/meta/authentification.yml
Normal file
8
roles/serveur_postfix/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: ldap-direct
|
||||
raison: >-
|
||||
SMTP ne parle pas OIDC. Consultation LDAP pour les identites et la remise.
|
||||
8
roles/serveur_postgresql/meta/authentification.yml
Normal file
8
roles/serveur_postgresql/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Comptes de service, secrets en voute ; pas d'humain.
|
||||
9
roles/serveur_powerdns/meta/authentification.yml
Normal file
9
roles/serveur_powerdns/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
DNS autoritatif ; l'API est joignable en local avec une cle, pas par un
|
||||
humain.
|
||||
10
roles/serveur_prometheus/meta/authentification.yml
Normal file
10
roles/serveur_prometheus/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: interne-sans-auth
|
||||
raison: >-
|
||||
Interface web sans authentification, joignable DANS le tenant. Non exposee au
|
||||
plan (aucun `expose`). Lacune connue : oauth2-proxy s'appliquerait, comme
|
||||
devant Icinga Web 2.
|
||||
8
roles/serveur_redis/meta/authentification.yml
Normal file
8
roles/serveur_redis/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Cache local, secret en voute ; pas d'humain.
|
||||
8
roles/serveur_rspamd/meta/authentification.yml
Normal file
8
roles/serveur_rspamd/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Filtrage courriel ; l'IHM de contrôle n'est pas activee.
|
||||
8
roles/serveur_step_ca/meta/authentification.yml
Normal file
8
roles/serveur_step_ca/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
AC interne : les hotes s'enrolent par jeton de provisionneur, pas par compte.
|
||||
9
roles/serveur_web_dorsal/meta/authentification.yml
Normal file
9
roles/serveur_web_dorsal/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Execute l'application du tenant ; l'authentification appartient a cette
|
||||
application.
|
||||
8
roles/serveur_web_frontal/meta/authentification.yml
Normal file
8
roles/serveur_web_frontal/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29 : un role sans cette
|
||||
# declaration, ou dont la declaration contredit son code, fait echouer le harnais.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
raison: >-
|
||||
Sert des fichiers statiques.
|
||||
|
|
@ -163,6 +163,94 @@ def preuve_modele_socle() -> tuple[bool, str]:
|
|||
return True, "Modele public socle : domaines/serveurs/applications/bases valides."
|
||||
|
||||
|
||||
PORTEES_AUTH = {"web-sso", "socle-identite", "ldap-direct", "interne-sans-auth",
|
||||
"sans-auth-humaine"}
|
||||
|
||||
|
||||
def preuve_authentification() -> tuple[bool, str]:
|
||||
"""Chaque role serveur_* declare sa position d'authentification, et ne ment pas.
|
||||
|
||||
La directive (D-38..D-41) est une regle de conduite : sans garde, elle finit par
|
||||
ne plus etre vraie — c'est ce qui etait arrive aux 28 lignes d'integration
|
||||
recopiees. La declaration seule ne suffirait pas non plus : on verifie qu'elle
|
||||
est COHERENTE avec le code du role.
|
||||
|
||||
Echecs : role sans declaration, portee inconnue, `web-sso` sans acces de secours,
|
||||
`web-sso natif` avec compte local mais sans reglage `<role>_connexion_locale`,
|
||||
`web-sso natif` dont le code ne mentionne aucun OIDC, `ldap-direct` sans
|
||||
reference LDAP.
|
||||
|
||||
Les portees `interne-sans-auth` sont COMPTEES, pas refusees : ce sont des lacunes
|
||||
assumees et nommees. Les taire les ferait oublier ; les refuser bloquerait le
|
||||
harnais sur une decision deja prise.
|
||||
"""
|
||||
import yaml as _yaml
|
||||
roles = RACINE / "roles"
|
||||
fautes, lacunes, par_portee = [], [], {}
|
||||
for d in sorted(x for x in roles.iterdir() if x.is_dir() and x.name.startswith("serveur_")):
|
||||
meta = d / "meta" / "authentification.yml"
|
||||
if not meta.is_file():
|
||||
fautes.append(f"{d.name}: aucune declaration (meta/authentification.yml)")
|
||||
continue
|
||||
decl = ((_yaml.safe_load(meta.read_text(encoding="utf-8")) or {})
|
||||
.get("authentification") or {})
|
||||
portee = str(decl.get("portee", "")).strip()
|
||||
if portee not in PORTEES_AUTH:
|
||||
fautes.append(f"{d.name}: portee '{portee}' inconnue")
|
||||
continue
|
||||
par_portee.setdefault(portee, []).append(d.name)
|
||||
if not str(decl.get("raison", "")).strip():
|
||||
fautes.append(f"{d.name}: portee sans raison")
|
||||
|
||||
# Confronter la declaration au CODE. Les premieres versions cherchaient les
|
||||
# mots « ldap » / « oidc » dans le role : trop faible — le mot « LDAP » dans un
|
||||
# commentaire de serveur_grafana suffisait a valider une declaration
|
||||
# `ldap-direct` mensongere. On exige donc des indices NOMMES : une variable du
|
||||
# namespace du role, ou une URI de schema ldap. Une phrase en prose n'en cree pas.
|
||||
corps = ""
|
||||
for f in d.rglob("*"):
|
||||
if f.is_file() and f.suffix in (".yml", ".j2"):
|
||||
corps += f.read_text(encoding="utf-8", errors="ignore").lower()
|
||||
defauts = {}
|
||||
fd = d / "defaults" / "main.yml"
|
||||
if fd.is_file():
|
||||
defauts = _yaml.safe_load(fd.read_text(encoding="utf-8")) or {}
|
||||
preuve_ldap = (f"{d.name}_ldap" in corps or "ldap://" in corps or "ldaps://" in corps)
|
||||
preuve_oidc = any(f"{d.name}_{m}" in corps for m in ("oidc", "oauth"))
|
||||
|
||||
if portee in ("web-sso", "socle-identite") and not str(decl.get("secours", "")).strip():
|
||||
fautes.append(f"{d.name}: {portee} sans acces de secours declare")
|
||||
if portee == "web-sso":
|
||||
if decl.get("mecanisme") not in ("natif", "oauth2-proxy"):
|
||||
fautes.append(f"{d.name}: web-sso sans mecanisme (natif|oauth2-proxy)")
|
||||
# `aucun` = le service n'a pas de compte local du tout (une passerelle).
|
||||
# Sans cette valeur, on serait tente d'ecrire « ferme » — ce qui laisserait
|
||||
# croire qu'une porte a ete fermee alors qu'il n'y en a jamais eu.
|
||||
if decl.get("formulaire_local") not in ("ferme", "masque", "ouvert", "aucun"):
|
||||
fautes.append(f"{d.name}: web-sso sans posture de formulaire local")
|
||||
if decl.get("mecanisme") == "natif" and decl.get("formulaire_local") != "aucun":
|
||||
# DEFINI dans defaults, pas seulement reference ailleurs : le gabarit
|
||||
# peut citer la variable alors que plus rien ne lui donne de valeur.
|
||||
if f"{d.name}_connexion_locale" not in defauts:
|
||||
fautes.append(f"{d.name}: web-sso natif sans reglage "
|
||||
f"{d.name}_connexion_locale dans defaults/main.yml")
|
||||
if not preuve_oidc:
|
||||
fautes.append(f"{d.name}: se declare web-sso natif, mais aucune "
|
||||
f"variable {d.name}_oidc/_oauth dans son code")
|
||||
if portee == "ldap-direct" and not preuve_ldap:
|
||||
fautes.append(f"{d.name}: se declare ldap-direct, mais aucune variable "
|
||||
f"{d.name}_ldap ni URI ldap:// dans son code")
|
||||
if portee == "interne-sans-auth":
|
||||
lacunes.append(d.name)
|
||||
|
||||
if fautes:
|
||||
return False, "; ".join(fautes[:6])
|
||||
total = sum(len(v) for v in par_portee.values())
|
||||
resume = ", ".join(f"{k} {len(v)}" for k, v in sorted(par_portee.items()))
|
||||
suffixe = f" ; {len(lacunes)} lacune(s) nommee(s) : {', '.join(lacunes)}" if lacunes else ""
|
||||
return True, f"{total} role(s) serveur declares ({resume}){suffixe}."
|
||||
|
||||
|
||||
def preuve_propriete_des_intrants() -> tuple[bool, str]:
|
||||
"""Aucune cle de l'HEBERGEUR ne subsiste dans un group_vars de tenant.
|
||||
|
||||
|
|
@ -380,6 +468,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_propriete_des_intrants},
|
||||
{"id": "P28", "titre": "Pools Proxmox : un par tenant, sans collision", "refs": ["AFF-110"],
|
||||
"cmds": [[sys.executable, "scripts/devis_proxmox_pools.py", "--verifier"]]},
|
||||
{"id": "P29", "titre": "Authentification : chaque role declare sa position", "refs": ["AFF-111"],
|
||||
"func": preuve_authentification},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue