fix(cd): preserve verified host trust on ssh outage

This commit is contained in:
ogt
2026-07-14 09:06:08 +08:00
parent 964beb2564
commit 1a5adef360
2 changed files with 59 additions and 6 deletions

View File

@@ -2113,12 +2113,28 @@ jobs:
# 後續 patch 推進缺漏的 known_hosts → asyncssh 拒所有 SSH。
# 修法scan 完驗證所有 allowlisted 主機都在;缺任何一台就 abort
# 不能覆蓋現有 secret防止 production SSH 自動修復路徑癱瘓。
ssh-keyscan 192.168.0.110 192.168.0.112 192.168.0.120 192.168.0.121 192.168.0.188 > /tmp/known_hosts_repair 2>/tmp/known_hosts_scan_err || true
: > /tmp/known_hosts_repair
: > /tmp/known_hosts_scan_err
EXPECTED_HOSTS=5
PRESENT=0
for scan_attempt in 1 2 3; do
ssh-keyscan -T 5 192.168.0.110 192.168.0.112 192.168.0.120 192.168.0.121 192.168.0.188 \
>> /tmp/known_hosts_repair 2>>/tmp/known_hosts_scan_err || true
sort -u /tmp/known_hosts_repair -o /tmp/known_hosts_repair
PRESENT=0
for ip in 192.168.0.110 192.168.0.112 192.168.0.120 192.168.0.121 192.168.0.188; do
if grep -qE "^\${ip}[[:space:]]" /tmp/known_hosts_repair 2>/dev/null; then
PRESENT=\$((PRESENT + 1))
fi
done
if [ "\$PRESENT" -eq "\$EXPECTED_HOSTS" ]; then
break
fi
sleep "\$scan_attempt"
done
for ip in 192.168.0.110 192.168.0.112 192.168.0.120 192.168.0.121 192.168.0.188; do
if grep -qE "^\${ip}[[:space:]]" /tmp/known_hosts_repair 2>/dev/null; then
PRESENT=\$((PRESENT + 1))
:
else
echo "⚠️ ssh-keyscan 缺主機 \${ip}"
fi
@@ -2137,10 +2153,27 @@ jobs:
|| { echo "❌ ssh-mcp-key known_hosts 更新失敗,停止部署"; exit 1; }
rm -f /tmp/known_hosts_repair /tmp/known_hosts_scan_err
else
echo "❌ ssh-keyscan 只抓到 \${PRESENT}/\${EXPECTED_HOSTS} 台主機,跳過 patch保留現有 secret"
cat /tmp/known_hosts_scan_err 2>/dev/null | head -10
rm -f /tmp/known_hosts_repair /tmp/known_hosts_scan_err
exit 1
# A transient guest SSH outage must not overwrite trust data, but it
# also must not blindly green-light the deploy. Decode only on the
# K3s controller, verify all five public host entries, then delete
# the temporary file without printing its contents.
EXISTING_KNOWN_HOSTS=/tmp/known_hosts_existing
EXISTING_PRESENT=0
if \$KUBECTL get secret ssh-mcp-key -n awoooi-prod \
-o jsonpath='{.data.known_hosts}' | base64 -d > "\$EXISTING_KNOWN_HOSTS"; then
for ip in 192.168.0.110 192.168.0.112 192.168.0.120 192.168.0.121 192.168.0.188; do
if grep -qE "^\${ip}[[:space:]]" "\$EXISTING_KNOWN_HOSTS" 2>/dev/null; then
EXISTING_PRESENT=\$((EXISTING_PRESENT + 1))
fi
done
fi
rm -f "\$EXISTING_KNOWN_HOSTS" /tmp/known_hosts_repair /tmp/known_hosts_scan_err
if [ "\$EXISTING_PRESENT" -eq "\$EXPECTED_HOSTS" ]; then
echo "⚠️ fresh host-key scan \${PRESENT}/\${EXPECTED_HOSTS}; existing ssh-mcp-key verified 5/5 and preserved"
else
echo "❌ fresh host-key scan \${PRESENT}/\${EXPECTED_HOSTS}; existing ssh-mcp-key coverage \${EXISTING_PRESENT}/\${EXPECTED_HOSTS}"
exit 1
fi
fi
echo "✅ 所有 Secrets 注入完成"

View File

@@ -0,0 +1,20 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
WORKFLOW = ROOT / ".gitea/workflows/cd.yaml"
def test_cd_known_hosts_gate_retries_and_verifies_preserved_secret() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
gate = workflow[workflow.index(": > /tmp/known_hosts_repair") :]
gate = gate[: gate.index('echo "✅ 所有 Secrets 注入完成"')]
assert "for scan_attempt in 1 2 3" in gate
assert 'sleep "\\$scan_attempt"' in gate
assert "existing ssh-mcp-key verified 5/5 and preserved" in gate
assert "EXISTING_PRESENT=\\$((EXISTING_PRESENT + 1))" in gate
assert 'if [ "\\$EXISTING_PRESENT" -eq "\\$EXPECTED_HOSTS" ]' in gate
assert 'rm -f "\\$EXISTING_KNOWN_HOSTS"' in gate
assert "cat /tmp/known_hosts_scan_err" not in gate
assert "existing ssh-mcp-key coverage" in gate