Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-134 — CLI de migration de la clé d’hôte air-sshd vers le magasin hostkeys.aircfg (prérequis privsep)

Statut : Accepté (2026-07-29). RFC de structure (ADR-015). Concrétise un fragment d’ADR-121 (provisioning) en s’appuyant sur ADR-108 (air-keystore) et ADR-129 §D3.

Catégorie : outillage d’administration couche 2 (air-sshd). Aucun descellement, aucun tag de sceau.

Contexte — le déploiement 0.3.0 est bloqué

air-sshd 0.3.0 démarre en privsep par défaut. En privsep, le démon refuse (fail-closed, ADR-129 §D3) de lire la clé d’hôte privée depuis les graines host_key_seeds embarquées dans sshd.aircfg : ces graines sont lues par le listener, or la clé privée ne doit jamais s’y trouver. Le privsep exige un magasin hostkeys.aircfg (ADR-108, 0600) désigné par host_key_store_path, que le monitor lit après avoir forké l’enfant.

Les machines déjà déployées (0.2.1) portent leur clé d’hôte en graines dans sshd.aircfg et n’ont pas de hostkeys.aircfg. Il n’existait aucune commande pour créer ce magasin — d’où l’échec du déploiement 0.3.0. Cet ADR comble ce trou sans changer l’identité de l’hôte (pas d’avertissement « host key changed » côté clients).

Décision

Ajouter à air-sshd une sous-commande d’administration qui migre la ou les clés d’hôte existantes du sshd.aircfg vers un magasin hostkeys.aircfg scellé, câble le chemin, et retire les graines du sshd.aircfg.

Nom (ADR-029, explicite) : air-sshd migrate-host-key-store <chemin-absolu> (défaut recommandé : /etc/air/keystore/hostkeys.aircfg).

Comportement, dans l’ordre, fail-closed :

  1. Lit sshd.aircfg (chemin habituel /etc/air/sshd.aircfg, surchargé par l’option de config existante). Décodage éditable (read_sshd_config_editable).
  2. Idempotence / garde :
    • si host_key_store_path est déjà défini et que les graines sont vides ⇒ « déjà migré », succès sans écriture ;
    • si les graines sont vides et pas de store ⇒ erreur (rien à migrer) ;
    • refuse d’écraser un hostkeys.aircfg existant non vide sans confirmation explicite (option --force), pour ne pas détruire un magasin déjà provisionné.
  3. Construit une entrée [AirHostKey] par graine : AirSshKeyPair::from_seed(seed, comment), created_at = maintenant, not_before = 0, not_after = u64::MAX (pas de péremption — on préserve le comportement des graines, qui n’en avaient pas ; la rotation relève d’ADR-121, hors périmètre).
  4. air_keystore::hostkeys::save(<chemin>, &entrées) — écrit l’artefact 0600 (StoreVisibility::Secret, écriture atomique, graine zeroizee après usage). Le magasin appartient à root (le monitor le lit en root pré-fork ; ADR-124 §D4).
  5. settings.host_key_store_path = Some(<chemin>) et settings.host_key_seeds.clear() — la clé privée quitte le sshd.aircfg lisible par le listener. C’est le point de sécurité de la migration.
  6. Réécrit sshd.aircfg (write_sshd_config, enveloppe AIRCFGv1).

Invariant préservé : la même clé d’hôte (mêmes graines ⇒ même paire Ed25519) ⇒ même empreinte publique ⇒ aucun client ne voit l’hôte changer.

Portée

Couche 2 (air-sshd), plus une dépendance déjà présente (air-keystore). Aucun descellement, aucun sceau. La création du compte système _air-sshd (autre prérequis privsep) reste hors de cet ADR — provisionnée à la main au déploiement, postinst du .deb tracé comme amélioration packaging séparée (ADR-121).

Preuves exigées (rouge sans le correctif)

  1. Round-trip : migrer un sshd.aircfg à graines ⇒ hostkeys.aircfg 0600 décodable (hostkeys::load) portant la même clé publique ; sshd.aircfg réécrit a host_key_store_path défini et graines vides.
  2. Le démon démarre : après migration, air-sshd en privsep démarre (là où il fail-closait avant) — test root de bout en bout (listener→monitor→enfant→worker).
  3. Idempotence : re-migrer ⇒ « déjà migré », pas d’écrasement ; --force exigé pour remplacer un magasin non vide.
  4. Fail-closed : graines absentes ⇒ erreur ; échec d’écriture du magasin ⇒ le sshd.aircfg n’est pas modifié (pas de config à moitié migrée).
  5. Identité préservée : empreinte publique identique avant/après.

Conséquences

La migration de la clé d’hôte vers le magasin scellé débloque le déploiement, et aligne air-sshd sur la doctrine de configuration binaire.

Un outil de migration devient nécessaire : les installations existantes ne peuvent pas basculer sans reprise explicite de leur clé.

Alternatives rejetées

  • Générer une clé neuve : simple, mais change l’identité de l’hôte ⇒ avertissement client sur toute la flotte déjà déployée. Refusé pour une migration.
  • Désactiver le privsep : renierait la raison d’être de 0.3.0.