cles : la cle USB se suffit a elle-meme, et la restauration est prouvee
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
Le jour ou l on s en sert, le poste est mort — et cloner le depot demande la cle SSH qui est dans l archive qu on essaie d ouvrir. Une procedure rangee dans le depot serait inaccessible exactement quand elle sert. L export depose donc restaurer_cles.py et un LISEZ-MOI a cote de l archive. La cle ne demande plus que gpg, python3 et la phrase de passe. Le script remet chaque fichier a sa place selon son NOM (machine neuve, parfois autre compte), repose les droits a 0600 — ssh refuse une cle privee lisible par d autres, et son message ne dit pas qu il s agit d un droit — et refuse d ecraser une cle presente, en regardant AVANT d ecrire. Le LISEZ-MOI porte les trois commandes manuelles. Un outil peut avoir un defaut ; gpg et tar seront la. EPROUVE : export vers une cle, poste neuf vide, restauration depuis la cle SEULE, empreintes comparees — identiques 4 sur 4, droits 700/600, et le refus d ecraser tire. Le filet manuel passe au meme test separement, identiques 4 sur 4. make cles-restaurer refusera puisque les cles sont en place — et ce refus est la preuve que l archive s ouvre et que la phrase de passe est la bonne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
3eff217bd5
commit
dcbb57db69
5 changed files with 319 additions and 0 deletions
50
CHANGELOG.md
50
CHANGELOG.md
|
|
@ -1,5 +1,55 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-05 (2) — Rouvrir les cles : la cle USB doit se suffire a elle-meme
|
||||||
|
|
||||||
|
**56 preuves.** Les cles sont sorties du poste. Restait la moitie qui compte : savoir les
|
||||||
|
REMETTRE. Une sauvegarde qu'on ne sait pas rouvrir n'est pas une sauvegarde.
|
||||||
|
|
||||||
|
### Le piege, et pourquoi la procedure ne peut pas vivre dans le depot
|
||||||
|
|
||||||
|
Le jour ou l'on s'en sert, **le poste est mort**. Le depot Set-OPS est replique trois fois
|
||||||
|
— eregion, la forge du site, patient 0 — mais le CLONER demande la cle SSH, qui est
|
||||||
|
justement dans l'archive qu'on essaie d'ouvrir. Une procedure de restauration rangee dans
|
||||||
|
le depot serait donc inaccessible exactement quand elle sert.
|
||||||
|
|
||||||
|
`exporter_cles.py` depose donc, A COTE DE L'ARCHIVE : `restaurer_cles.py` et un
|
||||||
|
`LISEZ-MOI-RESTAURATION.txt`. La cle ne demande plus que `gpg`, `python3` et la phrase de
|
||||||
|
passe.
|
||||||
|
|
||||||
|
### Ce que le script fait de plus qu'un `tar -x`
|
||||||
|
|
||||||
|
- il remet chaque fichier a sa place selon son NOM, pas selon un chemin enregistre — on
|
||||||
|
restaure souvent sur une machine neuve, parfois sous un autre compte ;
|
||||||
|
- il repose les droits a **0600**. `ssh` REFUSE une cle privee que d'autres peuvent lire,
|
||||||
|
et son message ne dit pas qu'il s'agit d'un droit — une archive extraite depuis un
|
||||||
|
support FAT arrive toujours dans cet etat ;
|
||||||
|
- il **refuse d'ecraser** une cle deja presente, et regarde AVANT d'ecrire : refuser a
|
||||||
|
mi-parcours laisserait la moitie des cles en place et l'autre non, un etat que personne
|
||||||
|
ne sait diagnostiquer.
|
||||||
|
|
||||||
|
### Et si meme ce script ne tourne pas
|
||||||
|
|
||||||
|
Le LISEZ-MOI porte les trois commandes manuelles — `gpg`, `tar`, `chmod`. C'est le vrai
|
||||||
|
filet : un outil peut avoir un defaut, `gpg` et `tar` seront la.
|
||||||
|
|
||||||
|
### EPROUVE, PAS SUPPOSE
|
||||||
|
|
||||||
|
Cycle complet sur des fichiers factices : export vers une cle, poste neuf entierement
|
||||||
|
vide, restauration **depuis la cle seule**, puis comparaison.
|
||||||
|
|
||||||
|
IDENTIQUES — 4 empreintes sur 4
|
||||||
|
700 .ssh 600 .ssh/id_git_ed25519 600 .config/setops-vault-…
|
||||||
|
REFUS : ces cles existent deja ici — Rien n'a ete ecrit.
|
||||||
|
|
||||||
|
Le filet manuel a ete passe au meme test, separement : identiques, 4 sur 4.
|
||||||
|
|
||||||
|
### Le test le moins cher, a refaire souvent
|
||||||
|
|
||||||
|
make cles-restaurer ARCHIVE=/media/…/setops-cles-*.tar.gpg
|
||||||
|
|
||||||
|
Il refusera, puisque les cles sont en place — et **ce refus est la preuve** que l'archive
|
||||||
|
s'ouvre et que la phrase de passe est la bonne. A refaire apres chaque changement de cle.
|
||||||
|
|
||||||
## 2026-09-05 — 1 644 octets valent l'installation, et ils n'existaient qu'a un endroit
|
## 2026-09-05 — 1 644 octets valent l'installation, et ils n'existaient qu'a un endroit
|
||||||
|
|
||||||
**56 preuves.** En cherchant par quoi reprendre, une mesure a change l'ordre des
|
**56 preuves.** En cherchant par quoi reprendre, une mesure a change l'ordre des
|
||||||
|
|
|
||||||
17
Makefile
17
Makefile
|
|
@ -575,6 +575,23 @@ cles-exporter: ## Sort les cles du poste, chiffrees et relues — VERS=<repertoi
|
||||||
python3 scripts/exporter_cles.py --vers "$(VERS)"
|
python3 scripts/exporter_cles.py --vers "$(VERS)"
|
||||||
|
|
||||||
.PHONY: cles-recenser cles-exporter
|
.PHONY: cles-recenser cles-exporter
|
||||||
|
cles-compagnons: ## Depose le script de restauration sur une cle DEJA faite — VERS=<repertoire>
|
||||||
|
@# POUR UNE CLE EXISTANTE. L'archive n'est pas refaite : ni phrase de passe redemandee,
|
||||||
|
@# ni risque d'ecraser ce qui est deja verifie.
|
||||||
|
@if [ -z "$(VERS)" ]; then \
|
||||||
|
printf 'Refus: relancer avec VERS=<repertoire de la cle>.\n'; exit 2; fi
|
||||||
|
python3 scripts/exporter_cles.py --vers "$(VERS)" --compagnons
|
||||||
|
|
||||||
|
cles-restaurer: ## Remet les cles en place depuis une archive — ARCHIVE=<fichier>
|
||||||
|
@# EN VRAI, ON N'UTILISE PAS CETTE CIBLE. Le jour ou l'on restaure, le depot n'est pas
|
||||||
|
@# la — le cloner demande la cle SSH qu'on essaie justement de recuperer. On lance
|
||||||
|
@# alors `restaurer_cles.py` DEPUIS LA CLE USB, ou les trois commandes du LISEZ-MOI.
|
||||||
|
@# Cette cible existe pour eprouver la manoeuvre pendant qu'on a encore le choix.
|
||||||
|
@if [ -z "$(ARCHIVE)" ]; then \
|
||||||
|
printf 'Refus: relancer avec ARCHIVE=<chemin de l archive .tar.gpg>.\n'; exit 2; fi
|
||||||
|
python3 scripts/restaurer_cles.py "$(ARCHIVE)" $(if $(FORCER),--forcer)
|
||||||
|
|
||||||
|
.PHONY: cles-compagnons cles-restaurer
|
||||||
cacher-paquets: ## Tire les paquets NON DEBIAN dans le cache du controleur (a faire en ligne)
|
cacher-paquets: ## Tire les paquets NON DEBIAN dans le cache du controleur (a faire en ligne)
|
||||||
@# CE QUI REND LA RECONSTRUCTION HORS LIGNE POSSIBLE. Trois depots tiers sont en
|
@# CE QUI REND LA RECONSTRUCTION HORS LIGNE POSSIBLE. Trois depots tiers sont en
|
||||||
@# HTTPS et contournent le cache du site (`Acquire::https::Proxy "DIRECT"`). Sans
|
@# HTTPS et contournent le cache du site (`Acquire::https::Proxy "DIRECT"`). Sans
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,48 @@ gestionnaire de mots de passe qui n'est pas sur ce poste.
|
||||||
**2. Une seconde copie, dans un autre lieu physique.** Un support unique dans un tiroir
|
**2. Une seconde copie, dans un autre lieu physique.** Un support unique dans un tiroir
|
||||||
unique, c'est le problème qu'on vient de fermer, déplacé de quelques mètres.
|
unique, c'est le problème qu'on vient de fermer, déplacé de quelques mètres.
|
||||||
|
|
||||||
|
## Les rouvrir — la moitié qui compte
|
||||||
|
|
||||||
|
Le jour où l'on s'en sert, **le poste est mort**. Le dépôt Set-OPS est répliqué trois
|
||||||
|
fois, mais le *cloner* demande la clé SSH… qui est dans l'archive qu'on essaie d'ouvrir.
|
||||||
|
Une procédure rangée dans le dépôt serait donc inaccessible exactement quand elle sert.
|
||||||
|
|
||||||
|
La clé se suffit donc à elle-même. À côté de l'archive :
|
||||||
|
|
||||||
|
```
|
||||||
|
setops-cles-<poste>.tar.gpg les clés, chiffrées
|
||||||
|
restaurer_cles.py le script, autonome
|
||||||
|
LISEZ-MOI-RESTAURATION.txt le mode d'emploi, et les commandes manuelles
|
||||||
|
```
|
||||||
|
|
||||||
|
Sur la machine neuve, il ne faut que `gpg`, `python3` et la phrase de passe :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 restaurer_cles.py setops-cles-*.tar.gpg --lister # voir sans rien écrire
|
||||||
|
python3 restaurer_cles.py setops-cles-*.tar.gpg # remettre en place
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script remet chaque fichier à sa place selon son nom, et **repose les droits à 0600**.
|
||||||
|
Ce n'est pas cosmétique : `ssh` refuse une clé privée que d'autres peuvent lire, et son
|
||||||
|
message ne dit pas qu'il s'agit d'un droit — une archive extraite depuis un support FAT
|
||||||
|
arrive systématiquement dans cet état. Il **refuse** aussi d'écraser une clé déjà là :
|
||||||
|
lancé par erreur sur un poste qui fonctionne, il ne détruit rien.
|
||||||
|
|
||||||
|
Si même ce script refuse de tourner, le LISEZ-MOI porte les commandes manuelles — `gpg`,
|
||||||
|
`tar`, `chmod`. **Les deux chemins ont été éprouvés** le 2026-09-05 : export, poste vidé,
|
||||||
|
restauration depuis la clé seule, empreintes comparées — identiques, 4 sur 4, droits
|
||||||
|
700/600.
|
||||||
|
|
||||||
|
## L'éprouver pendant qu'on a encore le choix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make cles-restaurer ARCHIVE=/media/…/setops-cles-*.tar.gpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Il refusera, puisque les clés sont déjà en place — et ce refus est en soi la preuve que
|
||||||
|
l'archive s'ouvre et que la phrase de passe est la bonne. C'est le test le moins cher
|
||||||
|
qui existe, et il vaut d'être refait après chaque changement de clé.
|
||||||
|
|
||||||
## Quand recommencer
|
## Quand recommencer
|
||||||
|
|
||||||
Quand une voûte est créée ou sa clé changée (`voutes.py`), quand une paire SSH de runner
|
Quand une voûte est créée ou sa clé changée (`voutes.py`), quand une paire SSH de runner
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,75 @@ def _refuser_destination_interne(dest: Path) -> None:
|
||||||
" cle est a l'interieur. Viser un support amovible ou une machine tierce.")
|
" cle est a l'interieur. Viser un support amovible ou une machine tierce.")
|
||||||
|
|
||||||
|
|
||||||
|
LISEZ_MOI = """SI TU LIS CECI, LE POSTE EST PROBABLEMENT MORT.
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
Cette cle contient les SECRETS d'une installation Set-OPS : ce qui ouvre les voutes
|
||||||
|
chiffrees, et les cles SSH qui entrent sur les machines. Le code, lui, est ailleurs —
|
||||||
|
replique sur plusieurs forges. C'est ici qu'est la partie qu'on ne peut pas refabriquer.
|
||||||
|
|
||||||
|
CE QU'IL FAUT, ET RIEN D'AUTRE
|
||||||
|
------------------------------
|
||||||
|
- la phrase de passe (elle n'est PAS sur cette cle, c'est voulu) ;
|
||||||
|
- `gpg` et `python3`, presents sur tout Debian ou Ubuntu.
|
||||||
|
|
||||||
|
LA MANOEUVRE
|
||||||
|
------------
|
||||||
|
python3 restaurer_cles.py setops-cles-*.tar.gpg --lister # voir sans rien ecrire
|
||||||
|
python3 restaurer_cles.py setops-cles-*.tar.gpg # remettre en place
|
||||||
|
|
||||||
|
Le script remet chaque fichier a sa place (~/.config ou ~/.ssh) et repose les droits a
|
||||||
|
0600. Ce dernier point n'est pas cosmetique : `ssh` REFUSE une cle privee que d'autres
|
||||||
|
peuvent lire, et son message ne dit pas qu'il s'agit d'un droit. Il refuse aussi
|
||||||
|
d'ecraser une cle deja presente — lance par erreur sur un poste qui marche, il ne
|
||||||
|
detruit rien.
|
||||||
|
|
||||||
|
SI MEME CE SCRIPT NE TOURNE PAS
|
||||||
|
-------------------------------
|
||||||
|
Trois commandes suffisent. C'est le vrai filet :
|
||||||
|
|
||||||
|
gpg --output cles.tar --decrypt setops-cles-*.tar.gpg
|
||||||
|
mkdir -p ~/.config ~/.ssh && tar -xf cles.tar -C /tmp
|
||||||
|
cp /tmp/setops-vault-* ~/.config/ ; cp /tmp/id_* ~/.ssh/
|
||||||
|
chmod 700 ~/.ssh ~/.config ; chmod 600 ~/.ssh/id_* ~/.config/setops-vault-*
|
||||||
|
|
||||||
|
ENSUITE, ESSAYER — PAS SUPPOSER
|
||||||
|
-------------------------------
|
||||||
|
ssh -o BatchMode=yes ansible@<un hyperviseur> hostname
|
||||||
|
python3 scripts/voutes.py etat (depuis le depot, une fois clone)
|
||||||
|
|
||||||
|
Une cle remise en place qu'on n'a pas essayee n'est pas une cle restauree.
|
||||||
|
|
||||||
|
OU EST LE RESTE
|
||||||
|
---------------
|
||||||
|
Le depot Set-OPS vit sur plusieurs forges ; les adresses sont dans `.git/config` de
|
||||||
|
n'importe quelle copie. Les sauvegardes des machines vivent sur le depot du site, et
|
||||||
|
leur mot de passe restic est dans les voutes que cette cle ouvre.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _deposer_compagnons(dest: Path) -> list[str]:
|
||||||
|
"""Le script de restauration et son mode d'emploi, A COTE de l'archive.
|
||||||
|
|
||||||
|
POURQUOI CE N'EST PAS FACULTATIF (2026-09-05). Le jour ou l'on ouvre cette cle, le
|
||||||
|
poste est mort. Le depot Set-OPS est replique trois fois — mais le CLONER demande la
|
||||||
|
cle SSH, qui est dans l'archive qu'on essaie justement d'ouvrir. Une procedure de
|
||||||
|
restauration rangee dans le depot serait donc inaccessible exactement quand elle sert.
|
||||||
|
|
||||||
|
La cle doit se suffire a elle-meme. On y depose le script, et un LISEZ-MOI qui porte
|
||||||
|
en plus les commandes manuelles — pour le cas ou meme le script refuserait de tourner.
|
||||||
|
"""
|
||||||
|
poses = []
|
||||||
|
src = Path(__file__).parent / "restaurer_cles.py"
|
||||||
|
if src.is_file():
|
||||||
|
(dest / "restaurer_cles.py").write_bytes(src.read_bytes())
|
||||||
|
(dest / "restaurer_cles.py").chmod(0o755)
|
||||||
|
poses.append("restaurer_cles.py")
|
||||||
|
(dest / "LISEZ-MOI-RESTAURATION.txt").write_text(LISEZ_MOI, encoding="utf-8")
|
||||||
|
poses.append("LISEZ-MOI-RESTAURATION.txt")
|
||||||
|
return poses
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("--vers",
|
ap.add_argument("--vers",
|
||||||
|
|
@ -101,10 +170,27 @@ def main() -> int:
|
||||||
# verifier qu'on n'emporte ni trop ni trop peu.
|
# verifier qu'on n'emporte ni trop ni trop peu.
|
||||||
ap.add_argument("--recenser", action="store_true",
|
ap.add_argument("--recenser", action="store_true",
|
||||||
help="Montrer ce qui sortirait, sans rien ecrire")
|
help="Montrer ce qui sortirait, sans rien ecrire")
|
||||||
|
# POUR UNE CLE DEJA FAITE. L'archive n'est pas refaite — on ne redemande pas une
|
||||||
|
# phrase de passe et on ne risque pas d'ecraser ce qui est deja verifie.
|
||||||
|
ap.add_argument("--compagnons", action="store_true",
|
||||||
|
help="Deposer seulement le script de restauration et son mode d'emploi")
|
||||||
ap.add_argument("--nom", default="setops-cles",
|
ap.add_argument("--nom", default="setops-cles",
|
||||||
help="Nom de base de l'archive (defaut: setops-cles)")
|
help="Nom de base de l'archive (defaut: setops-cles)")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.compagnons:
|
||||||
|
if not args.vers:
|
||||||
|
raise SystemExit("REFUS : --vers requis.")
|
||||||
|
dest = Path(args.vers).expanduser()
|
||||||
|
_refuser_destination_interne(dest)
|
||||||
|
if not dest.is_dir():
|
||||||
|
raise SystemExit(f"REFUS : {dest} n'existe pas ou n'est pas un repertoire.")
|
||||||
|
for nom in _deposer_compagnons(dest):
|
||||||
|
print(f" + {dest / nom}")
|
||||||
|
print("\n La cle se suffit desormais a elle-meme : script, mode d'emploi, et")
|
||||||
|
print(" les commandes manuelles si meme le script refuse de tourner.")
|
||||||
|
return 0
|
||||||
|
|
||||||
if not args.recenser:
|
if not args.recenser:
|
||||||
if not args.vers:
|
if not args.vers:
|
||||||
raise SystemExit("REFUS : --vers requis (ou --recenser pour voir sans ecrire).")
|
raise SystemExit("REFUS : --vers requis (ou --recenser pour voir sans ecrire).")
|
||||||
|
|
@ -210,6 +296,8 @@ def main() -> int:
|
||||||
acces = ("permissions non applicables — support sans droits Unix (FAT/exFAT). "
|
acces = ("permissions non applicables — support sans droits Unix (FAT/exFAT). "
|
||||||
"L'archive est lisible par qui branche la cle : c'est le chiffrement "
|
"L'archive est lisible par qui branche la cle : c'est le chiffrement "
|
||||||
"qui protege, pas le support.")
|
"qui protege, pas le support.")
|
||||||
|
for nom in _deposer_compagnons(dest):
|
||||||
|
print(f" + {nom}")
|
||||||
print(f" VERIFIE : {len(attendu)} fichier(s) relus, empreintes identiques.")
|
print(f" VERIFIE : {len(attendu)} fichier(s) relus, empreintes identiques.")
|
||||||
print(f" Acces : {acces}")
|
print(f" Acces : {acces}")
|
||||||
print(f" Archive : {archive} ({archive.stat().st_size} octets)\n")
|
print(f" Archive : {archive} ({archive.stat().st_size} octets)\n")
|
||||||
|
|
|
||||||
122
scripts/restaurer_cles.py
Normal file
122
scripts/restaurer_cles.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Remet en place les cles sorties par `exporter_cles.py`. Autonome, sans le depot.
|
||||||
|
|
||||||
|
POURQUOI CE SCRIPT DOIT POUVOIR VIVRE SUR LA CLE USB.
|
||||||
|
|
||||||
|
Le jour ou l'on s'en sert, le poste est mort. Le depot Set-OPS est replique — eregion, la
|
||||||
|
forge du site, patient 0 — mais le CLONER demande la cle SSH, qui est justement dans
|
||||||
|
l'archive qu'on essaie d'ouvrir. Une procedure de restauration qui vit dans le depot
|
||||||
|
serait donc inaccessible exactement quand elle sert.
|
||||||
|
|
||||||
|
`exporter_cles.py` en depose donc une copie A COTE DE L'ARCHIVE, avec un mode d'emploi.
|
||||||
|
La cle USB se suffit a elle-meme : gpg, python3, et les trois commandes manuelles ecrites
|
||||||
|
dans le LISEZ-MOI si meme ce script refuse de tourner.
|
||||||
|
|
||||||
|
CE QU'IL FAIT DE PLUS QU'UN `tar -x` :
|
||||||
|
|
||||||
|
- il remet CHAQUE fichier a sa place (`~/.config` ou `~/.ssh`, selon son nom) ;
|
||||||
|
- il repose les permissions a 0600. Sans ca, `ssh` REFUSE une cle privee lisible par
|
||||||
|
d'autres — « UNPROTECTED PRIVATE KEY FILE » — et le message n'aide pas a comprendre
|
||||||
|
qu'il s'agit d'un droit, pas d'une cle invalide. Une archive extraite depuis un
|
||||||
|
support FAT arrive systematiquement dans cet etat ;
|
||||||
|
- il REFUSE d'ecraser une cle existante. Lance par erreur sur un poste qui fonctionne,
|
||||||
|
il ne doit rien detruire.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
MAISON = Path.home()
|
||||||
|
|
||||||
|
|
||||||
|
def _destination(nom: str) -> Path:
|
||||||
|
"""Ou remettre un fichier — deduit de son nom, pas d'un chemin enregistre.
|
||||||
|
|
||||||
|
L'archive ne garde que les noms de base : un chemin absolu la rendrait dependante du
|
||||||
|
compte qui l'a creee, et on restaure souvent sur une machine neuve, parfois sous un
|
||||||
|
autre nom d'utilisateur.
|
||||||
|
"""
|
||||||
|
if nom.startswith("setops-vault"):
|
||||||
|
return MAISON / ".config" / nom
|
||||||
|
if nom.startswith("id_"):
|
||||||
|
return MAISON / ".ssh" / nom
|
||||||
|
raise SystemExit(f"REFUS : {nom} n'est ni une cle de voute ni une cle SSH connue.")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("archive", help="Chemin de l'archive .tar.gpg")
|
||||||
|
ap.add_argument("--lister", action="store_true",
|
||||||
|
help="Montrer ce que l'archive contient, sans rien ecrire")
|
||||||
|
ap.add_argument("--forcer", action="store_true",
|
||||||
|
help="Ecraser une cle deja presente (a n'utiliser qu'en connaissance)")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
arch = Path(args.archive).expanduser()
|
||||||
|
if not arch.is_file():
|
||||||
|
raise SystemExit(f"REFUS : {arch} introuvable.")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
clair = Path(tmp) / "cles.tar"
|
||||||
|
print(" La phrase de passe de l'archive va etre demandee.\n")
|
||||||
|
r = subprocess.run(["gpg", "--quiet", "--output", str(clair),
|
||||||
|
"--decrypt", str(arch)])
|
||||||
|
if r.returncode != 0:
|
||||||
|
raise SystemExit(
|
||||||
|
"\nECHEC du dechiffrement.\n"
|
||||||
|
" Phrase de passe erronee, ou archive abimee. gpg verifie l'integrite\n"
|
||||||
|
" (AES256 + MDC) : s'il refuse, le contenu n'est pas fiable.")
|
||||||
|
|
||||||
|
with tarfile.open(clair) as tar:
|
||||||
|
noms = [n for n in tar.getnames() if "/" not in n and not n.startswith("..")]
|
||||||
|
if args.lister:
|
||||||
|
print(f" {len(noms)} fichier(s) dans l'archive :\n")
|
||||||
|
for n in sorted(noms):
|
||||||
|
m = tar.getmember(n)
|
||||||
|
print(f" {n:<44} {m.size:>5} o -> {_destination(n)}")
|
||||||
|
print("\n LISTE SEULE — rien n'a ete ecrit.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# ON REGARDE AVANT D'ECRIRE. Refuser a mi-parcours laisserait la moitie des
|
||||||
|
# cles en place et l'autre non — un etat que personne ne sait diagnostiquer.
|
||||||
|
deja = [n for n in noms if _destination(n).exists()]
|
||||||
|
if deja and not args.forcer:
|
||||||
|
print(" REFUS : ces cles existent deja ici —", file=sys.stderr)
|
||||||
|
for n in sorted(deja):
|
||||||
|
print(f" ! {_destination(n)}", file=sys.stderr)
|
||||||
|
print("\n Rien n'a ete ecrit. Ce poste a peut-etre deja ses cles.\n"
|
||||||
|
" Si l'ecrasement est VOULU, relancer avec --forcer.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
poses = []
|
||||||
|
for n in sorted(noms):
|
||||||
|
dest = _destination(n)
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
dest.parent.chmod(0o700)
|
||||||
|
membre = tar.extractfile(n)
|
||||||
|
if membre is None:
|
||||||
|
continue
|
||||||
|
dest.write_bytes(membre.read())
|
||||||
|
# 0600 SANS CONDITION : `ssh` refuse une cle privee que d'autres peuvent
|
||||||
|
# lire, et `ansible-vault` n'est pas plus tolerant. Une archive extraite
|
||||||
|
# depuis un support FAT arrive toujours avec de mauvais droits.
|
||||||
|
dest.chmod(0o600)
|
||||||
|
poses.append(dest)
|
||||||
|
|
||||||
|
print(f" {len(poses)} cle(s) remises en place, en 0600 :\n")
|
||||||
|
for d in poses:
|
||||||
|
print(f" {d}")
|
||||||
|
print("\n VERIFIER MAINTENANT, PAS PLUS TARD :")
|
||||||
|
print(" ssh -o BatchMode=yes ansible@<un hyperviseur> hostname")
|
||||||
|
print(" python3 scripts/voutes.py etat")
|
||||||
|
print("\n Une cle remise en place qu'on n'a pas essayee n'est pas une cle restauree.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Reference in a new issue