Architektur

Architektur und Design

Warum Rust

SSH-Frontière ist aus drei Gründen in Rust geschrieben:

  1. Speichersicherheit: kein Buffer Overflow, kein Use-after-free, kein Null-Pointer. Für eine Sicherheitskomponente, die als Login-Shell läuft, ist das entscheidend.

  2. Statisches Binary: kompiliert mit dem Target x86_64-unknown-linux-musl (andere Targets möglich ohne Funktionsgarantie), das Binary ist ~1 MB groß und hat keine Systemabhängigkeit. Auf den Server kopieren und fertig.

  3. Performance: das Programm startet, validiert, führt aus und beendet sich in Millisekunden. Kein Runtime, kein Garbage Collector, kein JIT.

Synchron und kurzlebig

SSH-Frontière ist ein synchrones One-Shot-Programm. Kein Daemon, kein Async, kein Tokio.

Der Lebenszyklus ist einfach:

  1. sshd authentifiziert die SSH-Verbindung per Schlüssel
  2. sshd forkt und führt ssh-frontiere als Login-Shell aus
  3. ssh-frontiere validiert und führt den Befehl aus
  4. Der Prozess beendet sich

Jede SSH-Verbindung erzeugt einen neuen Prozess. Kein geteilter Zustand zwischen Verbindungen, keine Nebenläufigkeitsprobleme.

Codestruktur

Der Code ist in Module mit klaren Verantwortlichkeiten organisiert:

ModulVerantwortlichkeit
main.rsEinstiegspunkt, Argument-Flattening, Orchestrator-Aufruf
orchestrator.rsHauptfluss: Banner, Header, Befehl, Antwort, Sitzungsschleife
config.rsTOML-Konfigurationsstrukturen, Fail-fast-Validierung
protocol.rsHeader-Protokoll: Parser, Banner, Auth, Sitzung, Body
crypto.rsSHA-256 (FIPS 180-4 Implementierung), Base64, Nonce, Challenge-Response
dispatch.rsBefehlsparsing (Anführungszeichen, key=value), Auflösung, RBAC
chain_parser.rsBefehlsketten-Parser (Operatoren ;, &, |)
chain_exec.rsKettenausführung: strikte Sequenz (;), permissiv (&), Fallback (|)
discovery.rshelp- und list-Befehle: Domänen- und Aktionsentdeckung
logging.rsStrukturiertes JSON-Logging, Maskierung sensibler Argumente
output.rsJSON-Antwort, Exit-Codes
lib.rsStellt crypto für das Proof-Binary und Fuzz-Helpers bereit

Jedes Modul hat seine Testdatei (*_tests.rs) im selben Verzeichnis.

Ein Hilfsbinary proof (src/bin/proof.rs) berechnet Authentifizierungs-Proofs für E2E-Tests und Client-Integration.

Header-Protokoll

SSH-Frontière verwendet ein Textprotokoll über stdin/stdout. Die Präfixe unterscheiden sich je nach Richtung:

Client zum Server (stdin):

PräfixRolle
+ Konfiguriert: Direktiven (auth, session, body)
# Kommentiert: vom Server ignoriert
(Klartext)Befehl: Domäne Aktion [Argumente]
. (allein in einer Zeile)Blockende: beendet einen Befehlsblock

Server zum Client (stdout):

PräfixRolle
#> Kommentiert: Banner, informative Nachrichten
+> Konfiguriert: Capabilities, Challenge-Nonce
>>> Antwortet: finale JSON-Antwort
>> Stdout: Standard-Ausgabe im Streaming (ADR 0011)
>>! Stderr: Fehlerausgabe im Streaming

Verbindungsablauf

