Bundled moduli の再生成¶
SIMNOS は Diffie-Hellman Group Exchange (DH-GEX) 用の moduli ファイルを
simnos/plugins/servers/moduli に同梱しています。これにより、system
moduli ファイル (/etc/ssh/moduli) を持たないホスト (主に Windows /
macOS) でも paramiko ベースの SSH サーバが GEX 鍵交換をサポートできます。
このページでは、このファイルが存在する理由、再生成のタイミング、および
正確な手順を文書化します。
なぜ SIMNOS が moduli を同梱するのか¶
paramiko 自身は moduli ファイルを同梱していません。/etc/ssh/moduli または
/usr/local/etc/moduli をシステムが提供することを期待していますが、
Windows / macOS ホストは通常これを持ちません。moduli ファイルが無い場合、
paramiko の GEX server-mode は stale-snapshot bug (paramiko issue
2126) に遭遇するため、
SIMNOS は workaround として GEX アルゴリズムを全て server-side で disable
します。これに paramiko 5.0 の SHA-1 KEX 削除が組み合わさると、
netmiko.fortinet.FortinetSSH のような SHA-1-leaning legacy client と
空の KEX overlap が発生し、Windows / macOS で deterministic な接続失敗を
引き起こします。
自前で moduli ファイルを同梱することで、これらの platform でも server が
gex-sha256 を advertise できるようになり、SHA-1 KEX を復活させずに接続性を
回復します。詳細な root-cause 解析は paramiko issue 2126 への
follow-up コメント
を参照してください。
なぜ paramiko 同梱の moduli を使わないのか?¶
paramiko は moduli を同梱していません (検証済: 当該 paramiko package 内に
moduli ファイルは存在しない)。自前で同梱することで、OpenSSH サーバが
installed されていない platform でも SIMNOS が自己完結します。
なぜ OpenSSH の openssh-portable から直接 copy しないのか?¶
可能ではあります (license は BSD 系で SIMNOS の MIT と互換) が、自前で
ssh-keygen で生成することで以下の利点があります:
- 生成日時が記録される (ssh-keygen の
# Timeヘッダで自動記録) - rotation 責任が明確 (再生成タイミングを SIMNOS 側で制御可能)
- OpenSSH の release cadence から独立
コストは maintainer マシンでの 1 回限りの数時間の生成だけです。
moduli は公開情報¶
DH prime は SSH protocol で KEX 時に毎回 client へ送信されるため、秘密
情報ではありません。OpenSSH 自身も
openssh-portable
public リポジトリで moduli を公開しています。SIMNOS が moduli を GitHub
public repo に同梱することは同じ慣習であり、security implication はあり
ません。
Rotation policy¶
- 3 年ごと (現 bundle の次回推奨日: 2029-05、ファイル各行の
先頭カラムにある ssh-keygen 生成タイムスタンプ (
YYYYMMDDHHMMSS形式) + 3 年で計算) - Ad-hoc: 2048-bit 以上の DH-GEX に対する新規 logjam-class precomputation 攻撃が報告された場合
Rotation 頻度は意図的に緩めに設定しています — 2048-bit 以上の DH prime は precomputation 攻撃の実用範囲外ですし、SIMNOS は production SSH server というより test simulator としての用途が中心だからです。3 年という baseline は主要 Linux distribution の典型的な refresh 周期に揃えてあります。
手順¶
Linux maintainer machine で実行してください。-M screen step は CPU-bound
かつ single-threaded で、物理 host では合計数時間、VM 環境ではさらに長
時間かかる場合があります。そのため 3072-bit と 4096-bit の screen 処理は
split -n l/N で candidates ファイルを N 分割し、複数 ssh-keygen process
を並列に走らせるアプローチを取ります。ファイルは 1 度生成して commit
するだけで、CI で再生成することはありません。
現在の bundle には 2048-bit, 3072-bit, 4096-bit の primes が含まれます。
4096-bit batch は Windows host で生成しています (下記の
Alternative: Windows PowerShell 節
を参照)。理由は、2048/3072 を生成した VM host では ssh-keygen -M screen
の 4096-bit 処理が非現実的に遅かったためです。
# 1. 各 bit size の候補生成 (高速、数秒〜数分)
ssh-keygen -M generate -O bits=2048 moduli-2048.candidates
ssh-keygen -M generate -O bits=3072 moduli-3072.candidates
ssh-keygen -M generate -O bits=4096 moduli-4096.candidates
# 2a. 2048-bit candidates を 1 process で screening (約 30 分〜3 時間)
ssh-keygen -M screen -f moduli-2048.candidates moduli-2048
# 2b. 3072-bit candidates を分割し、複数 ssh-keygen を並列実行
# (利用可能 CPU 数に合わせて N (下記は 8) を調整。Linux なら
# `nproc` で物理 / 仮想コア数を確認可能)
split -n l/8 moduli-3072.candidates moduli-3072.chunk.
for chunk in moduli-3072.chunk.*; do
(ssh-keygen -M screen -f "$chunk" "${chunk}.screened") &
done
wait
cat moduli-3072.chunk.*.screened > moduli-3072
# 2c. 4096-bit candidates も同じ並列パターン (`N` のチューニングは 2b の
# `nproc` 注釈を参照)。VM 環境での single-process screening は
# 10 時間以上かかるので並列化を強く推奨
# (bare-metal 8-core ならおよそ 1 時間で完了)
split -n l/8 moduli-4096.candidates moduli-4096.chunk.
for chunk in moduli-4096.chunk.*; do
(ssh-keygen -M screen -f "$chunk" "${chunk}.screened") &
done
wait
cat moduli-4096.chunk.*.screened > moduli-4096
# 3. 同梱ファイルとして concatenate
# (ファイル名は拡張子なし、OpenSSH 慣習に揃える)
cat moduli-2048 moduli-3072 moduli-4096 > simnos/plugins/servers/moduli
# 4. 中間ファイル cleanup (`-f` で部分実行時の missing ファイルを許容)
rm -f moduli-*.candidates moduli-2048 moduli-3072 moduli-4096 \
moduli-3072.chunk.* moduli-3072.chunk.*.screened \
moduli-4096.chunk.* moduli-4096.chunk.*.screened
# 5. 検証 (合計で数百〜数千行になるはず)。各行は YYYYMMDDHHMMSS 形式の
# ssh-keygen 生成タイムスタンプから始まる、例:
# `20260516054136 2 6 100 2047 2 D5AC...`
wc -l simnos/plugins/servers/moduli
head -1 simnos/plugins/servers/moduli
これらのコマンドは cwd にのみ書き込み、system の /etc/ssh/moduli には
触れません。root 権限も不要です。
Alternative: Windows PowerShell¶
Maintainer の Linux host が VM の場合、4096-bit の screening は 10 時間 以上かかることがあります。Windows host 上で PowerShell 7 と Windows 同梱の OpenSSH client を直接使うと VM オーバーヘッドを回避でき、bare-metal 8-core マシンなら 4096-bit でもおよそ 1 時間で完了します。
# 任意の作業 directory で実行可能 (cwd にのみ書き込み)。
# 要件: Windows 10/11 + OpenSSH Client 有効 + PowerShell 7+
# (ForEach-Object -Parallel は PowerShell 5.1 では使えない)
# 1. candidates 生成 (single-thread、4096-bit で約 5〜15 分)
ssh-keygen -M generate -O bits=4096 candidates-4096.txt
# 2. candidates を 8 分割 (実 core 数に合わせて 8 を調整)
# `$lines[$start..$end]` ではなく `Select-Object -Skip / -First` を
# 使うのは、PowerShell の `..` 演算子は $start > $end のとき空配列で
# はなく逆順を返すため、小さい入力で行の重複を silent に引き起こすから
$lines = Get-Content candidates-4096.txt
$chunkSize = [Math]::Ceiling($lines.Count / 8)
0..7 | ForEach-Object {
$start = $_ * $chunkSize
$lines | Select-Object -Skip $start -First $chunkSize |
Set-Content "candidates-4096.chunk$_.txt"
}
# 3. 並列 screening (-ThrottleLimit は step 2 の chunk 数と一致させる)。
# 注意: ForEach-Object -Parallel は ssh-keygen の失敗を silent に飲み込む。
# merge 前に各 `moduli-4096.chunk*.txt` が non-empty か必ず確認すること
# (例: `Get-ChildItem moduli-4096.chunk*.txt | Where-Object Length -eq 0`)
0..7 | ForEach-Object -Parallel {
ssh-keygen -M screen -f "candidates-4096.chunk$_.txt" "moduli-4096.chunk$_.txt"
} -ThrottleLimit 8
# 4. chunk を merge。Windows の Set-Content は CRLF で書くため
# moduli (LF 必須) に直すために [IO.File]::WriteAllText を使う
$content = (0..7 | ForEach-Object { Get-Content "moduli-4096.chunk$_.txt" -Raw }) -join ""
$content = $content -replace "`r`n", "`n"
[IO.File]::WriteAllText((Join-Path $pwd 'moduli-4096-lf.txt'), $content)
# 5. moduli-4096-lf.txt を Linux maintainer machine に転送して
# 既存の 2048/3072 bundle に追記:
# cat moduli-4096-lf.txt >> simnos/plugins/servers/moduli
Windows 側の ssh-keygen は -M generate / -M screen フラグを Unix 版と
共通仕様で備えているため出力フォーマットは完全に同一で、Linux で生成
した batch に直接 concat できます。
wheel に moduli が含まれていることの verify¶
新しいファイルを commit したら、local で wheel をビルドし、moduli ファイル が含まれていることを確認してください:
uv build
unzip -l dist/simnos-*.whl | grep moduli
tar tzf dist/simnos-*.tar.gz | grep moduli
両コマンドで moduli の path が表示されるはずです。uv_build (現在の
build backend) は package directory 配下の non-Python ファイルを
デフォルトで全て include する ため、現状の build に対して
pyproject.toml への追加設定は不要 です。上記の check は将来この
default 挙動が変わった場合の regression を catch する目的で置いて
います。
将来の uv_build (or 別 backend) でファイルが drop される事態が発生
したら、fallback として以下を pyproject.toml に明示追加します:
[tool.uv.build-backend]
source-include = ["simnos/plugins/servers/moduli"]
Release CI workflow (.github/workflows/pypi-publish.yml) も publish
前に wheel + sdist 両方の moduli ファイル存在を assert します。
Commit と PR¶
- Commit message:
chore: regenerate bundled moduli (#NNN) - PR description に新しい「next recommended rotation」日 (生成日 + 3 年) を 記録
- logjam-class trigger による再生成の場合は、その背景も簡潔に記載