#!/usr/bin/env python3 """Verify each team's SSH passwords actually work in the live containers. state.json can look perfect while the container holds a different password — set_ssh_passwords() races container boot and its failures are easy to miss. This proves the binding from the INSIDE (per the skill rule: never trust config, prove it with a real login). python3 panel/verify_ssh_creds.py [teamIdx ...] """ import json import subprocess import sys from concurrent.futures import ThreadPoolExecutor from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import teams as orch def probe(user, pw, port, host="127.0.0.1"): r = subprocess.run( ["sshpass", "-p", pw, "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "ConnectTimeout=8", "-o", "LogLevel=ERROR", "-p", str(port), f"{user}@{host}", "whoami; hostname"], capture_output=True, text=True, timeout=30) out = (r.stdout or "").strip().splitlines() return (r.returncode == 0 and len(out) >= 2, out, (r.stderr or "").strip()[:80]) def main(): want = [int(a) for a in sys.argv[1:]] teams = [t for t in orch.list_teams() if not want or t["index"] in want] for t in teams: idx = t["index"] names = [c["name"] for c in orch.enabled_challenges()] jobs = [] for n in names: if n not in (t.get("ports") or {}): continue jobs.append((n, t["ports"][n]["ssh"], t.get("chall_passwords", {}).get(n))) ok = bad = 0 details = [] users = orch.challenge_ssh_users() with ThreadPoolExecutor(max_workers=6) as ex: futs = {ex.submit(probe, users.get(n, "root"), pw, port): n for n, port, pw in jobs if pw} for fut, n in futs.items(): good, out, err = fut.result() if good: ok += 1 # hostname must be _teamN — proves the binding details.append((n, out[1] if len(out) > 1 else "?")) else: bad += 1 details.append((n, f"FAIL {err}")) print(f"team{idx} ({t.get('label')}): ssh {ok} ok / {bad} fail") for n, info in details: if info == "FAIL" or info.startswith("FAIL"): print(f" {n}: {info}") hosts = [i for n, i in details if not i.startswith("FAIL")] mism = [(n, i) for n, i in details if not i.startswith("FAIL") and not i.endswith(f"_team{idx}")] if mism: print(f" !! hostname mismatch (not _team{idx}): {mism}") else: print(f" all hostnames correct (e.g. {hosts[0] if hosts else '-'})") if __name__ == "__main__": main()