ADR-099 — air-ssh/air-sshd : ergonomie client (user@host, DNS, clé fichier, shell interactif) + sous-système de configuration binaire (/etc/air & XDG, édition CLI, export/import JSON)
Statut : Accepté (2026-07-23, décision BDFL). Ouvre le chantier
« ergonomie + configuration » attendu par ADR-095
(qui renvoyait explicitement le jeu de verbes, le format de config cliente et
session/PTY à un « addendum ou RFC »). Fait suite à la phase 3 terminée
(ADR-097) — air-ssh (client+serveur)
est fonctionnellement complet et interopérable OpenSSH dans les deux sens. S’appuie sur
la doctrine de configuration binaire (ADR-073,
ADR-040,
ADR-041,
ADR-033), le précédent de schéma bespoke
(ADR-096), et la note d’orientation
sauvegarde (/etc/air + homes XDG).
Catégorie : Architecture (couche 2, ergonomie + configuration). Évolution structurante → ratification BDFL par RFC (ADR-015). Contient un raffinement de doctrine (ADR-073, §5) à trancher.
Contexte
air-ssh connect fonctionne aujourd’hui avec une CLI de mise au point : --host
IPv4 littérale, --key-seed hexadécimal, --command (exec non interactif). Pour
qu’air-ssh/air-sshd soient utilisables au quotidien et configurables, il
manque :
- Ergonomie client : la syntaxe
user@host, la résolution DNS (noms d’hôte), la lecture d’une clé d’identité depuis un fichier, et le shell interactif (PTY) — c.-à-d.air-ssh connect user@hostdonne un shell. - Configuration : les deux exécutables doivent lire, éditer et échanger une
configuration binaire — système (
/etc/air) pour le serveur, par-utilisateur (XDG) pour le client — puisque la config Air est binaire ([ADR-073]) : pas d’édition « à l’éditeur de texte », un outillage de production/mutation est nécessaire.
Décision
1. Ergonomie client air-ssh (verbes git-style, [ADR-095])
air-ssh connect [user@]host [-p PORT] [-i KEYFILE] [COMMAND…]user@host: parsing SSH usuel ;userpar défaut =$USER;hostpeut être un nom ou une IP littérale.- Résolution DNS : via le résolveur couche 1 existant (
air-socketdns.rs/resolver.rs, sans-IO [ADR-091]) — asynchrone, io_uring. IPv4 d’abord ; IPv6 suit. (Lève la limitation « IPv4 littérale » de C.6.) - Clé d’identité fichier (
-i, sinon la conf cliente §3, sinon~/.ssh/id_ed25519) : lecture d’une clé privée Ed25519 OpenSSH (openssh-key-v1, non chiffrée d’abord ; passphrase différée). Parser fuzzé (entrée non fiable). (La graine hex reste un chemin dev/test.) - Shell interactif (aucune
COMMAND) :pty-req+shell, terminal local en mode raw, pump bidirectionnel (stdin local ↔ canal, canal ↔ stdout local) joint par les combinateurs [ADR-098]. Dépendance serveur : le pump PTY interactif côtéair-sshd(maître PTY ↔ canal, différé en C.3/C.5) doit être livré en parallèle — le PTY est déjà alloué surpty-req(seam existant).
air-ssh keygen: génère une paire Ed25519 d’identité (écrite au format Air ou OpenSSH), pour peupler la conf cliente.air-ssh pubkey(existe) l’exporte en ligneauthorized_keysOpenSSH.
2. Configuration binaire — le modèle commun ([ADR-040]/[ADR-033])
- Format : artefact Cap’n Proto avec enveloppe (version + checksum), comme
toute config Air. Deux domaines de schéma bespoke (précédent [ADR-096]) :
air-sshd-config(serveur : écoute, host keys, privsep/drop_to_user, politique d’autorisation,authorized_keysbackend U.3b) etair-ssh-config(client : hôtes connus, identité par défaut, options par-hôte façonHostd’OpenSSH). - Canonique = binaire, au repos. Le texte n’est jamais la config-au-repos.
3. Emplacements ([ADR-041], note d’orientation sauvegarde)
- Serveur :
/etc/air/sshd.aircfg— config système ;air-configseul écrivain de/etc([ADR-041]) ; permissions restrictives (contient des secrets → régime/etc/shadow0600). - Client : XDG —
${XDG_CONFIG_HOME:-~/.config}/air/ssh.aircfg(par-utilisateur, capturé par la sauvegarde des homes). Clés privées d’identité sous le même arbre, 0600.
4. Édition CLI + export/import JSON (les deux exécutables)
Sous-commande config commune, verbes git-style :
… config get [CHEMIN]— lit un champ (ou tout) de l’artefact binaire.… config set CHEMIN VALEUR— mute l’artefact binaire (validation schéma avant écriture, Principe 4). Le serveur exige le mot de passe administrateur ([ADR-073]).… config export [--json]— projette la config binaire en JSON (texte lisible, round-trippable ; les champs$secretélidés par défaut, comme les diagnostics d’air-config).… config import FICHIER.json— importe un JSON :parse JSON → bind (schéma) → encode Cap'n Proto → écrit l'artefact(pipeline pur validé, réutilisant le compilateurair-config;air-configreste seul écrivain).
5. Raffinement de doctrine [ADR-073] (à trancher)
[ADR-073] fournissait un codec texte read-only « pour les autres, jamais utilisé
par Air pour écrire ». Le présent chantier introduit un import (JSON → binaire) par
l’outil d’Air lui-même. On raffine : le JSON reste un format d’interchange
(inspection/édition hors-ligne), jamais la config-au-repos ; l’import
re-valide et ré-encode vers le binaire canonique (mono-écrivain air-config, ADR-041).
La doctrine « pas de conf en clair au repos » est préservée ; ce qui change est
qu’Air accepte un JSON transitoire comme source d’une mutation validée (au même
titre que la source DSL d’[ADR-033]).
Ratifié BDFL (2026-07-23) — précision normative. Le JSON est exclusivement un
format d’entrée/sortie / import/export servant à mettre à jour les fichiers de
configuration binaires. À l’exécution, air-ssh (client) et air-sshd
(serveur) lisent leur configuration uniquement depuis leur fichier binaire — le
JSON n’est jamais lu au runtime, n’est jamais la config-au-repos, et ne sert
qu’à l’outillage d’édition (config export/import).
6. Sûreté
- Parsers d’entrée non fiable fuzzés : clé privée OpenSSH, JSON d’import, réponses
DNS (le résolveur couche 1 l’est déjà). Aucune
unwrap/indexation paniquante. - Secrets (clés privées, mot de passe admin, champs
$secret) zeroizés des tampons de travail ; élidés des exports JSON par défaut. - Mode raw du terminal restauré en toute sortie (RAII /
Drop), même sur erreur.
Incréments (chaque incrément = 1 PR verte, fuzz sur tout parseur, interop réel)
| Inc. | Contenu | Preuve |
|---|---|---|
| E.0 | Cet ADR (design + plan). | ratifié |
| E.1 | DNS + user@host client : parsing + résolution async (air-socket), air-ssh connect user@host. | air-ssh connect thierry@host (nom résolu) → exec |
| E.2 | Clé fichier : parser openssh-key-v1 Ed25519 (non chiffré) + fuzz ; -i KEYFILE. | clé ~/.ssh/id_ed25519 réelle → auth OK vs sshd |
| E.3 | Shell interactif : pump PTY serveur (maître ↔ canal) + client (raw mode + pump joint). | air-ssh connect user@host → shell interactif (vs air-sshd et OpenSSH) |
| E.4 | Schémas config bespoke air-sshd-config/air-ssh-config (Cap’n Proto) + lecture au démarrage (/etc/air, XDG). | démon/client lisent leur conf binaire |
| E.5 | CLI config get/set + admin password serveur. | mutation binaire validée |
| E.6 | Export/import JSON (config export --json / import). | round-trip binaire↔JSON |
Blocage connu
Les schémas config sont Cap’n Proto : leur génération de code exige l’outil
capnp — ABSENT de l’environnement (même blocage que U.3b, [ADR-096]). E.4→E.6
dépendent d’une génération de schéma = opération mainteneur. E.1→E.3 (ergonomie,
sans nouveau schéma) sont débloquées. (Décision d’ordonnancement : commencer par
E.1→E.3.)
Conséquences
L’ergonomie visée fait d’air-ssh un client utilisable au quotidien, et non
une démonstration de protocole : le passage depuis OpenSSH ne demande pas de
réapprentissage.
Chaque incrément est une PR verte, fuzz sur chaque parseur et interopérabilité réelle — le coût de vérification est donc payé au fil de l’eau, non à la fin.
Alternatives rejetées
- Config en texte clair éditable (YAML/TOML au repos) — viole [ADR-073] (doctrine binaire) ; le JSON n’est ici qu’un interchange transitoire, pas la config-au-repos.
- JSON comme unique format de config — perd l’artefact binaire déterministe + checksum/version + zeroization ([ADR-040]) ; rejeté.
- Exécutable
air-ssh-configdédié — [ADR-095] a déjà tranché contre un binaire « config-only » ; la sous-commandeconfigvit dans chaque exécutable. - Résolveur DNS via libc
getaddrinfo— Air est natif Rust safe ; le résolveur couche 1 sans-IO existe déjà ([ADR-091]). - Clé d’identité = graine hex uniquement — inutilisable au quotidien ; on lit les clés OpenSSH existantes des utilisateurs.
Références
- ADR-095 — packaging
air-ssh(verbes ; renvoie ce chantier à un RFC). - ADR-073 / ADR-040 / ADR-041 / ADR-033 — configuration binaire.
- ADR-096 — schéma bespoke (précédent) + blocage
capnp. - ADR-097 — phase 3 (PTY alloué, pump interactif différé).
- ADR-098 — combinateurs (pump bidirectionnel du shell).
- ADR-091 — sans-IO (résolveur DNS).