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-129 — Conception de V2.5 : transition post-auth (worker + proxy chiffré, options, from=)

Statut : Accepté (conception, 2026-07-29) — détaille le comment de la transition post-auth du privsep air-sshd, dont le quoi est ratifié par ADR-122 §5 et ADR-124 (D1/D6/§D4). Companion : ADR-128 (options), ADR-127 (AirTemporaryIdentity), ADR-112 (air-sandbox), ADR-069 (SCM_RIGHTS). Un point sécurité nouveau est remonté au BDFL (§Points remontés).

Catégorie : Conception d’architecture couche 2 (air-sshd). Aucun code : fixe la mécanique de V2.5 et son découpage en sous-incréments V2.5a/b/c avant toute implémentation.

Contexte — ce que V2.5 doit livrer

Après un AUTHORIZE accordé (V2.4 : le monitor rend AuthorizeAllow{forced_command, permissions}), le privsep doit servir la session sans jamais donner à l’étage confiné les privilèges du compte cible. Restent, hérités des incréments précédents :

  • Transition post-auth par proxy (ADR-124 §D6, non implémentée) : l’enfant pré-auth garde Transport<S> (état de chiffrement) et devient proxy ; un worker setuid parle ssh-connection en clair.
  • Application des options (ADR-128) : command=, no-pty, no-*-forwarding… sont décodées/résolues mais rien ne les exécute — c’est le worker.
  • Corollaire §D4 : l’enfant a besoin de K_S public (pour son KEXINIT/reply), que le proto RPC V2.1 ne transmet pas.
  • from= : reporté par ADR-128 (exige l’adresse du pair de confiance).
  • Contrat mono-fil d’AirTemporaryIdentity (ADR-127, avertissement A2) : à honorer au câblage du démon.

Décisions de conception

D1 — Le monitor forke le worker post-auth ; cage selon le service

À l’ALLOW final, le monitor (root borné) fork_process un worker : AirPrivilegeDrop::for_user(compte)drop_privileges (setuid irréversible vers le compte cible). La cage dépend du service demandé (ADR-122 §7) :

  • shell / exec : drop-only (largage de privilèges, sans cage seccomp/Landlock — un shell interactif a besoin d’une surface syscall large ; le confinement est l’identité non-root + no_new_privs) ;
  • SFTP : cage complète sftp-worker (seccomp sftp-worker + Landlock du sous-arbre autorisé), posée avant de servir le moindre octet SFTP.

Le type de service n’est connu qu’après l’auth (requête de canal ssh-connection). Le worker démarre donc drop-only, lit la première requête de canal/subsystem, puis pose la cage sftp-worker si et seulement si c’est du SFTP, avant tout traitement de données.

D2 — Transition par proxy chiffré + passage de fd par SCM_RIGHTS

L’enfant pré-auth conserve Transport<S> et devient un proxy : réseau→clair vers le worker, clair→réseau. Le worker ne voit jamais de clé de session (ADR-124 §D6).

Mécanique du tuyau clair : le monitor crée un socketpair(AF_UNIX, SOCK_STREAM) ; il en donne un bout au worker (hérité au fork), et envoie l’autre bout à l’enfant pré-auth par SCM_RIGHTS sur le canal RPC déjà établi (ADR-069, disponible). L’enfant relaie alors entre le réseau (via Transport) et ce tuyau clair. Le worker n’a pas le socket réseau ; l’enfant n’a aucun privilège.

D3 — K_S public : hérité du listener, jamais le privé dans l’enfant

L’enfant reçoit K_S public par héritage du listener (matérialisé avant le fork de l’enfant ; une clé publique n’est pas un secret). La clé privée reste chargée dans le monitor, post-fork enfant (§D4 amendé). Pas de message HOSTKEY ajouté au proto : l’héritage du public suffit et évite un aller-retour RPC.

D4 — from= : adresse du pair fournie par le LISTENER, jamais par l’enfant

