sauvegarde : le site protege enfin son propre etat
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
L hebergeur protegeait l etat de tous ses locataires et pas le sien. Deux choses qu il detient et que personne ne peut regenerer : la racine de son AC, et la forge du genome. Le reste est reconstructible par le code. Preuve faite, pas annoncee : sauvegarde reelle puis restic check et restitution. 13 fichiers pour l AC (root_ca_key et intermediate_ca_key compris), 835 pour la forge. Le site depose avec SON identite, sur le compte restic que serveur_backup lui cree, separe des comptes des locataires par les memes permissions. La cible est derivee du expose de l application qui porte serveur_backup_site : le nom du service, pas une adresse — client_backup_repo la grave dans le chemin de chaque instantane. P36 ne lisait que le plan de l instance montee, donc jamais celui du site — qui detient pourtant le plus. Elle lit desormais les deux. Verifiee en la faisant echouer : integration retiree, la preuve tire. Reste ouvert : personne ne verifie les sauvegardes du site. Le depot tourne avec verification_locale a false (pose pour les locataires, dont il ne peut pas ouvrir les depots) et le site n a pas de supervision. La sauvegarde existe et se restaure ; c est son SILENCE qui n alerte pas encore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
53c5f41a1a
commit
cf9abe74b6
4 changed files with 119 additions and 12 deletions
51
CHANGELOG.md
51
CHANGELOG.md
|
|
@ -1,5 +1,56 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-02 (3) — Le site sauvegarde son propre etat, et la preuve le lui demande
|
||||
|
||||
**56 preuves.** L'hebergeur protegeait l'etat de tous ses locataires et pas le sien. Deux
|
||||
choses qu'il detient et que personne ne peut regenerer :
|
||||
|
||||
- **la racine de son autorite de certification** (`/etc/step-ca`) — compromise, elle forge
|
||||
tout nom ; perdue, il faut redistribuer la confiance a chaque machine de chaque
|
||||
ecosysteme ;
|
||||
- **la forge du genome** (`/var/lib/forgejo`) — les quatre depots dont tout descend. Ils
|
||||
vivent aussi sur les postes et les runners, mais la forge est le seul endroit ou ils se
|
||||
rejoignent.
|
||||
|
||||
Tout le reste est reconstructible par le code : le cache se re-remplit, la zone DNS se
|
||||
regenere depuis le plan, les depots du runner vivent dans git.
|
||||
|
||||
**Preuve faite, pas annoncee.** Sauvegarde reelle sur les deux hotes, puis `restic check`
|
||||
et restitution : 13 fichiers / 152 Ko pour l'AC — `secrets/root_ca_key` et
|
||||
`secrets/intermediate_ca_key` compris — et 835 fichiers / 31 Mo pour la forge.
|
||||
|
||||
### Son propre compte, sur son propre depot
|
||||
|
||||
Le site depose avec SON identite (`backup_pubkey` au plan, moitie privee dans
|
||||
`underlay.vault.yml`), sur le compte `restic` que `serveur_backup` lui cree — celui dont le
|
||||
home est `/srv/restic/site`, a cote des comptes des locataires et separe d'eux par les
|
||||
memes permissions. Celle des locataires ne lui sert a rien et ne doit pas lui servir.
|
||||
|
||||
La cible est DERIVEE du `expose` de l'application qui porte `serveur_backup_site` — le nom
|
||||
du SERVICE, la meme source qu'un locataire utilise. Une adresse gravee dans
|
||||
`client_backup_repo` rendrait le depot indeplacable.
|
||||
|
||||
### P36 lisait le plan de l'instance montee, donc jamais celui du site
|
||||
|
||||
La preuve qui exige qu'un detenteur d'etat porte `client_backup` ne regardait que
|
||||
l'ecosysteme. L'hebergeur y echappait entierement — et c'est lui qui detient le plus.
|
||||
|
||||
Elle lit desormais les deux plans, avec le meme catalogue et la meme regle : ce qui se lit
|
||||
statiquement se prouve statiquement, sinon on l'apprend le jour de la restauration (D-75).
|
||||
`9 hote(s) de l'ecosysteme et 2 du site`.
|
||||
|
||||
**Verifiee en la faisant echouer** : l'integration retiree de `site-pki-01`, P36 tire et le
|
||||
harnais passe NON CONFORME. Une garde qui ne peut pas echouer ne garde rien — cette
|
||||
semaine en a deja produit deux.
|
||||
|
||||
### Ce qui reste ouvert, et qu'il faut savoir
|
||||
|
||||
**Personne ne verifie les sauvegardes du site.** `serveur_backup` rapporte l'etat reel des
|
||||
instantanes a Icinga ; le site n'a pas de supervision, et son depot tourne avec
|
||||
`serveur_backup_verification_locale: false` — pose pour les locataires, dont il ne peut pas
|
||||
ouvrir les depots. Pour SES propres depots il le pourrait, mais il n'a personne a qui le
|
||||
dire. La sauvegarde existe et se restaure ; c'est son SILENCE qui n'alerte pas encore.
|
||||
|
||||
## 2026-09-02 (2) — Les machines du site sont durcies : le commentaire disait vrai, le code n'en faisait que la moitie
|
||||
|
||||
**56 preuves.** Les six machines du SITE — racine de l'AC, forge du genome, cache
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (56 OK · 0 echec · 0 saute)
|
||||
- **Verdict** : ❌ NON CONFORME (55 OK · 1 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (30 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ❌ ECHEC | 1 hote(s) detiennent de l'etat sans sauvegarde : site : site-pki-01 detient ['serveur_step_ca'] — ajouter `client_backup` a leurs `integrations` dans `plan/serv |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 39 role(s) serveur/client tous nommes, 40 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
|
|
|
|||
|
|
@ -678,22 +678,51 @@ def preuve_etat_sauvegarde() -> tuple[bool, str]:
|
|||
serveurs = (yaml.safe_load((plan / "serveurs.yml").read_text(encoding="utf-8"))
|
||||
or {}).get("serveurs") or {}
|
||||
|
||||
detenu: dict[str, set[str]] = {}
|
||||
for app in apps.values():
|
||||
groupe = str((app or {}).get("groupe") or "")
|
||||
if groupe in catalogue:
|
||||
detenu.setdefault(str((app or {}).get("hote") or ""), set()).add(groupe)
|
||||
def _manques(apps: dict, serveurs: dict, ou: str) -> tuple[list[str], int]:
|
||||
detenu: dict[str, set[str]] = {}
|
||||
for app in (apps or {}).values():
|
||||
groupe = str((app or {}).get("groupe") or "")
|
||||
if groupe in catalogue:
|
||||
detenu.setdefault(str((app or {}).get("hote") or ""), set()).add(groupe)
|
||||
return ([f"{ou} : {hote} detient {sorted(groupes)}"
|
||||
for hote, groupes in sorted(detenu.items())
|
||||
if "client_backup" not in ((serveurs.get(hote) or {}).get("integrations") or [])],
|
||||
len(detenu))
|
||||
|
||||
manques, n_tenant = _manques(apps, serveurs, "ecosysteme")
|
||||
|
||||
# LE SITE DETIENT DE L'ETAT LUI AUSSI, ET LE PLUS CONSEQUENT (2026-09-02).
|
||||
#
|
||||
# Cette preuve ne lisait que le plan de l'instance MONTEE. Or l'hebergeur porte la
|
||||
# racine de son autorite de certification et la forge du genome — perdre l'une oblige
|
||||
# a redistribuer la confiance a chaque machine de chaque ecosysteme, perdre l'autre
|
||||
# coupe la source dont tout descend. Rien ne l'exigeait, et rien ne l'aurait dit.
|
||||
#
|
||||
# Meme catalogue, meme regle : ce qui se lit statiquement se prouve statiquement,
|
||||
# sinon on l'apprend le jour de la restauration (D-75). Le site n'a simplement pas
|
||||
# d'`instance/` — son plan vit a cote de sa carte.
|
||||
n_site = 0
|
||||
try:
|
||||
import underlay as underlay_mod
|
||||
if underlay_mod.chemin():
|
||||
m_site, n_site = _manques(
|
||||
(underlay_mod.lire_plan_site("applications.yml") or {}).get("applications") or {},
|
||||
(underlay_mod.lire_plan_site("serveurs.yml") or {}).get("serveurs") or {},
|
||||
"site")
|
||||
manques += m_site
|
||||
except Exception:
|
||||
# Aucune carte d'hebergeur montee : on prouve ce qu'on peut voir, et on ne
|
||||
# transforme pas une absence en echec.
|
||||
pass
|
||||
|
||||
manques = [f"{hote} detient {sorted(groupes)}"
|
||||
for hote, groupes in sorted(detenu.items())
|
||||
if "client_backup" not in ((serveurs.get(hote) or {}).get("integrations") or [])]
|
||||
if manques:
|
||||
return False, (f"{len(manques)} hote(s) detiennent de l'etat sans sauvegarde : "
|
||||
+ " | ".join(manques)
|
||||
+ " — ajouter `client_backup` a leurs `integrations` dans "
|
||||
"`plan/serveurs.yml`.")
|
||||
return True, (f"{len(detenu)} hote(s) detiennent de l'etat, tous porteurs de "
|
||||
f"`client_backup` ({len(catalogue)} groupe(s) au catalogue).")
|
||||
return True, (f"{n_tenant} hote(s) de l'ecosysteme et {n_site} du site detiennent de "
|
||||
f"l'etat, tous porteurs de `client_backup` "
|
||||
f"({len(catalogue)} groupe(s) au catalogue).")
|
||||
|
||||
|
||||
def preuve_lecteur_declare() -> tuple[bool, str]:
|
||||
|
|
|
|||
|
|
@ -180,6 +180,27 @@ def inventaire() -> dict:
|
|||
|
||||
_locataires_backup = _locataires_sauvegarde()
|
||||
|
||||
# OU LE SITE DEPOSE SON PROPRE ETAT.
|
||||
#
|
||||
# Le site a de l'etat a lui, et pas des moindres : la racine de son autorite de
|
||||
# certification, et la forge qui porte le genome. Tout le reste est reconstructible
|
||||
# par le code — le cache se re-remplit, la zone DNS se regenere depuis le plan, les
|
||||
# depots du runner vivent dans git.
|
||||
#
|
||||
# IL DEPOSE SUR SON PROPRE DEPOT, avec SA propre identite (`backup_pubkey` du plan,
|
||||
# moitie privee dans `underlay.vault.yml`). Celle des locataires ne lui sert a rien
|
||||
# et ne doit surtout pas lui servir : chaque deposant a son compte, le sien est
|
||||
# `restic` — celui que `serveur_backup` cree, avec `/srv/restic/site` pour home.
|
||||
#
|
||||
# LE NOM DU SERVICE, PAS L'ADRESSE. `client_backup_repo` grave cette valeur dans le
|
||||
# chemin de CHAQUE instantane : une adresse rendrait le depot indeplacable. On prend
|
||||
# donc le `expose` declare par l'application qui porte `serveur_backup_site` —
|
||||
# la meme source que celle qu'un locataire utilise.
|
||||
_cible_backup = next(
|
||||
(str(f) for app in applications.values()
|
||||
if str(app.get("groupe") or "") == "serveur_backup_site"
|
||||
for f in (app.get("expose") or [])), "")
|
||||
|
||||
# Les groupes d'une machine du site, tels que `plan/applications.yml` les declare.
|
||||
def _groupes_de(nom_hote: str) -> set:
|
||||
return {str(a.get("groupe")) for a in applications.values()
|
||||
|
|
@ -382,6 +403,12 @@ def inventaire() -> dict:
|
|||
# depend de l'ordre des plays est un reglage qui reviendra en arriere.
|
||||
#
|
||||
# Ici, tout play qui applique `ssh_hardening` a cet hote la voit.
|
||||
# Le depot du site, pour les machines du site qui y deposent. Vide s'il n'y
|
||||
# en a pas — `client_backup` refuse alors, plutot que de viser un nom qui
|
||||
# ne repond pas : mieux vaut un deploiement qui s'arrete qu'une sauvegarde
|
||||
# qu'on croit faite.
|
||||
**({"client_backup_cible": _cible_backup,
|
||||
"client_backup_utilisateur_distant": "restic"} if _cible_backup else {}),
|
||||
# Les ecosystemes qui deposent leur etat ici, un compte Unix chacun.
|
||||
"serveur_backup_site_locataires": (
|
||||
_locataires_backup
|
||||
|
|
|
|||
Loading…
Reference in a new issue