linux:ca-ssh
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| linux:ca-ssh [2026/06/03 13:16] – root | linux:ca-ssh [2026/06/03 18:23] (Version actuelle) – [Étape 4.2 : Signature par la CA Yubikey] root | ||
|---|---|---|---|
| Ligne 2: | Ligne 2: | ||
| Guide complet pour mettre en place une autorité de certification SSH dans un contexte MSP, avec support multi-Yubikey et intégration dans l' | Guide complet pour mettre en place une autorité de certification SSH dans un contexte MSP, avec support multi-Yubikey et intégration dans l' | ||
| + | |||
| + | **Architecture allégée** : la clé privée de la CA est stockée sur une Yubikey 5 NFC dédiée (slot PIV), pas sur disque. Un backup d' | ||
| ---- | ---- | ||
| Ligne 10: | Ligne 12: | ||
| ^ Rôle ^ Système ^ Notes ^ | ^ Rôle ^ Système ^ Notes ^ | ||
| - | | **Machine CA** (signature des certifs) | Linux (LXC Proxmox) | + | | **Poste de signature** (utilisation Yubikey CA) | Windows / Linux / macOS | OpenSSH |
| - | | **Poste technicien** (utilisation Yubikey) | Windows / Linux / macOS | OpenSSH inclus dans Windows 10/11 (1809+) | | + | | **Poste technicien** (utilisation Yubikey |
| | **Serveurs clients** (qui acceptent les certifs) | Linux principalement | Windows Server avec OpenSSH for Windows fonctionne aussi | | | **Serveurs clients** (qui acceptent les certifs) | Linux principalement | Windows Server avec OpenSSH for Windows fonctionne aussi | | ||
| Ligne 22: | Ligne 24: | ||
| * L' | * L' | ||
| - | **Côté machine | + | **Pour signer avec la Yubikey |
| ---- | ---- | ||
| Ligne 41: | Ligne 43: | ||
| **OpenSSH 10.0** : meilleurs algos par défaut, mais surtout : | **OpenSSH 10.0** : meilleurs algos par défaut, mais surtout : | ||
| - | * Les **clés DSA ne sont plus supportées du tout**, même avec les options de compat ('' | + | * Les **clés DSA ne sont plus supportées du tout**, même avec les options de compat ('' |
| * Le support FIDO2 (libfido2) est plus mature qu'en Bookworm. | * Le support FIDO2 (libfido2) est plus mature qu'en Bookworm. | ||
| * Les algorithmes post-quantum (mlkem768x25519-sha256) sont disponibles par défaut pour le key exchange. | * Les algorithmes post-quantum (mlkem768x25519-sha256) sont disponibles par défaut pour le key exchange. | ||
| - | |||
| - | **''/ | ||
| - | |||
| - | **''/ | ||
| - | |||
| - | **openssh-server ne lit plus '' | ||
| ==== Support à long terme ==== | ==== Support à long terme ==== | ||
| Ligne 57: | Ligne 53: | ||
| C'est donc une base saine pour démarrer une infra MSP qui doit vivre 4-5 ans sans upgrade majeur. | C'est donc une base saine pour démarrer une infra MSP qui doit vivre 4-5 ans sans upgrade majeur. | ||
| - | |||
| - | ==== Images officielles ==== | ||
| - | |||
| - | * Cloud image : https:// | ||
| - | * Template LXC Proxmox : '' | ||
| ---- | ---- | ||
| Ligne 68: | Ligne 59: | ||
| < | < | ||
| - | ┌──────────────────┐ | + | ┌──────────────────────────┐ |
| - | │ Yubikey x2 │ | + | │ Poste de signature |
| - | │ par tech │ → génèrent leurs clés ed25519-sk localement | + | │ (laptop MIS Vincent) |
| - | └────────┬─────────┘ | + | │ │ |
| - | │ .pub envoyée à Odoo | + | │ ┌────────────────────┐ │ ┌─────────────────────────┐ |
| - | ▼ | + | │ │ Yubikey CA dédiée |
| - | ┌──────────────────┐ | + | │ │ (slot PIV 9c) |
| - | │ | + | │ │ ECCP384 |
| - | │ makeitsimple_ | + | │ │ touch CACHED |
| - | │ | + | │ |
| - | │ │ | + | │ │ |
| - | │ | + | │ ┌────────────────────┐ |
| - | └────────┬─────────┘ | + | │ │ Fichier emergency |
| - | | + | │ │ chiffré age │ │ |
| - | | + | │ │ dans Bitwarden |
| - | | + | │ └────────────────────┘ |
| - | | + | └──────────────────────────┘ |
| - | | + | │ |
| - | │ └──────────────────────┘ | + | │ déploiement |
| - | | + | ▼ |
| - | | + | ┌──────────────────────────────────┐ |
| - | | + | │ Serveurs |
| - | ┌──────────────────┐ | + | │ / |
| - | │ Serveurs | + | │ (200+ VMs) |
| - | │ clients | + | │ │ 2. Emergency CA pub |
| - | │ (200+ VMs) │ / | + | │ / |
| - | └──────────────────┘ | + | └──────────────────────────────────┘ |
| </ | </ | ||
| + | |||
| + | **Le principe** : | ||
| + | * La CA quotidienne vit sur Yubikey PIV → clé privée non-extractible, | ||
| + | * La CA emergency est un fichier classique chiffré avec '' | ||
| + | * Les **deux** clés publiques sont déployées sur les serveurs comme CAs valides | ||
| + | * En cas de perte de la Yubikey CA, on déchiffre l' | ||
| ---- | ---- | ||
| - | ===== Phase 1 : Préparation de la LXC CA ===== | + | ===== Phase 1 : Préparation |
| - | ==== Étape 1.1 : Création du conteneur | + | ==== Étape 1.1 : Matériel requis |
| - | Sur le Proxmox | + | * **1x Yubikey 5 NFC dédiée à la CA** (~50 €). À **séparer** de tes Yubikeys SSH d' |
| + | * Ton laptop | ||
| + | |||
| + | ==== Étape 1.2 : Installation des outils ==== | ||
| + | |||
| + | **Sur Debian / Ubuntu / WSL2** | ||
| <code bash> | <code bash> | ||
| - | # Variables | + | sudo apt update |
| - | CTID=200 | + | sudo apt install |
| - | HOSTNAME=" | + | |
| - | STORAGE=" | + | |
| - | BRIDGE=" | + | |
| - | TEMPLATE=" | + | age |
| - | # Vérifier le nom exact disponible avec : | + | </ |
| - | # pveam update && pveam available | grep debian-13 | + | |
| - | # Et télécharger si pas encore présent : | + | |
| - | # pveam download local debian-13-standard_13.5-1_amd64.tar.zst | + | |
| - | # Création LXC | + | **Sur macOS** (Homebrew) |
| - | pct create $CTID local:vztmpl/ | + | |
| - | --hostname $HOSTNAME \ | + | |
| - | --cores 1 --memory 512 --swap 256 \ | + | |
| - | --rootfs $STORAGE:8 \ | + | |
| - | --net0 name=eth0, | + | |
| - | --nameserver 10.10.10.1 \ | + | |
| - | --onboot 0 \ | + | |
| - | --start 0 \ | + | |
| - | --unprivileged 1 \ | + | |
| - | --features nesting=0, | + | |
| - | --password " | + | |
| - | # Démarrage | + | <code bash> |
| - | pct start $CTID | + | brew install yubikey-manager opensc age openssh |
| </ | </ | ||
| - | ==== Étape 1.2 : Durcissement initial | + | **Sur Windows** : |
| + | |||
| + | <code powershell> | ||
| + | # Yubikey Manager : https:// | ||
| + | # Installer aussi yubico-piv-tool qui contient ykcs11.dll | ||
| + | # OpenSSH inclus avec Windows 10/11 (sinon : Add-WindowsCapability) | ||
| + | # age : winget install FiloSottile.age | ||
| + | </ | ||
| + | |||
| + | ==== Étape 1.3 : Localisation du provider PKCS# | ||
| + | |||
| + | Cette information est cruciale pour les commandes de signature. Note le chemin selon ton OS : | ||
| + | |||
| + | ^ OS ^ Chemin PKCS#11 (OpenSC) ^ | ||
| + | | Debian/ | ||
| + | | Debian/ | ||
| + | | macOS (Intel) | ''/ | ||
| + | | macOS (Apple Silicon) | ''/ | ||
| + | | Windows | '' | ||
| + | |||
| + | Vérification rapide qu'il existe : | ||
| <code bash> | <code bash> | ||
| - | # Entrer dans la LXC | + | # Linux/macOS |
| - | pct enter $CTID | + | ls -la $PKCS11_PATH |
| + | </ | ||
| - | # Hardening de base | + | Pour la suite du document, on utilisera la variable '' |
| - | apt update && apt upgrade -y | + | |
| - | apt install -y openssh-server fail2ban ufw vim | + | |
| - | # Firewall : SSH uniquement depuis le subnet de management | + | <code bash> |
| - | ufw default deny incoming | + | export PKCS11=/usr/lib/ |
| - | ufw default deny outgoing | + | </code> |
| - | ufw allow from 10.10.10.0/24 to any port 22 proto tcp | + | |
| - | ufw allow out 53/udp # DNS pour apt | + | |
| - | ufw allow out 80,443/tcp # apt updates | + | |
| - | ufw enable | + | |
| - | # Utilisateur dédié pour la signature | + | ==== Étape 1.4 : Initialisation des PINs de la Yubikey CA ==== |
| - | useradd -m -s /bin/bash ca-signer | + | |
| - | mkdir -p / | + | |
| - | chmod 700 / | + | |
| - | # Désactivation auth password sur SSH | + | ⚠️ **Yubikey CA branchée**. Vérifie que c'est la bonne (pas une Yubikey SSH du quotidien) : |
| - | sed -i ' | + | |
| - | sed -i 's/^#\? | + | <code bash> |
| - | systemctl restart ssh | + | ykman info |
| + | # Doit afficher le serial number de la Yubikey CA | ||
| </ | </ | ||
| - | ==== Étape 1.3 : Stockage chiffré | + | **PIN PIV** (8 caractères minimum, sera demandé à chaque session de signature) : |
| - | Pour aller plus loin, la rootfs de la LXC peut être stockée sur un dataset ZFS chiffré côté hôte Proxmox | + | <code bash> |
| + | ykman piv access change-pin | ||
| + | # Default PIN: 123456 | ||
| + | # New PIN: [choisir 8+ chars] | ||
| + | </ | ||
| + | |||
| + | **PUK** (utilisé pour débloquer si trop d' | ||
| <code bash> | <code bash> | ||
| - | # Sur l' | + | ykman piv access change-puk |
| - | zfs create -o encryption=aes-256-gcm -o keyformat=passphrase rpool/ | + | # Default PUK: 12345678 |
| - | # La passphrase sera demandée à chaque démarrage de l' | + | # New PUK: [générer 8 chars aléatoires] |
| </ | </ | ||
| + | |||
| + | **Management Key** (utilisée pour les opérations admin PIV, on la protège avec le PIN) : | ||
| + | |||
| + | <code bash> | ||
| + | ykman piv access change-management-key --algorithm AES256 --generate --protect | ||
| + | # --protect = stocke la management key chiffrée par le PIN | ||
| + | </ | ||
| + | |||
| + | 📋 **Notes à conserver dans Bitwarden, entrée "MIS Yubikey CA"** : | ||
| + | * Serial number de la Yubikey | ||
| + | * PIN PIV | ||
| + | * PUK | ||
| + | * (la management key est protégée par le PIN, pas besoin de la stocker à part) | ||
| ---- | ---- | ||
| Ligne 177: | Ligne 197: | ||
| ===== Phase 2 : Génération des CAs ===== | ===== Phase 2 : Génération des CAs ===== | ||
| - | ==== Étape 2.1 : CA pour les utilisateurs | + | ==== Étape 2.1 : CA principale sur Yubikey |
| <code bash> | <code bash> | ||
| - | # Connecté en tant que ca-signer dans la LXC | + | # Yubikey CA branchée |
| - | mkdir -p ~/mis-ca && cd ~/mis-ca | + | mkdir -p ~/mis-ssh-ca && cd ~/mis-ssh-ca |
| chmod 700 . | chmod 700 . | ||
| - | ssh-keygen | + | # Génération de la paire ECCP384 dans le slot 9c (Digital Signature) |
| - | -f mis-users-ca \ | + | ykman piv keys generate \ |
| - | -C "MIS Users CA - Make IT Simple" | + | |
| - | -N ' | + | --pin-policy ONCE \ |
| + | --touch-policy CACHED | ||
| + | | ||
| + | mis-users-ca-yubi-cert.pub | ||
| + | # → demande le PIN | ||
| + | # → demande le touch | ||
| </ | </ | ||
| - | ==== Étape 2.2 : CA pour les hôtes (serveurs) | + | Décortiquons les options : |
| + | |||
| + | ^ Option ^ Valeur ^ Sens ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | |||
| + | **Alternative plus stricte** : '' | ||
| + | |||
| + | ==== Étape 2.2 : Certificat self-signed PIV ==== | ||
| + | |||
| + | PIV exige qu'un certificat X.509 soit présent dans le slot, même s'il ne sert à rien dans notre usage SSH : | ||
| <code bash> | <code bash> | ||
| - | ssh-keygen -t ed25519 -a 200 \ | + | ykman piv certificates generate |
| - | -f mis-hosts-ca \ | + | --subject |
| - | -C " | + | --valid-days 3650 \ |
| - | -N ' | + | 9c \ |
| + | mis-users-ca-yubi-cert.pub | ||
| + | # → demande le PIN | ||
| </ | </ | ||
| - | ==== Étape 2.3 : Backup chiffré des clés privées | + | ==== Étape 2.3 : Export de la clé publique au format SSH ==== |
| - | ⚠️ | + | C'est cette clé publique qui sera déployée sur tous les serveurs comme **CA principale acceptée** : |
| <code bash> | <code bash> | ||
| - | # Création d'une archive chiffrée | + | ssh-keygen |
| - | cd ~/mis-ca | + | |
| - | tar czf - mis-users-ca | + | |
| - | age -p > mis-ca-backup-$(date +%Y%m%d).tar.gz.age | + | |
| - | # Demande une passphrase distincte des passphrases des CAs | + | |
| - | # À conserver dans : | + | # Inspection |
| - | # 1. Bitwarden (attachment de l' | + | cat mis-users-ca-yubi.pub |
| - | # 2. Clé USB chiffrée dans coffre physique | + | # Doit afficher : ecdsa-sha2-nistp384 AAAA... |
| - | # 3. (optionnel) Coffre bancaire pour archive papier-QR de la passphrase | + | |
| + | # Renommer avec un commentaire explicite | ||
| + | sed -i 's|$| MIS Users CA - Yubikey|' | ||
| </ | </ | ||
| - | ---- | + | ==== Étape 2.4 : Génération de la CA emergency (container éphémère) ==== |
| - | ===== Phase 3 : Bootstrap d'un serveur client ===== | + | La CA emergency est une clé privée Ed25519 classique, mais qui ne doit **jamais exister en clair sur ton laptop** après génération. La meilleure approche |
| - | ==== Étape 3.1 : Déploiement de la CA users sur le serveur ==== | + | **Pourquoi pas juste sur ton laptop ?** Parce qu' |
| - | Sur un serveur client (Debian provisionné | + | **Génération |
| <code bash> | <code bash> | ||
| - | # Copie de la clé publique de la CA users | + | # Lancement d'un container Debian 13 minimal, SANS RÉSEAU |
| - | scp ca-signer@10.10.10.50: | + | docker run --rm -it \ |
| - | chmod 644 /etc/ssh/mis-users-ca.pub | + | |
| + | --hostname offline-ca-gen \ | ||
| + | -v ~/mis-ssh-ca:/output \ | ||
| + | debian: | ||
| + | </ | ||
| - | # Création d'un KRL vide initial | + | **Dans le container** (aucun accès Internet possible) : |
| - | ssh-keygen -k -f / | + | |
| - | chmod 644 / | + | |
| - | # Configuration sshd | + | <code bash> |
| - | cat >> /etc/ssh/ | + | # Installation des outils (depuis le cache APT du container, pas Internet) |
| + | # Si l' | ||
| + | apt-get install -y openssh-client age coreutils 2>/dev/null || echo "Image sans cache, voir note" | ||
| - | # ===== MIS SSH CA ===== | + | cd /tmp |
| - | TrustedUserCAKeys | + | |
| - | RevokedKeys | + | # Génération Ed25519 classique |
| - | PasswordAuthentication | + | ssh-keygen -t ed25519 -a 200 \ |
| - | KbdInteractiveAuthentication no | + | -f mis-users-ca-emergency \ |
| - | PubkeyAuthentication yes | + | -C "MIS Users CA - EMERGENCY USE ONLY" \ |
| - | # ====================== | + | -N '' |
| + | |||
| + | # Chiffrement avec age - passphrase en interactif (PAS en argument !) | ||
| + | age --passphrase \ | ||
| + | -o mis-users-ca-emergency.age \ | ||
| + | mis-users-ca-emergency | ||
| + | # → entrer une passphrase TRÈS robuste (Diceware 6-8 mots recommandé) | ||
| + | |||
| + | # Effacement sécurisé de la version en clair | ||
| + | shred -u mis-users-ca-emergency | ||
| + | |||
| + | # Copie des fichiers finaux vers l' | ||
| + | cp mis-users-ca-emergency.age | ||
| + | cp mis-users-ca-emergency.pub /output/ | ||
| + | |||
| + | # Sortie du container → destruction automatique (--rm) | ||
| + | exit | ||
| + | </code> | ||
| + | |||
| + | **Note** : si '' | ||
| + | |||
| + | <code bash> | ||
| + | # À faire une seule fois, sur ton hôte | ||
| + | cat > /tmp/Dockerfile <<' | ||
| + | FROM debian: | ||
| + | RUN apt-get update && \ | ||
| + | | ||
| + | | ||
| + | rm -rf / | ||
| EOF | EOF | ||
| - | # Test et reload | + | docker build -t mis-ca-gen: |
| - | sshd -t && systemctl reload | + | |
| + | # Puis utiliser cette image (en remplaçant debian: | ||
| + | docker run --rm -it --network none -v ~/mis-ssh-ca:/output mis-ca-gen: | ||
| </ | </ | ||
| - | À industrialiser via Ansible | + | **Variante Podman rootless** |
| - | ==== Étape 3.2 : Certificat d' | + | <code bash> |
| + | podman run --rm -it --network none -v ~/ | ||
| + | </ | ||
| - | Sur la CA : | + | **Résultat sur ton hôte** |
| <code bash> | <code bash> | ||
| - | # Récupération de la clé publique | + | ls -la ~/ |
| - | scp mis-admin@srv01.acme.lan:/ | + | # mis-users-ca-emergency.age |
| + | # mis-users-ca-emergency.pub | ||
| + | # mis-users-ca-yubi.pub ← clé publique de la Yubikey CA | ||
| + | # mis-users-ca-yubi-cert.pub ← cert X.509 PIV (inutile pour SSH) | ||
| + | </ | ||
| - | # Signature comme hôte | + | ==== Étape 2.5 : CA pour les hôtes |
| - | ssh-keygen -s ~/ | + | |
| - | -I "srv01.acme.lan-$(date +%Y)" \ | + | |
| - | -h \ | + | |
| - | -n " | + | |
| - | -V " | + | |
| - | / | + | |
| - | # Envoi sur le serveur | + | Même approche pour la CA d' |
| - | scp / | + | * Soit sur la **même Yubikey** dans le slot 9a (Authentication) ou 9d (Key Management) |
| - | ssh mis-admin@srv01.acme.lan "sudo mv / | + | * Soit avec un fichier classique chiffré, plus simple pour démarrer |
| - | # Activation côté serveur | + | Comme tu as décidé de ne pas faire les certifs machines tout de suite, on saute cette étape pour le moment. À reprendre quand tu te lanceras dans les certifs |
| - | ssh mis-admin@srv01.acme.lan "sudo sed -i '/ | + | |
| - | echo ' | + | ==== Étape 2.6 : Stratégie de stockage des secrets (double chiffrement) ==== |
| - | sudo systemctl reload ssh" | + | |
| + | **Principe** : la clé emergency est protégée par **deux couches** indépendantes : | ||
| + | |||
| + | < | ||
| + | Clé privée Ed25519 (claire) | ||
| + | ↓ couche 1 : chiffrement age (passphrase forte) | ||
| + | Fichier .age (illisible sans passphrase age) | ||
| + | ↓ couche 2 : attachment dans Bitwarden (chiffré par master password) | ||
| + | Coffre Bitwarden (illisible sans master password) | ||
| </ | </ | ||
| - | ==== Étape 3.3 : Configuration côté postes techniciens ==== | + | Pour que cette défense en profondeur fonctionne, **les deux secrets doivent être stockés à des endroits différents** |
| - | Sur **chaque** poste tech (Windows ou Linux), dans '' | + | === Ce qui va dans Bitwarden === |
| + | |||
| + | Entrée | ||
| + | |||
| + | **Notes | ||
| < | < | ||
| - | @cert-authority *.acme.lan,*.mis.lan,10.10.* ssh-ed25519 AAAAC3... MIS Hosts CA | + | ═══════════════════════════════════════════════ |
| + | PROCÉDURE DE RÉCUPÉRATION D' | ||
| + | |||
| + | 1. Récupérer la passphrase age : | ||
| + | - Coffre bureau MIS, enveloppe scellée "CA Emergency" | ||
| + | - OU coffre backup (maison Vincent / coffre bancaire) | ||
| + | |||
| + | 2. Télécharger | ||
| + | |||
| + | 3. Sur container éphémère : | ||
| + | | ||
| + | cd /work | ||
| + | age --decrypt mis-users-ca-emergency.age > recovery-key | ||
| + | chmod 600 recovery-key | ||
| + | # utiliser pour signer (voir Phase 9 du runbook) | ||
| + | shred -u recovery-key | ||
| + | | ||
| + | |||
| + | ═══════════════════════════════════════════════ | ||
| + | INFOS YUBIKEY CA | ||
| + | |||
| + | Serial number | ||
| + | PIN PIV : [PIN à compléter] | ||
| + | PUK : [PUK à compléter] | ||
| + | Algorithme | ||
| + | Slot : 9c | ||
| + | Touch policy | ||
| + | PIN policy | ||
| + | |||
| + | ═══════════════════════════════════════════════ | ||
| + | FINGERPRINTS (vérification d' | ||
| + | |||
| + | Yubikey CA pub : [ssh-keygen -l -f mis-users-ca-yubi.pub] | ||
| + | Emergency | ||
| + | |||
| + | ═══════════════════════════════════════════════ | ||
| </ | </ | ||
| - | (la clé publique correspond au contenu de '' | + | **Attachments |
| - | À partir | + | ^ Fichier ^ Sensibilité ^ Pourquoi dans Bitwarden ^ |
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | |||
| + | **Ce qui ne doit JAMAIS aller dans Bitwarden** : | ||
| + | * ❌ La passphrase age (sinon couche 1 + couche 2 = même point de défaillance) | ||
| + | * ❌ La clé privée emergency en clair | ||
| + | * ❌ Un export PKCS#12 de la Yubikey (impossible techniquement, | ||
| + | |||
| + | === Ce qui va sur papier (hors numérique) === | ||
| + | |||
| + | La **passphrase age** uniquement. | ||
| + | |||
| + | * **Localisation principale** : coffre physique au bureau MIS, dans une enveloppe scellée "CA Emergency - Vincent uniquement" | ||
| + | * **Localisation backup** : coffre physique à la maison | ||
| + | |||
| + | **Format recommandé** : passphrase Diceware 6-8 mots, plus facile à recopier sans erreur qu'une chaîne aléatoire. | ||
| + | |||
| + | Exemple : '' | ||
| + | |||
| + | **Approche avancée (optionnelle)** : Shamir Secret Sharing avec '' | ||
| + | |||
| + | === Backup hors-Bitwarden additionnel === | ||
| + | |||
| + | En complément (ceinture-bretelles) : | ||
| + | * Clé USB chiffrée LUKS dans coffre physique avec une copie de tout '' | ||
| + | * À refaire à chaque rotation/ | ||
| + | * La passphrase LUKS est encore un secret différent (toujours pas dans Bitwarden) | ||
| + | |||
| + | ==== Étape 2.7 : Test de récupération (CRITIQUE) ==== | ||
| + | |||
| + | ⚠️ **Avant de tout ranger dans le coffre et de considérer le setup terminé**, tu dois faire un test de récupération complet. C'est l' | ||
| + | |||
| + | <code bash> | ||
| + | # 1. Dans un dossier temporaire de test | ||
| + | mkdir / | ||
| + | |||
| + | # 2. Copier le .age depuis Bitwarden (téléchargement de l' | ||
| + | # OU depuis ~/ | ||
| + | cp ~/ | ||
| + | |||
| + | # 3. Aller récupérer le papier dans le coffre, lire la passphrase | ||
| + | # (Geste réel, pas une simulation : tu dois vérifier que la passphrase | ||
| + | # est bien lisible et que tu peux la recopier sans erreur) | ||
| + | |||
| + | # 4. Déchiffrer en utilisant la passphrase du papier | ||
| + | age --decrypt mis-users-ca-emergency.age > recovery-key | ||
| + | # → entrer la passphrase telle qu' | ||
| + | |||
| + | # 5. Vérifier que la clé déchiffrée est bien la bonne | ||
| + | ssh-keygen -y -f recovery-key > recovered.pub | ||
| + | diff recovered.pub ~/ | ||
| + | # Sortie attendue : VIDE (les deux fichiers sont identiques) | ||
| + | # Si différents → la passphrase a été mal recopiée OU le .age est corrompu | ||
| + | |||
| + | # 6. Nettoyage immédiat | ||
| + | shred -u recovery-key recovered.pub | ||
| + | cd .. && rm -rf / | ||
| + | </ | ||
| + | |||
| + | **Si le test réussit, tu as validé** : | ||
| + | * ✅ La passphrase est correctement écrite sur papier (lisible, sans erreur) | ||
| + | * ✅ Le fichier '' | ||
| + | * ✅ Tu sais exécuter la procédure (le geste compte autant que le savoir) | ||
| + | * ✅ Les fingerprints correspondent | ||
| + | |||
| + | **Si le test échoue**, NE PAS continuer. Identifier le problème (passphrase mal recopiée ? mauvais fichier '' | ||
| + | |||
| + | **À refaire annuellement** dans le cadre du drill de récupération (voir checklist en fin de doc). | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Phase 3 : Bootstrap d'un serveur | ||
| + | |||
| + | ==== Étape 3.1 : Préparation du fichier de CAs publiques ==== | ||
| + | |||
| + | Sur ton poste de signature, prépare un fichier qui contient les **deux** clés publiques (Yubikey + emergency) que tu vas déployer sur tous les serveurs : | ||
| + | |||
| + | <code bash> | ||
| + | cd ~/ | ||
| + | cat mis-users-ca-yubi.pub mis-users-ca-emergency.pub > mis-users-ca-deploy.pub | ||
| + | |||
| + | cat mis-users-ca-deploy.pub | ||
| + | # Doit contenir 2 lignes : | ||
| + | # ecdsa-sha2-nistp384 AAAA... | ||
| + | # ssh-ed25519 AAAA... MIS Users CA - EMERGENCY USE ONLY | ||
| + | </ | ||
| + | |||
| + | C'est ce fichier '' | ||
| + | |||
| + | ==== Étape 3.2 : Déploiement sur un serveur client ==== | ||
| + | |||
| + | Sur un serveur client (Debian 13 provisionné via cloud-init) : | ||
| + | |||
| + | <code bash> | ||
| + | # Copie du fichier de CAs | ||
| + | scp ~/ | ||
| + | |||
| + | # Côté serveur, déploiement | ||
| + | ssh mis-admin@srv01.acme.lan | ||
| + | sudo install -o root -g root -m 0644 / | ||
| + | |||
| + | # Création d'un KRL vide initial | ||
| + | sudo ssh-keygen -k -f / | ||
| + | sudo chmod 644 / | ||
| + | |||
| + | # Configuration sshd | ||
| + | sudo tee -a / | ||
| + | |||
| + | # ===== MIS SSH CA ===== | ||
| + | TrustedUserCAKeys / | ||
| + | RevokedKeys / | ||
| + | PasswordAuthentication no | ||
| + | KbdInteractiveAuthentication no | ||
| + | PubkeyAuthentication yes | ||
| + | # ====================== | ||
| + | EOF | ||
| + | |||
| + | # Test et reload | ||
| + | sudo sshd -t && sudo systemctl reload ssh | ||
| + | </ | ||
| + | |||
| + | À industrialiser via Ansible (voir Phase 7). | ||
| ---- | ---- | ||
| Ligne 295: | Ligne 537: | ||
| ==== Étape 4.1 : Génération sur le poste du technicien ==== | ==== Étape 4.1 : Génération sur le poste du technicien ==== | ||
| - | **Sur Windows** : | + | **Sur Windows** |
| <code powershell> | <code powershell> | ||
| - | # PowerShell, Yubikey branchée | ||
| # Définition d'un PIN FIDO2 si pas déjà fait (via Yubikey Manager GUI) | # Définition d'un PIN FIDO2 si pas déjà fait (via Yubikey Manager GUI) | ||
| Ligne 316: | Ligne 557: | ||
| Résultat : '' | Résultat : '' | ||
| - | ==== Étape 4.2 : Signature par la CA ==== | + | ==== Étape 4.2 : Signature par la CA Yubikey |
| - | Le technicien | + | Le tech envoie sa '' |
| <code bash> | <code bash> | ||
| - | # Sur la LXC, en tant que ca-signer | + | cd ~/mis-ssh-ca |
| - | cd ~/mis-ca | + | export PKCS11=/ |
| # Variables | # Variables | ||
| - | PUBKEY_PATH=/ | + | PUBKEY=/ |
| CERT_ID=" | CERT_ID=" | ||
| PRINCIPALS=" | PRINCIPALS=" | ||
| VALIDITY=" | VALIDITY=" | ||
| - | SERIAL=1001 | + | SERIAL=1001 |
| - | ssh-keygen -s mis-users-ca \ | + | ssh-keygen -s mis-users-ca-yubi.pub \ |
| + | -D $PKCS11 | ||
| -I " | -I " | ||
| -n " | -n " | ||
| Ligne 340: | Ligne 582: | ||
| -O no-agent-forwarding \ | -O no-agent-forwarding \ | ||
| -O permit-pty \ | -O permit-pty \ | ||
| - | "$PUBKEY_PATH" | + | "$PUBKEY" |
| - | # Le fichier vincent-yubi-primary-cert.pub | + | # Appuyer sur la yubikey dès que le pin est introduit |
| + | # → demande le PIN PIV (1x par session grâce à --pin-policy ONCE) | ||
| + | # → demande le touch sur la Yubikey CA | ||
| + | # → fichier vincent-yubi-primary-cert.pub généré à côté de la .pub | ||
| </ | </ | ||
| + | |||
| + | **Note importante** : avec '' | ||
| Renvoyer le '' | Renvoyer le '' | ||
| Ligne 364: | Ligne 611: | ||
| </ | </ | ||
| - | Doit afficher : Principals, Valid from/to, Critical Options, Extensions. | + | Doit afficher : Signing CA ('' |
| ==== Étape 4.4 : Test de connexion ==== | ==== Étape 4.4 : Test de connexion ==== | ||
| Ligne 370: | Ligne 617: | ||
| <code bash> | <code bash> | ||
| ssh -i ~/ | ssh -i ~/ | ||
| - | # → PIN FIDO2 demandé | + | # → PIN FIDO2 demandé |
| - | # → touch Yubikey | + | # → touch Yubikey |
| # → connecté | # → connecté | ||
| </ | </ | ||
| - | |||
| - | **Indicateur clé** : aucune modification d''' | ||
| ==== Étape 4.5 : Enrôlement de la Yubikey de backup ==== | ==== Étape 4.5 : Enrôlement de la Yubikey de backup ==== | ||
| - | **Répéter Étape 4.1 à 4.4 avec la deuxième Yubikey**. | + | **Répéter Étape 4.1 à 4.4 avec la deuxième Yubikey |
| - | + | * Serial | |
| - | À la fin, le technicien a 6 fichiers dans '' | + | * Identifiant |
| - | + | ||
| - | < | + | |
| - | id_ed25519_sk_yubi1 | + | |
| - | id_ed25519_sk_yubi1.pub | + | |
| - | id_ed25519_sk_yubi1-cert.pub | + | |
| - | id_ed25519_sk_yubi2 | + | |
| - | id_ed25519_sk_yubi2.pub | + | |
| - | id_ed25519_sk_yubi2-cert.pub | + | |
| - | </ | + | |
| ==== Étape 4.6 : Configuration `~/ | ==== Étape 4.6 : Configuration `~/ | ||
| < | < | ||
| - | Host *.acme.lan acme-* | + | Host *.acme.lan acme-* *.mis.lan |
| - | User mis-admin | + | |
| - | IdentityFile ~/ | + | |
| - | IdentityFile ~/ | + | |
| - | IdentitiesOnly yes | + | |
| - | CertificateFile ~/ | + | |
| - | CertificateFile ~/ | + | |
| - | + | ||
| - | Host *.mis.lan | + | |
| User mis-admin | User mis-admin | ||
| IdentityFile ~/ | IdentityFile ~/ | ||
| Ligne 411: | Ligne 639: | ||
| CertificateFile ~/ | CertificateFile ~/ | ||
| </ | </ | ||
| - | |||
| - | SSH essaiera Yubi1 d' | ||
| ---- | ---- | ||
| Ligne 420: | Ligne 646: | ||
| ==== Étape 5.1 : Mise à jour de la KRL ==== | ==== Étape 5.1 : Mise à jour de la KRL ==== | ||
| - | Sur la CA, si on perd la Yubikey | + | Yubikey |
| <code bash> | <code bash> | ||
| - | cd ~/mis-ca | + | cd ~/mis-ssh-ca |
| - | # Création du spec de révocation | + | # Spec de révocation |
| cat > / | cat > / | ||
| # Révocation par serial | # Révocation par serial | ||
| Ligne 437: | Ligne 663: | ||
| EOF | EOF | ||
| - | # Génération/ | + | # Génération/ |
| - | ssh-keygen -k -f mis-revoked-keys -s mis-users-ca -u / | + | ssh-keygen -k -f mis-revoked-keys |
| + | | ||
| + | -D $PKCS11 \ | ||
| + | | ||
| # -u = update (ajoute au KRL existant) | # -u = update (ajoute au KRL existant) | ||
| - | # Sans -u, le KRL est remplacé | + | # → demande PIN + touch |
| - | + | ||
| - | # Vérification du contenu | + | |
| - | ssh-keygen -Q -f mis-revoked-keys -t krl | + | |
| </ | </ | ||
| - | ==== Étape 5.2 : Déploiement de la KRL ==== | + | ==== Étape 5.2 : Déploiement de la KRL via Ansible |
| - | + | ||
| - | Via Ansible sur tout le parc (voir Phase 7) : | + | |
| <code bash> | <code bash> | ||
| - | ansible-playbook -i inventory.yml mis-deploy-krl.yml | + | ansible-playbook -i inventory.yml mis-update-krl.yml |
| </ | </ | ||
| Ligne 459: | Ligne 683: | ||
| ===== Phase 6 : Renouvellement des certificats ===== | ===== Phase 6 : Renouvellement des certificats ===== | ||
| - | |||
| - | Les certificats ayant une durée de vie de 1 an, prévoir un workflow de renouvellement : | ||
| ==== Option A : Manuel ==== | ==== Option A : Manuel ==== | ||
| - | Refaire | + | Refaire |
| ==== Option B : Automatisé via Odoo ==== | ==== Option B : Automatisé via Odoo ==== | ||
| Ligne 471: | Ligne 693: | ||
| - Notifie le tech par mail | - Notifie le tech par mail | ||
| - Propose un wizard de renouvellement | - Propose un wizard de renouvellement | ||
| - | - Si le tech accepte, | + | - Si le tech accepte, |
| - | - Envoie le nouveau '' | + | - Envoie le nouveau '' |
| ---- | ---- | ||
| Ligne 488: | Ligne 710: | ||
| become: true | become: true | ||
| vars: | vars: | ||
| - | ca_pub_path: | + | ca_pub_path: |
| krl_path: files/ | krl_path: files/ | ||
| | | ||
| tasks: | tasks: | ||
| - | - name: Deploy MIS users CA public | + | - name: Deploy MIS users CA public |
| copy: | copy: | ||
| src: "{{ ca_pub_path }}" | src: "{{ ca_pub_path }}" | ||
| Ligne 544: | Ligne 766: | ||
| </ | </ | ||
| - | ==== Playbook de mise à jour KRL uniquement | + | ==== Playbook de mise à jour KRL uniquement ==== |
| '' | '' | ||
| Ligne 575: | Ligne 797: | ||
| ⚠️ **Ne JAMAIS sauter cette étape.** | ⚠️ **Ne JAMAIS sauter cette étape.** | ||
| - | Indépendamment | + | C'est un parachute **indépendant** |
| <code bash> | <code bash> | ||
| Ligne 583: | Ligne 805: | ||
| # Stocker mis-breakglass (privé) : | # Stocker mis-breakglass (privé) : | ||
| - | # - Chiffré dans Bitwarden (attachment) | + | # - Chiffré dans Bitwarden (attachment) |
| # - Sur clé USB chiffrée dans coffre physique | # - Sur clé USB chiffrée dans coffre physique | ||
| # - Supprimer du disque local après stockage sécurisé | # - Supprimer du disque local après stockage sécurisé | ||
| Ligne 593: | Ligne 815: | ||
| <code bash> | <code bash> | ||
| - | # Dans virt-customize de la golden image | ||
| virt-customize -a debian-13-genericcloud-amd64.qcow2 \ | virt-customize -a debian-13-genericcloud-amd64.qcow2 \ | ||
| --run-command ' | --run-command ' | ||
| Ligne 600: | Ligne 821: | ||
| ... | ... | ||
| </ | </ | ||
| - | |||
| - | Le jour où la CA est inaccessible (perte de toutes les Yubikeys, corruption de la LXC, etc.), tu sors la clé du coffre Bitwarden, tu te connectes avec, tu reconstruis la situation. | ||
| ---- | ---- | ||
| - | ===== Phase 9 : Migration future vers Yubikey PIV pour la CA ===== | + | ===== Phase 9 : Procédure de récupération d' |
| - | Quand le workflow sera rodé (6-12 mois), migration de la clé privée de la CA depuis disque vers Yubikey PIV : | + | ==== Cas 1 : Yubikey CA perdue / cassée, emergency disponible ==== |
| - | <code bash> | + | **Workflow** : |
| - | # Sur un poste sûr, Yubikey dédiée "CA Master" | + | |
| - | apt install yubikey-manager opensc | + | |
| - | # Génération d'une nouvelle | + | - Acheter une nouvelle Yubikey 5 NFC dédiée à la CA |
| - | ykman piv keys generate | + | - Déchiffrer temporairement l' |
| - | | + | |
| + | cd ~/ | ||
| + | age --decrypt mis-users-ca-emergency.age > / | ||
| + | chmod 600 / | ||
| + | ``` | ||
| + | - **Utiliser l'emergency CA pour signer | ||
| + | ```bash | ||
| + | # Préparer la nouvelle Yubikey (Étapes 1.4 + 2.1 à 2.3) | ||
| + | # Tu obtiens mis-users-ca-yubi-NEW.pub | ||
| + | ``` | ||
| + | | ||
| + | | ||
| + | cat mis-users-ca-yubi-NEW.pub mis-users-ca-emergency.pub > mis-users-ca-deploy.pub | ||
| + | ``` | ||
| + | | ||
| + | - Révoquer l' | ||
| + | | ||
| + | cat > /tmp/krl-revoke-old-ca.txt <<' | ||
| + | # Révocation de l' | ||
| + | | ||
| + | EOF | ||
| + | |||
| + | | ||
| + | -s mis-users-ca-yubi-NEW.pub \ | ||
| + | -D $PKCS11 \ | ||
| + | -u / | ||
| + | ``` | ||
| + | - **Effacement sécurisé** du fichier emergency en clair : | ||
| + | | ||
| + | shred -u / | ||
| + | ``` | ||
| + | - Mise à jour de l' | ||
| - | # Certificat self-signed pour PIV | + | **Important** : tous les certificats utilisateurs signés par l' |
| - | ykman piv certificates generate --subject " | + | |
| - | # Export au format SSH | + | ==== Cas 2 : Tout est perdu sauf break-glass ==== |
| - | ssh-keygen -D / | + | |
| - | </ | + | |
| - | Pour signer ensuite | + | Scénario apocalypse |
| - | < | + | - Connexion sur chaque serveur via SSH avec '' |
| - | ssh-keygen | + | ```bash |
| - | -D / | + | |
| - | -I " | + | ``` |
| - | -n "mis-admin, | + | - Régénération complète d'une nouvelle CA (depuis zéro, Phase 2) |
| - | -V " | + | - Déploiement manuel du nouveau '' |
| - | vincent-yubi-primary.pub | + | - Re-signature de toutes les Yubikeys techniciens |
| - | # → PIN PIV demandé | + | - Rotation du break-glass aussi (puisqu' |
| - | # → touch Yubikey | + | |
| - | # → certificat signé | + | |
| - | </ | + | |
| - | La clé privée | + | **À tester** : faire un drill annuel |
| ---- | ---- | ||
| Ligne 649: | Ligne 891: | ||
| # Inspecter un certificat utilisateur | # Inspecter un certificat utilisateur | ||
| ssh-keygen -L -f id_ed25519_sk_yubi1-cert.pub | ssh-keygen -L -f id_ed25519_sk_yubi1-cert.pub | ||
| + | |||
| + | # Voir l' | ||
| + | ykman piv info | ||
| + | |||
| + | # Voir le slot 9c (où vit la CA) | ||
| + | ykman piv keys metadata 9c | ||
| # Voir la KRL active sur un serveur | # Voir la KRL active sur un serveur | ||
| Ligne 657: | Ligne 905: | ||
| # Output: " | # Output: " | ||
| - | # Côté serveur, voir quelles | + | # Côté serveur, voir les connexions par certificat |
| journalctl -u ssh | grep " | journalctl -u ssh | grep " | ||
| - | # Lister les clés résidentes sur une Yubikey | + | # Lister les clés publiques visibles via PKCS#11 (avec Yubikey |
| - | ssh-keygen -K | + | ssh-keygen -D $PKCS11 -e |
| - | # Récupère les stubs sur le poste courant depuis la Yubikey | + | |
| + | # Tester une signature sans toucher à un fichier | ||
| + | echo " | ||
| </ | </ | ||
| - | ==== B. Format complet d'une signature ssh-keygen ==== | + | ==== B. Format complet d'une signature ssh-keygen |
| <code bash> | <code bash> | ||
| - | ssh-keygen -s CA_PRIVATE_KEY | + | ssh-keygen -s CA_PUBLIC_KEY \ |
| + | -D PKCS11_PROVIDER | ||
| -I CERT_IDENTIFIER \ | -I CERT_IDENTIFIER \ | ||
| -n PRINCIPAL1, | -n PRINCIPAL1, | ||
| Ligne 679: | Ligne 930: | ||
| ^ Option ^ Description ^ | ^ Option ^ Description ^ | ||
| - | | '' | + | | '' |
| + | | '' | ||
| | '' | | '' | ||
| | '' | | '' | ||
| Ligne 688: | Ligne 940: | ||
| | '' | | '' | ||
| | '' | | '' | ||
| - | | '' | ||
| | '' | | '' | ||
| | '' | | '' | ||
| Ligne 695: | Ligne 946: | ||
| ==== C. Liens utiles ==== | ==== C. Liens utiles ==== | ||
| - | * OpenSSH manual '' | + | * OpenSSH manual '' |
| * Documentation officielle Yubico FIDO2 SSH : https:// | * Documentation officielle Yubico FIDO2 SSH : https:// | ||
| - | * Cloud-init reference | + | * Documentation Yubico PIV : https://developers.yubico.com/ |
| + | * Documentation '' | ||
| * Format des certificats SSH (technique) : '' | * Format des certificats SSH (technique) : '' | ||
| Ligne 704: | Ligne 956: | ||
| ===== Checklist de mise en place ===== | ===== Checklist de mise en place ===== | ||
| - | ==== Phase 0 - Préparation | + | ==== Phase 0 - Matériel et logiciels |
| - | * [ ] Yubikeys commandées (minimum | + | * [ ] Yubikey 5 NFC dédiée à la CA commandée |
| - | * [ ] PINs FIDO2 définis sur chaque Yubikey (via Yubikey Manager) | + | * [ ] Yubikeys |
| - | * [ ] Bitwarden ready pour stockage des secrets | + | * [ ] Bitwarden ready avec entrée "MIS SSH CA - Master Backup" |
| + | * [ ] Coffre physique identifié | ||
| + | * [ ] OpenSSH + yubikey-manager + opensc + age installés sur le poste de signature | ||
| ==== Phase 1-2 - CA ==== | ==== Phase 1-2 - CA ==== | ||
| - | * [ ] LXC '' | + | * [ ] PINs PIV de la Yubikey CA configurés (PIN + PUK + management key) |
| - | * [ ] CA users générée + passphrase robuste | + | * [ ] CA principale générée sur Yubikey (slot 9c, ECCP384) |
| - | * [ ] CA hosts générée + passphrase robuste | + | * [ ] Clé publique Yubikey CA exportée ('' |
| - | * [ ] Backup | + | * [ ] Image Docker '' |
| - | * [ ] LXC éteinte par défaut, allumage à la demande | + | * [ ] Emergency |
| + | * [ ] Bitwarden : entrée "MIS SSH CA - Master Backup" | ||
| + | * [ ] Passphrase age écrite sur papier dans coffre bureau MIS | ||
| + | * [ ] Passphrase age (copie) dans coffre backup (maison ou bancaire) | ||
| + | * [ ] Backup | ||
| + | * [ ] **Test de récupération validé** (Étape 2.7) avant rangement définitif | ||
| ==== Phase 3 - Bootstrap parc ==== | ==== Phase 3 - Bootstrap parc ==== | ||
| - | * [ ] Compte break-glass '' | + | * [ ] Compte break-glass '' |
| * [ ] Playbook Ansible '' | * [ ] Playbook Ansible '' | ||
| + | * [ ] Fichier '' | ||
| * [ ] Déploiement progressif sur le parc complet | * [ ] Déploiement progressif sur le parc complet | ||
| - | * [ ] Certificats d' | ||
| ==== Phase 4 - Techniciens ==== | ==== Phase 4 - Techniciens ==== | ||
| Ligne 730: | Ligne 989: | ||
| * [ ] Module Odoo '' | * [ ] Module Odoo '' | ||
| * [ ] Intégration Semaphore pour déploiement KRL | * [ ] Intégration Semaphore pour déploiement KRL | ||
| - | * [ ] Cron de notification d' | + | * [ ] Cron de notification d' |
| - | * [ ] Migration CA vers Yubikey PIV (à 6-12 mois) | + | * [ ] **Drill de récupération annuel** |
| ---- | ---- | ||
| //Document maintenu par Make IT Simple - dernière révision : 2026// | //Document maintenu par Make IT Simple - dernière révision : 2026// | ||
linux/ca-ssh.1780492595.txt.gz · Dernière modification : de root
