flux : plus aucune sortie vers Internet sans un role qui le dise
La sortie 80/443 que le socle donnait a toute machine ne servait plus a apt (tout passe par le cache du site) mais a de la telemetrie et a des sorties non declarees. Elle devient conditionnelle (seulement_si artefacts_amorcage vide) ; la telemetrie de Grafana, Loki, Alloy et la verification de version de Collabora sont coupees a la source ; rspamd et la sonde du genome declarent leur sortie. Le devis du site evalue enfin seulement_si, ce qui retire aussi le 465/587 vers un Postfix du site qui n'ecoute que sur 25. Garde : test_conditions_site.py. Journal (125). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dd4ca09245
commit
ee0e33d452
14 changed files with 296 additions and 15 deletions
66
CHANGELOG.md
66
CHANGELOG.md
|
|
@ -1,5 +1,71 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-10 (125) — Plus aucune machine ne sort vers Internet sans un rôle qui le dise
|
||||
|
||||
**94 preuves.** `make verifier` passe, `make test` aussi avec le nouveau
|
||||
`test_conditions_site.py` ; lancé sur l'ancien code, ce test échoue sur ses quatre
|
||||
contrôles. Le plan de la frontière est vide après application (308 règles inchangées).
|
||||
|
||||
### Une sortie générale qui ne servait plus à ce qu'elle disait
|
||||
|
||||
Le socle donnait à chaque machine des trois écosystèmes une sortie 80/443 vers tout
|
||||
Internet, « pour les dépôts apt ». Sur les 26 machines des locataires, toutes les sources
|
||||
apt sont pourtant en `http://` et passent par le cache du site ; le site vise son propre
|
||||
cache depuis le 2026-09-14. Le journal de la frontière, sur une semaine, et trois relevés
|
||||
de sa table d'états (20 min, 1 h, puis le cache apt du site pour dater les cas rares) ont
|
||||
attribué chaque sortie restante à sa machine :
|
||||
|
||||
- les cartes de rspamd (maps.rspamd.com, ~2 700 connexions par jour et par locataire) et
|
||||
la carte SURBL (sa-update.surbl.org, ~245), depuis les deux `edge-mta-01` ;
|
||||
- grafana.com (versions) et stats.grafana.org (compteurs d'usage), depuis Grafana, Loki,
|
||||
et surtout Alloy, présent sur **toutes** les machines ;
|
||||
- collabora.co.uk, la vérification de version de Collabora toutes les 10 heures ;
|
||||
- le repère du génome de `site-ops-01` vers eregion.
|
||||
|
||||
PyPI, Galaxy, S3 et GitHub n'apparaissent plus après le 2026-10-09 au matin (entrée 121).
|
||||
|
||||
### Coupé à la source, déclaré dans le rôle, retiré du socle
|
||||
|
||||
La télémétrie est coupée là où elle naît : `--disable-reporting` pour Alloy, `analytics:
|
||||
reporting_enabled: false` pour Loki, `GF_ANALYTICS_*`, Gravatar et le fil de nouvelles
|
||||
pour Grafana, `fetch_update_check=0` et `allow_update_popup=false` pour Collabora. Les
|
||||
mises à jour arrivent par apt, comme le reste.
|
||||
|
||||
Ce qui sert vraiment est déclaré dans le rôle qui en a besoin : rspamd sort en 443 et 80
|
||||
(son repli local fige les listes à la version du paquet ; Daniel a choisi de garder des
|
||||
cartes fraîches, comme Postfix garde son 25), `serveur_ops_site` en 443 pour sa sonde.
|
||||
La raison de la sortie du cache du site nomme maintenant le dépôt de binaires.
|
||||
|
||||
La sortie du socle devient conditionnelle (`seulement_si: artefacts_amorcage` vide) : elle
|
||||
ne vaut plus que pour un écosystème sans aucune source d'artefacts.
|
||||
|
||||
### Le devis du site ignorait les conditions
|
||||
|
||||
`make flux` retirait la sortie des machines du site, le devis de la frontière la gardait :
|
||||
la boucle du site n'évaluait pas `seulement_si`, celle des locataires si. Elle l'évalue
|
||||
désormais sur les variables d'hôte de l'inventaire du site (inventaire illisible : la règle
|
||||
reste émise). Second effet, voulu : les règles 465/587 vers le Postfix du site, qui n'écoute
|
||||
que sur 25 (`serveur_postfix_submission_actif` vaut `false`), sont retirées sur six pattes.
|
||||
|
||||
`test_conditions_site.py` refuse qu'un rôle universel (socle, durcissement, `client_*`)
|
||||
ouvre Internet sans condition, et que la boucle du site cesse d'évaluer les conditions.
|
||||
|
||||
### Déployé et mesuré
|
||||
|
||||
Configurations sur les 26 machines des locataires et les 9 du site, sans échec ; vérifiées
|
||||
sur les processus en cours. Les hyperviseurs portent aussi `client_journal` mais n'ont pas
|
||||
été touchés : leur configuration Alloy et leur épinglage n'ont jamais reçu le changement du
|
||||
2026-10-03, écart antérieur à traiter à part. Frontière : 7 créations (deux alias rspamd,
|
||||
quatre règles rspamd, la sonde du génome), 32 retraits, relus. Aucun blocage vers Internet
|
||||
dans le journal après l'application, aucune erreur de carte dans rspamd.
|
||||
|
||||
**Limites.** Trois destinations du site restent non attribuées (Leaseweb 89.149.222.99,
|
||||
Gravatar, freebsd.org, une ou deux fois par jour) ; si elles venaient d'une autre machine
|
||||
que le cache, elles sont désormais bloquées et le journal nommera la machine. Deux
|
||||
connexions isolées par reconstruction (Google pendant l'installation d'Icinga sur `mon-01`,
|
||||
Cloudflare pendant celle de Nextcloud sur `collab-01`) seront bloquées à la prochaine : la
|
||||
reconstruction dira si elles comptaient.
|
||||
|
||||
## 2026-10-09 (124) — La raison du flux doit survivre au chargement du pare-feu
|
||||
|
||||
**94 preuves.** Les huit tests de libellés passent avec `make test`, ainsi que
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -335,6 +335,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
|||
python3 scripts/tests/test_expositions_sans_port.py
|
||||
python3 scripts/tests/test_proxmox_fw_externe.py
|
||||
python3 scripts/tests/test_libelles_parefeu.py
|
||||
python3 scripts/tests/test_conditions_site.py
|
||||
python3 scripts/tests/test_sonde_tcp.py
|
||||
python3 scripts/tests/test_restauration.py
|
||||
python3 scripts/tests/test_repertoires_partages.py
|
||||
|
|
|
|||
|
|
@ -430,3 +430,12 @@ Reste à trancher sur une interface **physique** : `ip ?`, `access-group ?`, `sp
|
|||
déclare 53 en UDP et TCP, la récursion depuis la racine que le choix souverain implique.
|
||||
Le `block out` de la section 5 est donc un vrai default-deny **assumé**, et le devis
|
||||
l'énonce désormais au lieu de le poser en silence.
|
||||
- **La sortie générale n'existe plus — 2026-10-10.** Le 123 était déjà passé à la frontière
|
||||
(2026-09-11). Le 443 et le 80 du socle sont devenus conditionnels (`seulement_si:
|
||||
artefacts_amorcage` vide) : le site et ses locataires prennent apt par le cache du site,
|
||||
et ne les reçoivent plus. Mesurée à la frontière, cette sortie ne servait plus qu'à ce
|
||||
qu'aucun rôle ne déclarait : télémétrie de Grafana, d'Alloy et de Loki et vérification
|
||||
de version de Collabora (coupées à la source), cartes de rspamd et repère du génome
|
||||
(déclarés dans `serveur_rspamd` et `serveur_ops_site`). Chaque sortie vers Internet porte
|
||||
désormais le nom du rôle qui en a besoin. `test_conditions_site.py` refuse qu'un rôle
|
||||
universel en rouvre une sans condition.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
| `serveur_backup_site` | ingress | 22 | tcp | voisins_site | ssh | Les écosystèmes de ce site déposent leur état ici tant qu'ils n'ont pas leur propre dépôt. SFTP par un utilisateur restreint, jamais un compte d'administration : le site reçoit des octets chiffrés par restic, il ne peut pas les lire. |
|
||||
| `serveur_cache_site` | ingress | 3142 | tcp | voisins_site | clair | Servir les caches des écosystèmes voisins. Debian n'est ainsi téléchargé qu'une fois pour tout le site, et le cache ne voit que des requêtes AGRÉGÉES — jamais quelle machine installe quoi. |
|
||||
| `serveur_cache_site` | egress | 80 | tcp | externe | clair | Remplir le cache depuis les dépôts Debian amont. En clair parce que les dépôts apt sont signés : l'intégrité vient de la signature, pas du transport. |
|
||||
| `serveur_cache_site` | egress | 443 | tcp | externe | tls-requis | Les dépôts tiers qui n'existent qu'en HTTPS (smallstep, Grafana, Icinga). Le cache les relaie pour que la flotte n'ait pas à sortir elle-même. |
|
||||
| `serveur_cache_site` | egress | 443 | tcp | externe | tls-requis | Les dépôts tiers qui n'existent qu'en HTTPS (smallstep, Grafana, Icinga), et le dépôt de binaires (roues PyPI, collections Galaxy, versions GitHub, Nextcloud, Forgejo). Le site les relaie pour que la flotte n'ait pas à sortir elle-même. |
|
||||
| `serveur_collabora` | ingress | 9980 | tcp | edge | clair | Éditeur servi au navigateur via l'edge (WebSocket WOPI ; TLS terminé à l'edge). |
|
||||
| `serveur_collabora` | ingress | 9980 | tcp | localhost | clair | Vérifications WOPI serveur→Collabora depuis Nextcloud co-localisé. |
|
||||
| `serveur_debian` | ingress | 22 | tcp | flotte, externe | ssh | Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). |
|
||||
|
|
@ -31,9 +31,9 @@
|
|||
| `serveur_debian` | ingress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. |
|
||||
| `serveur_debian` | egress | 53 | udp | frontiere | n-a | Résolution de noms à l'amorçage, avant que le résolveur de l'écosystème n'existe. Sans elle, les machines d'un site neuf ne peuvent pas résoudre leurs dépôts de paquets — et rien ne peut donc s'installer, y compris le résolveur lui-même. |
|
||||
| `serveur_debian` | egress | 53 | tcp | frontiere | n-a | Réponses longues et bascule TCP, obligatoires en DNS. Déclarer l'UDP sans le TCP donne une résolution qui marche jusqu'à la première réponse tronquée. |
|
||||
| `serveur_debian` | egress | 80 | tcp | externe | clair | Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport). |
|
||||
| `serveur_debian` | egress | 80 | tcp | externe | clair | Dépôts apt en clair et redirections des miroirs, pour un écosystème SANS amont d'artefacts. L'intégrité vient de la signature des paquets, pas du transport. |
|
||||
| `serveur_debian` | egress | 123 | udp | frontiere | n-a | Synchronisation d'horloge (NTP) contre la frontière, autorité de temps de l'écosystème. Une dérive fait échouer la validation des certificats step-ca et l'authentification SSO. |
|
||||
| `serveur_debian` | egress | 443 | tcp | externe | tls-requis | Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre. |
|
||||
| `serveur_debian` | egress | 443 | tcp | externe | tls-requis | Dépôts apt en direct, pour un écosystème SANS aucune source d'artefacts. Le site et ses locataires passent par le cache du site et ne la reçoivent pas. |
|
||||
| `serveur_debian` | egress | frag-needed | icmp | externe | n-a | ICMP « fragmentation nécessaire » sortant : c'est ainsi que nos hôtes signalent l'overlay à 1450 aux correspondants distants. |
|
||||
| `serveur_dns_public` | ingress | 53 | udp | voisins_site | clair | NOTIFY des primaires des locataires du site : une zone publique a change. Le contenu d'une zone publique n'a rien de secret ; ce qui compte est QUI peut le modifier, et c'est TSIG qui le garde, sur le transfert. |
|
||||
| `serveur_dns_public` | ingress | 53 | tcp | voisins_site | clair | Repli TCP des notifications des primaires des locataires. |
|
||||
|
|
@ -89,6 +89,7 @@
|
|||
| `serveur_ops_site` | egress | 22 | tcp | fabric | ssh | Shell des hyperviseurs : ce que l'API ne couvre pas — configuration reseau, ponts, deplacement de disques. Un pouvoir distinct de l'API, donc declare a part. |
|
||||
| `serveur_ops_site` | egress | 22 | tcp | serveur_ops_tenant | ssh | Insemination : amorcer le runner d'un tenant — socle, moteur, plan, plancher de resolution — pour qu'il prenne ensuite le relais sur ses propres machines. Ne transporte aucun secret : la voute est remise par un humain. |
|
||||
| `serveur_ops_site` | egress | 443 | tcp | fabric | tls-requis | API de la frontiere OPNsense : poser les alias et les regles qui ouvrent les flux du tenant qu'on materialise. Preparer le terrain sans cela laisserait un terrain injoignable. |
|
||||
| `serveur_ops_site` | egress | 443 | tcp | externe | tls-requis | Repere du genome : `git ls-remote` anonyme sur eregion (forge.alliance-boreale.ca) pour dire si la forge du site a pris du retard. Lecture seule, sans identifiant. |
|
||||
| `serveur_ops_site` | egress | 8006 | tcp | fabric | tls-requis | API de l'hyperviseur : créer, cloner et détruire les VM de la fabric. Le seul flux par lequel un écosystème peut en matérialiser un autre. |
|
||||
| `serveur_ops_tenant` | ingress | 22 | tcp | runner_site | ssh | Insémination : le runner du SITE amorce ce runner-ci — socle, moteur, plan, plancher de résolution — jusqu'à ce qu'un humain lui remette sa voûte. Vers cette machine seule, jamais vers le reste de l'écosystème. |
|
||||
| `serveur_postfix` | ingress | 25 | tcp | externe, client_smtp | starttls | SMTP entrant : courrier externe (MX) et notifications internes (client_smtp). |
|
||||
|
|
@ -119,6 +120,8 @@
|
|||
| `serveur_resolveur_site` | ingress | 53 | tcp | voisins_site | clair | Réponses longues et bascule TCP, obligatoires en DNS. |
|
||||
| `serveur_rspamd` | ingress | 11332 | tcp | localhost | clair | Protocole milter consommé par Postfix co-localisé (analyse + signature DKIM). Local uniquement. |
|
||||
| `serveur_rspamd` | ingress | 11334 | tcp | localhost | clair | Interface de contrôle rspamd (statistiques, apprentissage) en local. |
|
||||
| `serveur_rspamd` | egress | 80 | tcp | externe | clair | Carte de hachages SURBL (sa-update.surbl.org), publiee en clair par l'amont. Une carte alteree ne peut que mal classer un courriel ; elle n'execute rien. |
|
||||
| `serveur_rspamd` | egress | 443 | tcp | externe | tls-requis | Cartes de rspamd (maps.rspamd.com) : messageries gratuites et jetables, redirecteurs, listes blanches DMARC/SPF/DKIM, hameconnage. Sans elles l'antispam garde les copies du paquet, qui vieillissent. |
|
||||
| `serveur_step_ca` | ingress | 8443 | tcp | flotte | tls-requis | ACME + API step-ca : chaque nœud (client_pki) émet/renouvelle ses certificats et récupère la racine. |
|
||||
| `serveur_web_dorsal` | ingress | 80 | tcp | edge, serveur_web_frontal | clair | Front nginx local des webapps et des sites statiques, relaye par l'edge (noms internes) et par le web frontal (noms publics). |
|
||||
| `serveur_web_dorsal` | egress | 443 | tcp | flotte | tls | git clone/pull du depot de chaque app (Forgejo souverain) au deploiement. |
|
||||
|
|
@ -128,10 +131,10 @@
|
|||
|
||||
## Synthèse chiffrement
|
||||
|
||||
- **clair** : 51 flux
|
||||
- **clair** : 52 flux
|
||||
- **n-a** : 8 flux
|
||||
- **ssh** : 8 flux
|
||||
- **starttls** : 6 flux
|
||||
- **tls** : 10 flux
|
||||
- **tls-cible** : 2 flux
|
||||
- **tls-requis** : 34 flux
|
||||
- **tls-requis** : 36 flux
|
||||
|
|
|
|||
|
|
@ -194,12 +194,17 @@
|
|||
notify: Redemarrer alloy
|
||||
|
||||
# Le port d'ecoute est IMPOSE ici plutot que subi : voir defaults/main.yml.
|
||||
- name: Imposer le port de l interface locale d Alloy
|
||||
#
|
||||
# AUCUN RAPPORT D'USAGE (mesure du 2026-10-10). Par defaut, Alloy envoie a
|
||||
# stats.grafana.org la liste de ses composants actifs : relevé a la frontiere depuis
|
||||
# CHAQUE machine des trois ecosystemes, ~70 connexions par jour et par locataire. Il
|
||||
# passait par la sortie 443 generale du socle, qui n'existe plus.
|
||||
- name: Imposer le port de l interface locale d Alloy, sans rapport d usage
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/default/alloy
|
||||
regexp: "^CUSTOM_ARGS="
|
||||
line: >-
|
||||
CUSTOM_ARGS="--server.http.listen-addr={{ client_journal_http_adresse }}:{{ client_journal_http_port }}"
|
||||
CUSTOM_ARGS="--server.http.listen-addr={{ client_journal_http_adresse }}:{{ client_journal_http_port }} --disable-reporting"
|
||||
create: false
|
||||
notify: Redemarrer alloy
|
||||
|
||||
|
|
|
|||
|
|
@ -42,5 +42,6 @@ flux:
|
|||
pair: externe
|
||||
chiffrement: tls-requis
|
||||
raison: >-
|
||||
Les dépôts tiers qui n'existent qu'en HTTPS (smallstep, Grafana, Icinga). Le cache
|
||||
les relaie pour que la flotte n'ait pas à sortir elle-même.
|
||||
Les dépôts tiers qui n'existent qu'en HTTPS (smallstep, Grafana, Icinga), et le
|
||||
dépôt de binaires (roues PyPI, collections Galaxy, versions GitHub, Nextcloud,
|
||||
Forgejo). Le site les relaie pour que la flotte n'ait pas à sortir elle-même.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
# Surcharge de l'unité du paquet. Le `coolwsd.xml` livré (439 lignes commentées) reste
|
||||
# intact : le remplacer par un gabarit maison obligerait à suivre son évolution à chaque
|
||||
# version amont, et ferait perdre les commentaires qui expliquent chaque réglage.
|
||||
#
|
||||
# AUCUNE VÉRIFICATION DE VERSION (mesure du 2026-10-10) : par défaut coolwsd interroge
|
||||
# collabora.co.uk toutes les 10 heures, et le dit aux utilisateurs par une fenêtre. Les
|
||||
# mises à jour arrivent par apt ; `fetch_update_check=0` coupe l'appel, la fenêtre suit.
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/bin/coolwsd --version --o:sys_template_path=/opt/cool/systemplate \
|
||||
|
|
@ -12,6 +16,8 @@ ExecStart=/usr/bin/coolwsd --version --o:sys_template_path=/opt/cool/systemplate
|
|||
--o:ssl.termination={{ serveur_collabora_ssl_termination | bool | lower }} \
|
||||
--o:server_name={{ serveur_collabora_server_name }} \
|
||||
--o:storage.wopi.alias_group[0].host=https://{{ serveur_collabora_domaine_nextcloud }}:443 \
|
||||
--o:fetch_update_check=0 \
|
||||
--o:allow_update_popup=false \
|
||||
--o:admin_console.enable={{ serveur_collabora_admin_actif | bool | lower }}{% if serveur_collabora_admin_actif | bool %} \
|
||||
--o:admin_console.username={{ serveur_collabora_admin_utilisateur }} \
|
||||
--o:admin_console.password={{ serveur_collabora_admin_mot_de_passe }}{% endif %}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,39 @@ flux:
|
|||
chiffrement: ssh
|
||||
raison: "Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense)."
|
||||
|
||||
# --- Sortie générale de la flotte ---
|
||||
# Portée par le socle parce qu'elle vaut pour TOUS les hôtes, sans exception.
|
||||
# Non déclarée, la frontière la bloquerait : son `block out` final est un vrai
|
||||
# default-deny, et la flotte perdrait ses mises à jour et son horloge en silence.
|
||||
# --- Sortie apt directe : SEULEMENT SANS AMONT D'ARTEFACTS (2026-10-10) ---
|
||||
#
|
||||
# C'ÉTAIT UNE SORTIE 80/443 VERS TOUT INTERNET, POUR TOUTE MACHINE. Sa raison, « dépôts
|
||||
# apt », n'était plus vraie chez les locataires : leurs 26 machines prennent TOUTES leurs
|
||||
# sources en `http://` par le cache du site (`artefacts_amorcage`), aucune n'a de dépôt
|
||||
# HTTPS. Mesuré à la frontière, elle ne servait plus qu'à ce qu'aucun rôle ne déclarait :
|
||||
# télémétrie de Grafana, d'Alloy et de Loki, vérification de version de Collabora,
|
||||
# cartes de rspamd, repère du génome. Les trois premiers sont coupés à la source ; les
|
||||
# deux derniers ont désormais leur propre flux, dans leur rôle.
|
||||
#
|
||||
# ELLE NE RESTE QUE LÀ OÙ IL N'Y A AUCUN AMONT. Le site non plus ne la reçoit pas : ses
|
||||
# machines visent son propre cache depuis le 2026-09-14 (`site_inventaire.py`,
|
||||
# `artefacts_amorcage`), et le cache, lui, porte ses propres sorties 80/443
|
||||
# (`serveur_cache_site`, `serveur_artefacts`). Seul un écosystème sans aucune source
|
||||
# d'artefacts — un laboratoire isolé — prend encore ses paquets en direct.
|
||||
- sens: egress
|
||||
port: 443
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: tls-requis
|
||||
raison: "Dépôts apt en HTTPS (Debian, Smallstep, Grafana, Icinga, Forgejo, Nextcloud) — sans quoi aucun correctif de sécurité n'entre."
|
||||
seulement_si: {variable: artefacts_amorcage, non_vide: false}
|
||||
raison: >-
|
||||
Dépôts apt en direct, pour un écosystème SANS aucune source d'artefacts. Le site
|
||||
et ses locataires passent par le cache du site et ne la reçoivent pas.
|
||||
- sens: egress
|
||||
port: 80
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: clair
|
||||
raison: "Dépôts apt en clair et redirections HTTP des miroirs (l'intégrité vient de la signature des paquets, pas du transport)."
|
||||
seulement_si: {variable: artefacts_amorcage, non_vide: false}
|
||||
raison: >-
|
||||
Dépôts apt en clair et redirections des miroirs, pour un écosystème SANS amont
|
||||
d'artefacts. L'intégrité vient de la signature des paquets, pas du transport.
|
||||
# --- Decouverte de MTU de chemin (PMTUD) ---
|
||||
# L'overlay EVPN plafonne a 1450 : tout ce qui traverse la frontiere depend de ces
|
||||
# deux messages. Bloques, la connexion s'etablit, les petites requetes passent et les
|
||||
|
|
|
|||
|
|
@ -11,6 +11,17 @@ Environment=GF_SERVER_DOMAIN={{ serveur_grafana_hostname }}
|
|||
Environment=GF_SERVER_ROOT_URL={{ serveur_grafana_root_url }}
|
||||
Environment=GF_SECURITY_ADMIN_PASSWORD={{ serveur_grafana_admin_password }}
|
||||
Environment=GF_USERS_ALLOW_SIGN_UP=false
|
||||
{# GRAFANA N'APPELLE PERSONNE (mesure du 2026-10-10). Par defaut il interroge grafana.com
|
||||
(versions, greffons) et envoie ses compteurs d'usage a stats.grafana.org : ~124 et ~70
|
||||
connexions par jour, depuis le site et depuis chaque locataire. Les avatars viennent de
|
||||
gravatar.com, les nouvelles de grafana.com. Rien de tout cela ne sert une console
|
||||
interne ; les mises a jour arrivent par apt, comme le reste. #}
|
||||
Environment=GF_ANALYTICS_REPORTING_ENABLED=false
|
||||
Environment=GF_ANALYTICS_CHECK_FOR_UPDATES=false
|
||||
Environment=GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES=false
|
||||
Environment=GF_ANALYTICS_FEEDBACK_LINKS_ENABLED=false
|
||||
Environment=GF_SECURITY_DISABLE_GRAVATAR=true
|
||||
Environment=GF_NEWS_NEWS_FEED_ENABLED=false
|
||||
{# LA SYNTHESE COMME PAGE D'ACCUEIL (2026-10-04) : a la connexion, la question « faut-il
|
||||
agir ? » plutot que la liste des tableaux. Le fichier est pose par ce role (tableau de
|
||||
synthese) ; sans lui, Grafana revient a son accueil par defaut, sans erreur. #}
|
||||
|
|
|
|||
|
|
@ -39,3 +39,7 @@ compactor:
|
|||
|
||||
limits_config:
|
||||
retention_period: {{ serveur_loki_retention }}
|
||||
|
||||
{# Pas de rapport d'usage a stats.grafana.org (actif par defaut, mesure du 2026-10-10). #}
|
||||
analytics:
|
||||
reporting_enabled: false
|
||||
|
|
|
|||
|
|
@ -70,3 +70,16 @@ flux:
|
|||
Insemination : amorcer le runner d'un tenant — socle, moteur, plan, plancher de
|
||||
resolution — pour qu'il prenne ensuite le relais sur ses propres machines. Ne
|
||||
transporte aucun secret : la voute est remise par un humain.
|
||||
|
||||
# LE REPERE DU GENOME (2026-10-10). La sonde `sonde-genome-a-jour` compare la forge du
|
||||
# site a eregion par un `git ls-remote` anonyme, toutes les 15 minutes. Elle sortait par
|
||||
# la sortie 443 generale du socle, qui n'existe plus : la declarer ici la rend lisible.
|
||||
# Sans Internet, la sonde passe en INCONNU (code 3) — rien ne casse.
|
||||
- sens: egress
|
||||
port: 443
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: tls-requis
|
||||
raison: >-
|
||||
Repere du genome : `git ls-remote` anonyme sur eregion (forge.alliance-boreale.ca)
|
||||
pour dire si la forge du site a pris du retard. Lecture seule, sans identifiant.
|
||||
|
|
|
|||
|
|
@ -13,3 +13,32 @@ flux:
|
|||
pair: localhost
|
||||
chiffrement: clair
|
||||
raison: "Interface de contrôle rspamd (statistiques, apprentissage) en local."
|
||||
|
||||
# LES CARTES DE RSPAMD VIENNENT D'INTERNET, ET C'EST UN CHOIX (2026-10-10).
|
||||
#
|
||||
# Jusqu'ici rien ne le disait : rspamd sortait par la sortie 80/443 que le socle donnait
|
||||
# a TOUTE machine. Mesure a la frontiere : ~2 700 connexions par jour et par locataire
|
||||
# vers maps.rspamd.com, ~245 vers sa-update.surbl.org, depuis `edge-mta-01` seulement.
|
||||
#
|
||||
# Sans elles, rspamd ne tombe pas : chaque carte distante a son repli local
|
||||
# (`file:///etc/rspamd/maps.d/…`), fige a la version du paquet. Les listes de messageries
|
||||
# gratuites, de redirecteurs, d'hameconnage et de listes blanches DMARC vieilliraient en
|
||||
# silence. On garde donc la sortie — mais declaree, et pour ce seul role, comme le 25
|
||||
# sortant de Postfix.
|
||||
- sens: egress
|
||||
port: 443
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: tls-requis
|
||||
raison: >-
|
||||
Cartes de rspamd (maps.rspamd.com) : messageries gratuites et jetables, redirecteurs,
|
||||
listes blanches DMARC/SPF/DKIM, hameconnage. Sans elles l'antispam garde les copies
|
||||
du paquet, qui vieillissent.
|
||||
- sens: egress
|
||||
port: 80
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: clair
|
||||
raison: >-
|
||||
Carte de hachages SURBL (sa-update.surbl.org), publiee en clair par l'amont. Une
|
||||
carte alteree ne peut que mal classer un courriel ; elle n'execute rien.
|
||||
|
|
|
|||
|
|
@ -435,6 +435,35 @@ def _services_du_plan_site() -> dict[str, list[str]]:
|
|||
return {h: sorted(set(g)) for h, g in out.items()}
|
||||
|
||||
|
||||
_HOSTVARS_SITE: dict | None = None
|
||||
|
||||
|
||||
def applicable_au_site(fl: dict, role: str, machines: list[str]) -> bool:
|
||||
"""Le flux conditionnel s'applique-t-il a AU MOINS UNE machine du site qui porte le role ?
|
||||
|
||||
LE SITE N'EVALUAIT PAS `seulement_si` (2026-10-10). La boucle des locataires le faisait
|
||||
(`applicable_chez`), celle du site emettait tout flux, condition ou non. Invisible tant
|
||||
qu'aucune condition ne changeait de verdict au site ; vu le jour ou la sortie apt du
|
||||
socle est devenue conditionnelle : `make flux` la retirait des machines du site, et le
|
||||
devis de la frontiere la gardait. Deux couches, deux verdicts.
|
||||
|
||||
Meme evaluation que `resoudre_flux` pour le site : variables d'hote de l'inventaire du
|
||||
site, puis defauts du role. Inventaire illisible -> la regle est emise (statu quo).
|
||||
"""
|
||||
global _HOSTVARS_SITE
|
||||
if not fl.get("seulement_si"):
|
||||
return True
|
||||
if _HOSTVARS_SITE is None:
|
||||
try:
|
||||
import site_inventaire # noqa: PLC0415 — le site peut ne pas etre monte
|
||||
_HOSTVARS_SITE = (site_inventaire.inventaire().get("_meta") or {}).get("hostvars") or {}
|
||||
except Exception:
|
||||
_HOSTVARS_SITE = {}
|
||||
if not _HOSTVARS_SITE:
|
||||
return True
|
||||
return any(flux_applicable(fl, role, None, _HOSTVARS_SITE.get(m) or {}) for m in machines)
|
||||
|
||||
|
||||
def _ports_du_plan_site() -> dict[str, str]:
|
||||
"""Port declare au plan pour chaque role du site — ce que `port: derive` vaut ici.
|
||||
|
||||
|
|
@ -989,11 +1018,17 @@ def construire(tenants: list[tuple[str, str, dict]]) -> dict:
|
|||
"contenu": _ips,
|
||||
"description": f"Machines du site portant {_role}",
|
||||
}
|
||||
_machines_role = [str(m["nom"]) for m in _machines_site
|
||||
if _role == "serveur_debian"
|
||||
or _role in _services_site.get(m["nom"], [])]
|
||||
for _fl in flux_par_role.get(_role, []):
|
||||
_entrant = _fl.get("sens") == "ingress"
|
||||
_pairs_fl = _pairs(_fl)
|
||||
if "frontiere" in _pairs_fl:
|
||||
continue # bloc dedie, plus bas (voir la boucle des tenants)
|
||||
# Un flux conditionnel ne vaut que la ou son reglage le dit — au site aussi.
|
||||
if not applicable_au_site(_fl, _role, _machines_role):
|
||||
continue
|
||||
if _entrant and "voisins_site" in _pairs_fl:
|
||||
# Un tenant qui s'adresse au site : le paquet penetre le pare-feu par
|
||||
# le lien de TRANSIT, pas par la patte du site. Une regle par voisin,
|
||||
|
|
|
|||
81
scripts/tests/test_conditions_site.py
Normal file
81
scripts/tests/test_conditions_site.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Aucune sortie vers Internet n'est donnee a TOUTE machine, et le site lit les conditions.
|
||||
|
||||
POURQUOI (2026-10-10). Le socle donnait a chaque machine une sortie 80/443 vers tout
|
||||
Internet, « pour apt ». Apt passait deja par le cache du site : la sortie ne servait plus
|
||||
qu'a ce qu'aucun role ne declarait — telemetrie de Grafana, d'Alloy et de Loki, verification
|
||||
de version de Collabora, cartes de rspamd. Elle est devenue conditionnelle
|
||||
(`seulement_si: artefacts_amorcage` vide), et deux defauts sont apparus :
|
||||
|
||||
1. un role porte par toute machine (le socle, les `client_*`) peut rouvrir Internet a
|
||||
toute la flotte sans que rien ne le signale ;
|
||||
2. le devis de la frontiere n'evaluait `seulement_si` que chez les locataires : au site,
|
||||
tout flux conditionnel etait emis. `make flux` retirait la sortie des machines du site,
|
||||
la frontiere la gardait.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
import devis_opnsense as dv # noqa: E402
|
||||
from resoudre_flux import charger_flux # noqa: E402
|
||||
|
||||
UNIVERSELS = {"serveur_debian", "serveur_durci"}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
echecs: list[str] = []
|
||||
flux = charger_flux()
|
||||
|
||||
# 1. Les roles universels n'ouvrent pas Internet sans condition (hors ICMP du PMTUD).
|
||||
for role, liste in flux.items():
|
||||
if role not in UNIVERSELS and not role.startswith("client_"):
|
||||
continue
|
||||
for fl in liste:
|
||||
pairs = fl.get("pair") if isinstance(fl.get("pair"), list) else [fl.get("pair")]
|
||||
if (fl.get("sens") == "egress" and "externe" in pairs
|
||||
and str(fl.get("protocole")).lower() in ("tcp", "udp")
|
||||
and not fl.get("seulement_si")):
|
||||
echecs.append(f"{role} : sortie {fl.get('protocole')}/{fl.get('port')} vers "
|
||||
f"`externe` sans condition — donnee a TOUTE machine")
|
||||
|
||||
# 2. L'evaluation au site, sur des variables d'hote connues.
|
||||
socle = [fl for fl in flux.get("serveur_debian", [])
|
||||
if fl.get("sens") == "egress" and fl.get("seulement_si")
|
||||
and (fl.get("seulement_si") or {}).get("variable") == "artefacts_amorcage"]
|
||||
if not socle:
|
||||
echecs.append("serveur_debian : la sortie apt n'est plus conditionnee a `artefacts_amorcage`")
|
||||
else:
|
||||
fl = socle[0]
|
||||
dv._HOSTVARS_SITE = {"avec": {"artefacts_amorcage": "10.0.0.1:3142"}, "sans": {}}
|
||||
attendus = [(["avec"], False), (["sans"], True), (["avec", "sans"], True)]
|
||||
for machines, attendu in attendus:
|
||||
obtenu = dv.applicable_au_site(fl, "serveur_debian", machines)
|
||||
if obtenu is not attendu:
|
||||
echecs.append(f"applicable_au_site({machines}) = {obtenu}, attendu {attendu}")
|
||||
dv._HOSTVARS_SITE = {}
|
||||
if dv.applicable_au_site(fl, "serveur_debian", ["avec"]) is not True:
|
||||
echecs.append("inventaire du site illisible : la regle doit rester emise (statu quo)")
|
||||
dv._HOSTVARS_SITE = None
|
||||
|
||||
# 3. La boucle du site appelle bien l'evaluation, avant tout rendu de regle.
|
||||
source = (RACINE / "scripts" / "devis_opnsense.py").read_text(encoding="utf-8")
|
||||
boucle = re.search(r"for _fl in flux_par_role\.get\(_role, \[\]\):(.{0,600})", source, re.S)
|
||||
if not boucle or "applicable_au_site(" not in boucle.group(1):
|
||||
echecs.append("devis_opnsense : la boucle des flux du site n'evalue plus `seulement_si`")
|
||||
|
||||
for e in echecs:
|
||||
print(f"ECHEC {e}")
|
||||
if echecs:
|
||||
return 1
|
||||
print("OK — aucun role universel n'ouvre Internet ; le site lit les conditions.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Reference in a new issue