アーキテクチャ
アーキテクチャと設計
なぜRustなのか
SSH-FrontièreがRustで書かれている理由は3つあります:
-
メモリ安全性:バッファオーバーフロー、ユーズアフターフリー、ヌルポインタなし。ログインシェルとして動作するセキュリティコンポーネントにとって、これは重要です。
-
静的バイナリ:
x86_64-unknown-linux-muslターゲット(他のターゲットも可能ですが、機能は保証されません)でコンパイルすると、バイナリは約1 MBでシステム依存関係がありません。サーバーにコピーするだけで準備完了です。 -
パフォーマンス:プログラムは起動、検証、実行、終了をミリ秒で行います。ランタイム、ガベージコレクター、JITはありません。
同期的かつ一時的
SSH-Frontièreは同期的でワンショットのプログラムです。デーモンなし、非同期なし、Tokioなし。
ライフサイクルはシンプルです:
sshdが鍵でSSH接続を認証sshdがフォークしてssh-frontiereをログインシェルとして実行ssh-frontiereがコマンドを検証して実行- プロセスが終了
各SSH接続は新しいプロセスを作成します。接続間の共有状態なし、並行性の問題なし。
コード構造
コードは明確な責務を持つモジュールで構成されています:
| モジュール | 責務 |
|---|---|
main.rs | エントリポイント、引数のフラット化、オーケストレーター呼び出し |
orchestrator.rs | メインフロー:バナー、ヘッダ、コマンド、レスポンス、セッションループ |
config.rs | TOML設定構造体、フェイルファスト検証 |
protocol.rs | ヘッダプロトコル:パーサー、バナー、認証、セッション、ボディ |
crypto.rs | SHA-256(FIPS 180-4実装)、base64、ノンス、チャレンジ・レスポンス |
dispatch.rs | コマンドパース(引用符、key=value)、解決、RBAC |
chain_parser.rs | コマンドチェーンパーサー(演算子;、&、|) |
chain_exec.rs | チェーン実行:厳密な順序(;)、寛容(&)、フォールバック(|) |
discovery.rs | helpとlistコマンド:ドメインとアクションの発見 |
logging.rs | 構造化JSONログ、機密引数のマスキング |
output.rs | JSONレスポンス、終了コード |
lib.rs | proofバイナリとfuzzヘルパー用にcryptoを公開 |
各モジュールには同じディレクトリにテストファイル(*_tests.rs)があります。
補助的なproofバイナリ(src/bin/proof.rs)は、E2Eテストとクライアント統合のための認証プルーフを計算します。
ヘッダプロトコル
SSH-Frontièreはstdin/stdout上のテキストプロトコルを使用します。プレフィックスは方向によって異なります:
クライアントからサーバー(stdin):
| プレフィックス | 役割 |
|---|---|
+ | 設定:ディレクティブ(auth、session、body) |
# | コメント:サーバーに無視される |
| (プレーンテキスト) | コマンド:ドメイン アクション [引数] |
.(行に単独で記載) | ブロック終了:コマンドブロックを終了 |
サーバーからクライアント(stdout):
| プレフィックス | 役割 |
|---|---|
#> | コメント:バナー、情報メッセージ |
+> | 設定:機能、チャレンジノンス |
>>> | レスポンス:最終JSONレスポンス |
>> | 標準出力:標準出力のストリーミング(ADR 0011) |
>>! | 標準エラー:エラー出力のストリーミング |
接続フロー
クライアント サーバー
| |
| <-- バナー + 機能 ---------------- | #> ssh-frontiere 0.1.0
| | +> capabilities rbac, session, help, body
| | +> challenge nonce=a1b2c3...
| | #> type "help" for available commands
| |
| --- +auth(任意) ----------------> | + auth token=runner-ci proof=deadbeef...
| --- +session(任意) -------------> | + session keepalive
| |
| --- コマンド(プレーンテキスト) --> | forgejo backup-config
| --- ブロック終了 -----------------> | .
| <-- stdout ストリーミング -------- | >> Backup completed
| <-- 最終JSONレスポンス ---------- | >>> {"status_code":0,"status_message":"executed",...}
| |
| (session keepaliveの場合) |
| --- コマンド2 --------------------> | infra healthcheck
| --- ブロック終了 -----------------> | .
| <-- JSONレスポンス2 ------------- | >>> {"status_code":0,...}
| --- セッション終了(空ブロック) -> | .
| <-- セッション終了 --------------- | #> session closed
JSONレスポンス
各コマンドは>>>プレフィックス付きの1行の最終JSONレスポンスを生成します。標準出力とエラーは>>と>>!でストリーミング送信されます:
>> Backup completed
>>> {"command":"forgejo backup-config","status_code":0,"status_message":"executed","stdout":null,"stderr":null}
- 最終JSONレスポンスの
stdout/stderr=null:出力は>>と>>!でストリーミング送信済み - 実行されないコマンド(拒否、設定エラー)の場合も、
stdoutとstderrはnull
ボディプロトコル
+bodyヘッダにより、stdin経由で子プロセスに複数行のコンテンツを送信できます。4つの区切りモード:
+body:.(ドット)のみを含む行まで読み取り+body size=N:正確にNバイトを読み取り+body stop="DELIMITER":デリミタを含む行まで読み取り+body size=N stop="DELIMITER":最初に到達した区切り(サイズまたはマーカー)で読み取り終了
TOML設定
設定フォーマットは宣言的TOMLです。ADR 0001で文書化された選択理由:
- なぜTOMLか:人間が読みやすい、ネイティブな型付け、Rustエコシステムの標準、意味のあるインデントなし(YAMLと異なり)、設定用としてJSONより表現力が高い。
- なぜYAMLではないか:意味のあるインデントがエラーを起こしやすい、危険な暗黙の型変換(
on/off→ boolean)、複雑な仕様。 - なぜJSONではないか:コメントなし、冗長、人間の設定用に設計されていない。
設定は読み込み時に検証されます(フェイルファスト):TOML構文、フィールドの完全性、プレースホルダの整合性、少なくとも1つのドメイン、ドメインごとに少なくとも1つのアクション、空でないenum値。
依存関係ポリシー
SSH-Frontièreは不要な依存関係ゼロのポリシーを持っています。各外部クレートは実際のニーズによって正当化されなければなりません。
現在の依存関係
直接依存関係3つ、推移的依存関係約20:
| クレート | 用途 |
|---|---|
serde + serde_json | JSONシリアライゼーション(ログ、レスポンス) |
toml | TOML設定の読み込み |
評価マトリックス
依存関係を追加する前に、8つの加重基準(5点満点)で評価されます:ライセンス(不適格の場合は排除)、ガバナンス(×3)、コミュニティ(×2)、更新頻度(×2)、サイズ(×3)、推移的依存関係(×3)、機能(×2)、非ロックイン(×1)。最低スコア:3.5/5。
監査
cargo denyがライセンスと既知の脆弱性をチェックcargo auditがRustSecデータベースで脆弱性を検索- 認可されたソース:crates.ioのみ
プロジェクトの設計経緯
SSH-Frontièreは連続するフェーズ(1〜9、中間フェーズ2.5と5.5を含む)で開発され、体系的なTDD手法によりClaude Codeエージェントが主導しました:
| フェーズ | 内容 |
|---|---|
| 1 | 機能的なディスパッチャー、TOML設定、3段階RBAC |
| 2 | 本番設定、運用スクリプト |
| 2.5 | SHA-256 FIPS 180-4、BTreeMap、グレースフルタイムアウト |
| 3 | 統合ヘッダプロトコル、チャレンジ・レスポンス認証、セッション |
| 4 | E2E SSH Dockerテスト、コードクリーンアップ、forge統合 |
| 5 | 可視性タグ、水平的トークンフィルタリング |
| 5.5 | オプションノンス、名前付き引数、proofバイナリ(フェーズ6を含む、統合) |
| 7 | 設定ガイド、ドライラン--check-config、プレフィックスなしhelp |
| 8 | 構造化エラー型、ペダンティックclipy、cargo-fuzz、proptest |
| 9 | ボディプロトコル、自由引数、max_body_size、終了コード133 |
プロジェクトの設計者:
- Julien Garderon(BO):コンセプト、機能仕様、Rustの選択、プロジェクト名
- Claudeスーパーバイザー(PM/テックリード):技術分析、アーキテクチャ
- Claude Codeエージェント:実装、テスト、ドキュメント
人間と機械が共に、より良く、より速く、より高いセキュリティで協働する場所。