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-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@host donne 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 ; user par défaut = $USER ; host peut être un nom ou une IP littérale.
    • Résolution DNS : via le résolveur couche 1 existant (air-socket dns.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é sur pty-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 ligne authorized_keys OpenSSH.

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_keys backend U.3b) et air-ssh-config (client : hôtes connus, identité par défaut, options par-hôte façon Host d’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-config seul écrivain de /etc ([ADR-041]) ; permissions restrictives (contient des secrets → régime /etc/shadow 0600).
  • 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 VALEURmute 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.jsonimporte un JSON : parse JSON → bind (schéma) → encode Cap'n Proto → écrit l'artefact (pipeline pur validé, réutilisant le compilateur air-config ; air-config reste 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.ContenuPreuve
E.0Cet ADR (design + plan).ratifié
E.1DNS + user@host client : parsing + résolution async (air-socket), air-ssh connect user@host.air-ssh connect thierry@host (nom résolu) → exec
E.2Clé fichier : parser openssh-key-v1 Ed25519 (non chiffré) + fuzz ; -i KEYFILE.clé ~/.ssh/id_ed25519 réelle → auth OK vs sshd
E.3Shell 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.4Sché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.5CLI config get/set + admin password serveur.mutation binaire validée
E.6Export/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-config dédié — [ADR-095] a déjà tranché contre un binaire « config-only » ; la sous-commande config vit 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).