CLIENT                                  SERVER
  |                                        |
  |  <-- Banner + Capabilities ----------  |   #> ssh-frontiere 0.1.0
  |                                        |   +> capabilities rbac, session, help, body
  |                                        |   +> challenge nonce=a1b2c3...
  |                                        |   #> type "help" for available commands
  |                                        |
  |  --- +auth (optional) ------------->   |   + auth token=runner-ci proof=deadbeef...
  |  --- +session (optional) ---------->   |   + session keepalive
  |                                        |
  |  --- Befehl (Klartext) ------------>   |   forgejo backup-config
  |  --- Blockende -------------------->   |   .
  |  <-- Streaming stdout -------------   |   >> Backup completed
  |  <-- Finale JSON-Antwort ----------   |   >>> {"status_code":0,"status_message":"executed",...}
  |                                        |
  |  (bei Session Keepalive)               |
  |  --- Befehl 2 --------------------->   |   infra healthcheck
  |  --- Blockende -------------------->   |   .
  |  <-- JSON-Antwort 2 --------------   |   >>> {"status_code":0,...}
  |  --- Sitzungsende (leerer Block) ->   |   .
  |  <-- Session closed ---------------   |   #> session closed

JSON-Antwort

Jeder Befehl erzeugt eine finale JSON-Antwort in einer einzelnen Zeile, mit dem Präfix >>>. Standardausgabe und Fehler werden per Streaming über >> und >>! gesendet:

>> Backup completed
>>> {"command":"forgejo backup-config","status_code":0,"status_message":"executed","stdout":null,"stderr":null}

Body-Protokoll

Der Header +body ermöglicht das Übertragen mehrzeiliger Inhalte an den Kindprozess über stdin. Vier Begrenzungsmodi:

TOML-Konfiguration

Das Konfigurationsformat ist deklaratives TOML. Die Wahl ist in ADR 0001 dokumentiert:

Die Konfiguration wird beim Laden validiert (Fail-fast): TOML-Syntax, Vollständigkeit der Felder, Platzhalter-Konsistenz, mindestens eine Domäne, mindestens eine Aktion pro Domäne, nicht-leere Enum-Werte.

Abhängigkeitspolitik

SSH-Frontière verfolgt eine Politik von null nicht-essentiellen Abhängigkeiten. Jede externe Crate muss durch einen echten Bedarf begründet sein.

Aktuelle Abhängigkeiten

3 direkte Abhängigkeiten, ~20 transitive Abhängigkeiten:

CrateVerwendung
serde + serde_jsonJSON-Serialisierung (Logging, Antworten)
tomlTOML-Konfiguration laden

Bewertungsmatrix

Vor dem Hinzufügen einer Abhängigkeit wird sie anhand von 8 gewichteten Kriterien bewertet (Note /5): Lizenz (eliminatorisch), Governance (×3), Community (×2), Aktualisierungshäufigkeit (×2), Größe (×3), transitive Abhängigkeiten (×3), Funktionalitäten (×2), Nicht-Einschluss (×1). Mindestpunktzahl: 3,5/5.

Audit

Wie das Projekt entworfen wurde

SSH-Frontière wurde in aufeinanderfolgenden Phasen (1 bis 9, mit Zwischenphasen 2.5 und 5.5) entwickelt, gesteuert von Claude Code Agenten mit systematischer TDD-Methodik:

PhaseInhalt
1Funktionaler Dispatcher, TOML-Config, 3-stufiges RBAC
2Produktionskonfiguration, Betriebsskripte
2.5SHA-256 FIPS 180-4, BTreeMap, Graceful Timeout
3Einheitliches Header-Protokoll, Challenge-Response-Auth, Sitzungen
4E2E-SSH-Docker-Tests, Code-Bereinigung, Forge-Integration
5Sichtbarkeits-Tags, horizontale Token-Filterung
5.5Optionaler Nonce, benannte Argumente, Proof-Binary (inkl. Phase 6, zusammengeführt)
7Konfigurationsguide, Dry-Run --check-config, Help ohne Präfix
8Strukturierte Fehlertypen, Pedantic Clippy, cargo-fuzz, Proptest
9Body-Protokoll, freie Argumente, max_body_size, Exit-Code 133

Das Projekt wurde entworfen von:

Wo Mensch und Maschine zusammenarbeiten, besser, schneller, mit mehr Sicherheit.