from= est ajouté au schéma (fromPatterns, champ additif @id neuf sur AuthorizedKeyOptions — un magasin sans lui = « aucune restriction de source »). L’enforcement vit dans le monitor, contre l’adresse du pair que le LISTENER a obtenue à l’accept() et transmise au monitor (héritée au fork du monitor). Le monitor n’utilise JAMAIS une adresse fournie par l’enfant (un enfant compromis mentirait pour satisfaire from=). Aucun champ d’adresse n’est donc ajouté à AuthorizeRequest : le monitor la détient déjà, de source fiable. La CLI cesse de refuser from= une fois ce chemin en place.

D5 — Application des options par le worker

Le worker reçoit AuthorizeConstraint{forced_command, permissions} (hérité au fork) et l’applique dans le traitement ssh-connection : command= ⇒ exécute la commande forcée (en ignorant celle demandée) ; no-pty ⇒ refuse pty-req ; no-{port,agent,X11}-forwarding ⇒ refuse les canaux/requêtes de forwarding correspondants ; no-user-rc ⇒ pas de ~/.ssh/rc. Fail-closed : un bit de permission non reconnu a déjà fait rejeter la réponse (ADR-128).

D6 — Contrat mono-fil honoré au câblage

Le monitor reste une boucle bloquante mono-fil (ADR-124 §D2) ; AirTemporaryIdentity (!Send, ADR-127) y est créée/détruite synchroniquement. Le câblage du démon ne place pas le monitor dans un exécuteur async multi-fil. À vérifier par construction.

Découpage en sous-incréments

  • V2.5a — Socle de transition : K_S public hérité (D3) ; socketpair clair + passage SCM_RIGHTS au proxy (D2) ; fork du worker (setuid, drop-only) ; round-trip clair prouvé de bout en bout (un octet traverse réseau→proxy→worker→proxy→réseau), sans vraie session. Tests root.
  • V2.5b — Service + options : le worker parle réellement ssh-connection (shell/exec/SFTP) sur le tuyau clair ; cage sftp-worker posée pour SFTP (D1) ; application des options (D5). Vraie session ssh/sftp de bout en bout via le proxy.
  • V2.5c — from= : champ schéma additif (D4) ; adresse pair de confiance listener→monitor ; enforcement au monitor ; CLI accepte from=.

Chaque sous-incrément = branche + PR + revue (air-security-reviewer) + CI + barrière.

Points remontés au BDFL

  • Modèle de confiance de from= (D4) : décidé « adresse fournie par le listener, jamais par l’enfant ». C’est le seul choix sûr (un enfant compromis mentirait), mais il suppose que le listener conserve/transmette l’adresse du pair jusqu’au monitor — à confirmer comme contrainte d’architecture du câblage démon (V2.5c). Aucun autre point nouveau : D1-D3/D5/D6 découlent d’ADR-122/124 déjà ratifiés.

Conséquences

La transition post-authentification devient un point de bascule explicite : avant, l’enfant est confiné et sans secret ; après, le worker sert la session sous l’identité de l’utilisateur.

Le proxy chiffré ajoute une copie sur le chemin de données, contrepartie assumée de l’isolation.

Alternatives rejetées

  • Transfert de l’état de chiffrement au worker (re-exec façon OpenSSH). Rejeté (ADR-124 §D6) : sérialiser l’état cipher à travers un privilège est fragile ; le proxy garde les clés dans l’enfant confiné.
  • Message HOSTKEY dans le proto pour K_S public. Rejeté (D3) : l’héritage du public suffit, sans aller-retour ni surface de proto en plus.
  • Adresse du pair dans AuthorizeRequest (fournie par l’enfant). Rejeté (D4) : un enfant compromis falsifierait from=. La source fiable est le listener.
  • Cage seccomp complète pour le shell. Rejeté (ADR-122 §7) : un shell interactif exige une surface syscall trop large ; drop-only + non-root + no_new_privs est la posture retenue.

Suite

  • Sur confirmation du point from= (D4) : démarrer V2.5a. Enregistrement SUMMARY/registre/ suivi. Clôture V2.5b/c + V2.6 (durcissement + campagne de tests de sécurité) = clôture vague 1 → cycle de livraison.