materiel : la frontiere expose ses temperatures, seul le Prometheus du site les lit
os-node_exporter sur la patte de supervision, declare dans la carte du boitier : cible et hote Icinga en derivent. Les flux vers la frontiere ne s'ouvrent plus a tous pour un role qui n'est pas le socle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
49ceb81a4d
commit
ee386dca00
7 changed files with 87 additions and 12 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,5 +1,26 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-17 (2) — La frontiere rejoint la supervision du materiel
|
||||
|
||||
- **Exportateur** : greffon officiel `os-node_exporter` installe et configure par l'API
|
||||
d'OPNsense, n'ecoutant que sur la patte de supervision (`10.37.36.1:9100`), collecteurs
|
||||
utiles seulement. Declare dans `opnsense.yml` (`opnsense_exportateur_metriques`) : c'est ce
|
||||
fait du boitier qui fait deriver la cible Prometheus (`job="frontiere"`, `hote="bifrost-1"`)
|
||||
et l'hote Icinga — sans lui, rien n'est derive.
|
||||
- **Flux** `serveur_prometheus -> frontiere:9100`. Le bloc des flux vers la frontiere ouvrait
|
||||
TOUT flux a chaque locataire et chaque zone : juste pour l'heure du socle, beaucoup trop
|
||||
large pour une scrutation. Un role autre que le socle ne part desormais que de SES machines
|
||||
du site. Et le saut des flux `frontiere` chez les locataires arrive AVANT la creation de
|
||||
l'alias du role : le plan montrait deux alias `SETOPS_*_SRV_PROMETHEUS` que rien ne
|
||||
referencait.
|
||||
- Plan de la frontiere : 1 regle (opt9, tcp/9100, `SETOPS_SITE_SERVEUR_PROMETHEUS ->
|
||||
SETOPS_FRONTIERE`), appliquee et chargee.
|
||||
- **Carte d'orientation** : les equipements ne sont plus « sans supervision » (hyperviseurs et
|
||||
frontiere) ; les commutateurs le restent.
|
||||
|
||||
Mesure : `up{job="frontiere"}` = 1 ; `check_materiel.py --hote bifrost-1` : OK, 4 coeurs a
|
||||
41 °C. `make prouver` : CONFORME, 82 OK.
|
||||
|
||||
## 2026-09-17 (1) — Le materiel se regarde et se juge : hyperviseurs
|
||||
|
||||
Les hyperviseurs exposaient deja 68 temperatures, leurs ventilateurs, SMART et l'usure NVMe,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
| 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), 1 nom(s) surveille(s) sans reference orpheline. |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 42 groupes classes, aucun cycle, aucune arete en arriere ; playbooks/site.yml a jour. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 119 flux, schéma + matrice OK. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 40 rôles, 120 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 | serveur_resolveur_site |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 71 scripts expliques et atteignables, 131 cibles make documentees, 69 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (147 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 42 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 48 document(s) declarent leur lecteur (42 genere(s) exempte(s)). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 48 document(s) declarent leur lecteur (43 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 8 hote(s) de l'ecosysteme et 3 du site detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
|
|
@ -60,9 +60,9 @@
|
|||
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
|
||||
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
|
||||
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
|
||||
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 97 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
|
||||
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (137 lignes). |
|
||||
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 282 regles `pass`), tous non consignes et tous motives. |
|
||||
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ✅ OK | La carte designe 99 chemin(s) qui existent, et ses 7 chiffres correspondent a la mesure. |
|
||||
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (138 lignes). |
|
||||
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 283 regles `pass`), tous non consignes et tous motives. |
|
||||
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
|
||||
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
|
||||
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 13 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ README de rôles). Cette page comble ces deux trous.
|
|||
| rôles | 69 | `roles/*/` |
|
||||
| README de rôles | 69 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
||||
| documents | 44 | `docs/*.md` |
|
||||
| pièces d'audit | 47 | `docs/audit/*` |
|
||||
| pièces d'audit | 48 | `docs/audit/*` |
|
||||
| unités de wiki | 27 | `wiki/*.md` |
|
||||
| décisions en vigueur | 85 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
||||
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
||||
|
|
@ -72,7 +72,7 @@ Ce que je re-découvre sinon. **Consulter avant de concevoir un nouveau mécanis
|
|||
| Voûte au déploiement | secret jamais en clair ; **une voûte, une clé** depuis le 2026-08-28 | `ANSIBLE_VAULT_IDENTITY_LIST` construit par `scripts/voutes.py` (clé nommée `~/.config/setops-vault-<dépôt>`) ; déréférencé par `lookup('vars', <nom>)` | `autorisation.md` §6.1 |
|
||||
| Multi-instance | un dépôt par écosystème ; l'active = symlink `instance/`, les autres **découvertes par convention** (dossiers frères, aucun registre) | active : symlink `instance/` ; découverte : `scripts/instances.py` / `devis_reseau.py` (glob `../*/plan/nomenclature.yml` avec `index`) ; garde-fou collision : preuve **P21** | `multi-instances.md` |
|
||||
| Exposition → edge | app expose un FQDN public servi par un edge | `plan/domaines.yml` + `expose` (applications) | `bindings-conception.md` §4 |
|
||||
| Exploitation de l'hébergeur | ses **opérations** n'appartiennent à aucun tenant et restent **hors overlay** | **à moitié construit** : les *VM* du site ont leur inventaire (`scripts/site_inventaire.py`), leur socle, leur durcissement, leurs sauvegardes et leur supervision (`site-mon-01`, 2026-09-02). Les *équipements* — hyperviseurs, commutateurs, frontière — n'ont toujours ni inventaire, ni sauvegarde de configuration, ni supervision | `hebergeur-exploitation.md` |
|
||||
| Exploitation de l'hébergeur | ses **opérations** n'appartiennent à aucun tenant et restent **hors overlay** | **à moitié construit** : les *VM* du site ont leur inventaire (`scripts/site_inventaire.py`), leur socle, leur durcissement, leurs sauvegardes et leur supervision (`site-mon-01`, 2026-09-02). Les *équipements* : hyperviseurs et frontière **supervisés** depuis le 2026-09-17 — températures, ventilateurs, SMART, usure NVMe, un catalogue (`scripts/materiel.py`) lu par le tableau Grafana « Matériel » et le service Icinga `materiel` (`supervision-conception.md`). Les commutateurs restent sans supervision ; aucun équipement n'a de sauvegarde de configuration | `hebergeur-exploitation.md` |
|
||||
| Authentification | web → Keycloak ; LDAP source unique ; secours par `sudo`, formulaire local non annoncé | `<rôle>_connexion_locale: false` (grafana, forgejo, nextcloud) ; garde de version Forgejo ≥ 10 | `authentification.md` |
|
||||
| Accès & habilitations | Set-OPS **amorce** un accès sysadmin puis se retire ; les appartenances aux groupes ne sont **jamais réconciliées** — c'est une personne qui gouverne | **construit et éprouvé** (2026-08-08) : rôle `amorcage_acces` (idempotence par existence, D-67), groupes projetés en rôles par `serveur_keycloak`, `meta/acces.yml` dans les 5 rôles web ; chaîne LDAP → Keycloak → groupe → service exercée de bout en bout sur Icinga Web 2 | `autorisation.md` (§6 = runbook de reprise) |
|
||||
| **DNS public** | le locataire **écrit** ses zones `autorite: primaire-cache`, le site les **sert** en secondaire, un site pair les réplique. Transfert ouvert **à la clé TSIG seule** (`allow-axfr-ips` et TSIG sont alternatifs dans PowerDNS) ; instance `pdns@public` à part pour que le site ne puisse jamais interroger la zone `.internal` | `roles/serveur_dns_public/`, `roles/serveur_powerdns/tasks/zones-publiques.yml` ; relations dérivées dans `scripts/site_inventaire.py` ; mots de flux `dns_public_site` / `primaires_dns_locataires` ; garde **P82** | `dns-interne.md` §Zones publiques |
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
| `serveur_powerdns` | egress | 53 | tcp | externe | clair | Repli TCP de la résolution sortante du serveur autoritatif (réponses dépassant la taille UDP). |
|
||||
| `serveur_prometheus` | ingress | 9090 | tcp | localhost | clair | Console Prometheus consommée en local par Grafana co-localisé (pas d'exposition inter-nœud). |
|
||||
| `serveur_prometheus` | egress | 9100 | tcp | client_metrique | tls | Scrape des node_exporter (HTTPS via cert step-ca) sur chaque nœud instrumenté. |
|
||||
| `serveur_prometheus` | egress | 9100 | tcp | frontiere | clair | Scrape de l'exportateur de la frontiere (os-node_exporter), sur sa seule patte de supervision : l'equipement qui voit passer TOUT le trafic n'avait ni temperature ni charge dans la supervision. |
|
||||
| `serveur_prometheus` | egress | 9100 | tcp | fabric | clair | Scrape des hyperviseurs : la machine qui PORTE les VM etait invisible de la supervision qui les surveille. Elle ne peut pas pousser (route par defaut gelee, D-57), donc on la tire. |
|
||||
| `serveur_redis` | ingress | 6379 | tcp | localhost | clair | Cache/verrous consommés uniquement par l'application co-localisée (ex. Nextcloud). Aucune exposition inter-nœud. |
|
||||
| `serveur_resolveur` | ingress | 53 | udp | flotte | clair | Toute la flotte du tenant résout ici — et nulle part ailleurs. |
|
||||
|
|
@ -128,7 +129,7 @@
|
|||
|
||||
## Synthèse chiffrement
|
||||
|
||||
- **clair** : 49 flux
|
||||
- **clair** : 50 flux
|
||||
- **n-a** : 8 flux
|
||||
- **ssh** : 8 flux
|
||||
- **starttls** : 6 flux
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@ flux:
|
|||
# Prometheus tire donc, dans le sens que la frontiere route deja pour `serveur_ops_site`.
|
||||
# 7887 lignes de metriques sur `asgard`, dont 1005 unites systemd avec leur etat — soit
|
||||
# davantage que ce qu'un ping ou un porteur de sante auraient dit.
|
||||
# LA FRONTIERE ELLE-MEME (2026-09-17) : ses temperatures, sa charge, ses interfaces. Le
|
||||
# greffon `os-node_exporter` n'ecoute que sur sa patte de supervision.
|
||||
- sens: egress
|
||||
port: 9100
|
||||
protocole: tcp
|
||||
pair: frontiere
|
||||
chiffrement: clair
|
||||
raison: >-
|
||||
Scrape de l'exportateur de la frontiere (os-node_exporter), sur sa seule patte de
|
||||
supervision : l'equipement qui voit passer TOUT le trafic n'avait ni temperature ni
|
||||
charge dans la supervision.
|
||||
- sens: egress
|
||||
port: 9100
|
||||
protocole: tcp
|
||||
|
|
|
|||
|
|
@ -535,6 +535,12 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
tenants_sans_inventaire.append(nom)
|
||||
continue
|
||||
for role, fl in retenus:
|
||||
# AVANT L'ALIAS (2026-09-17). Le saut plus bas arrivait APRES la creation de
|
||||
# l'alias du role : un flux vers la frontiere declare par `serveur_prometheus`
|
||||
# posait `SETOPS_<tenant>_SRV_PROMETHEUS` chez chaque locataire, qu'aucune regle
|
||||
# ne referencait. Le plan l'a montre avant toute ecriture.
|
||||
if "frontiere" in _pairs(fl):
|
||||
continue
|
||||
if role == ROLE_SOCLE:
|
||||
destination = alias_tenant
|
||||
else:
|
||||
|
|
@ -1339,7 +1345,7 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
alias["SETOPS_FRONTIERE"] = {
|
||||
"type": "host",
|
||||
"contenu": _pattes_frontiere,
|
||||
"description": "Pattes de la frontiere active (autorite de temps)",
|
||||
"description": "Pattes de la frontiere active (heure, metriques)",
|
||||
}
|
||||
# LES PATTES D'ARRIVEE. Un locataire entre par le lien de transit — ses
|
||||
# passerelles de zone sont tenues par le SDN, pas par la frontiere. Une
|
||||
|
|
@ -1350,7 +1356,23 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
_portees_f += [("SETOPS_SITE", z) for z in
|
||||
sorted({_if_de(m.get("reseau")) for m in _machines_site})]
|
||||
for _role_f, _fl_f in _flux_frontiere:
|
||||
for _src_f, _if_f in _portees_f:
|
||||
# LE SOCLE PARLE A LA FRONTIERE DEPUIS PARTOUT ; UN ROLE, DEPUIS SES MACHINES
|
||||
# (2026-09-17). L'heure est demandee par toutes les machines : les portees
|
||||
# ci-dessus sont justes pour elle. La scrutation des metriques de la
|
||||
# frontiere, elle, n'est l'affaire que du Prometheus du SITE — l'ouvrir a
|
||||
# chaque locataire et a chaque zone aurait donne la carte de charge du
|
||||
# boitier a qui la demande.
|
||||
if _role_f == "serveur_debian":
|
||||
_portees_role = _portees_f
|
||||
else:
|
||||
_portees_role = [(nom_alias(_role_f, "SITE"), z) for z in
|
||||
sorted({_if_de(m.get("reseau")) for m in _machines_site
|
||||
if _role_f in _services_site.get(m["nom"], [])})]
|
||||
if not _portees_role:
|
||||
print(f"note : {_role_f} declare un flux vers la frontiere et aucune "
|
||||
f"machine du site ne le porte — aucune regle emise.",
|
||||
file=sys.stderr)
|
||||
for _src_f, _if_f in _portees_role:
|
||||
regles.append({
|
||||
"sens": "in",
|
||||
"interface": _if_f,
|
||||
|
|
|
|||
|
|
@ -536,6 +536,21 @@ def inventaire() -> dict:
|
|||
if str(h.get("role")) == "hyperviseur" and str(h.get("ip", "")).startswith("192.168.11.")}
|
||||
_fabric_metriques = sorted(_fabric_nommes)
|
||||
|
||||
# LA FRONTIERE, QUAND SON EXPORTATEUR EST DECLARE (2026-09-17). L'adresse est celle de sa
|
||||
# patte sur la zone declaree, lue dans l'underlay ; le nom est celui du boitier.
|
||||
_frontiere_metriques: dict[str, str] = {}
|
||||
try:
|
||||
_carte_o = U.chemin().resolve().parent / "opnsense.yml" if U.chemin() else None
|
||||
_expo_f = ((yaml.safe_load(_carte_o.read_text(encoding="utf-8")) or {})
|
||||
.get("opnsense_exportateur_metriques") or {}) if _carte_o and _carte_o.is_file() else {}
|
||||
except (OSError, yaml.YAMLError):
|
||||
_expo_f = {}
|
||||
if _expo_f:
|
||||
for _h in U.hotes(u):
|
||||
if (str(_h.get("role")) == "frontiere" and str(_h.get("etat") or "actif") == "actif"
|
||||
and str(_h.get("reseau")) == str(_expo_f.get("patte")) and _h.get("ip")):
|
||||
_frontiere_metriques[f"{_h['ip']}:{_expo_f.get('port', 9100)}"] = str(_h.get("nom"))
|
||||
|
||||
# CE QUE LA SUPERVISION IRA VOIR : chaque patte de la frontiere, nommee par la zone
|
||||
# qu'elle sert. Les commutateurs restent dehors — D-48 les tient hors flotte, et rien
|
||||
# ne les rend interrogeables sans leur ouvrir un acces qu'on ne veut pas leur ouvrir.
|
||||
|
|
@ -827,7 +842,10 @@ def inventaire() -> dict:
|
|||
"setops_materiel_hotes": [
|
||||
{"nom": _n, "adresse": _c.rsplit(":", 1)[0], "job": "fabric",
|
||||
"role": "hyperviseur"}
|
||||
for _c, _n in sorted(_fabric_nommes.items(), key=lambda x: x[1])]}
|
||||
for _c, _n in sorted(_fabric_nommes.items(), key=lambda x: x[1])]
|
||||
+ [{"nom": _n, "adresse": _c.rsplit(":", 1)[0], "job": "frontiere",
|
||||
"role": "frontiere"}
|
||||
for _c, _n in sorted(_frontiere_metriques.items(), key=lambda x: x[1])]}
|
||||
if (_fabric_nommes and ({"serveur_grafana", "serveur_icinga"}
|
||||
& set(_groupes_de(nom)))) else {}),
|
||||
# La fabric, en job separe — voir `_fabric_metriques` plus haut.
|
||||
|
|
@ -835,7 +853,9 @@ def inventaire() -> dict:
|
|||
# `hotes` : chaque serie porte `hote="asgard"`. Sans lui, un tableau du
|
||||
# materiel legende « 192.168.11.43:9100 » — et c'est a l'exploitant de se
|
||||
# souvenir quelle machine chauffe.
|
||||
[{"job": "fabric", "cibles": _fabric_metriques, "hotes": _fabric_nommes}]}
|
||||
[{"job": "fabric", "cibles": _fabric_metriques, "hotes": _fabric_nommes}]
|
||||
+ ([{"job": "frontiere", "cibles": sorted(_frontiere_metriques),
|
||||
"hotes": _frontiere_metriques}] if _frontiere_metriques else [])}
|
||||
if (_fabric_metriques and "serveur_prometheus" in _groupes_de(nom)) else {}),
|
||||
**communes,
|
||||
# En dernier : ce qu'une machine declare d'elle-meme prime sur ce que le
|
||||
|
|
|
|||
Loading…
Reference in a new issue