feat: team portal with own login, SSH web terminal, targets & guide

- Portal tim punya login sendiri (password = ssh_pass, session team_token)
- SSH Web GUI: /api/team/{idx}/ssh/ws (WebSocket+paramiko) → xterm.js terminal
  (fix: ssh_to_ws non-blocking poll, chall_passwords per-container auth)
- Root <slug>.gemastik.imrnes.team → portal tim (bukan login admin)
- Host validation: /team/{idx} & /team/{idx}/guide 403 kalau host != team domain
- /api/team/{idx}/targets: daftar service tim musuh (attack target)
- guide.html: panduan SSH/attack/defense untuk peserta
- fix esc() String(s) (bug: (s||'').replace is not a function saat port number)
- set_ssh_passwords retry loop (container boot race)
This commit is contained in:
root
2026-09-23 16:37:58 +08:00
parent 4a65f24af3
commit 43ed3df557
5 changed files with 566 additions and 68 deletions
+13 -4
View File
@@ -209,10 +209,19 @@ def set_ssh_passwords(idx: int):
cont = f"{name}_container_team{idx}"
pw = st["chall_passwords"][name]
cmd = f"echo 'ctfuser:{pw}' | chpasswd"
r = subprocess.run(["docker", "exec", cont, "sh", "-c", cmd],
capture_output=True, text=True, timeout=30)
if r.returncode != 0:
print(f"[set_ssh_passwords] {cont}: FAILED ({r.stderr.strip()[:100]})")
# retry a few times — right after `compose up`, container may still be booting
ok = False
for attempt in range(5):
r = subprocess.run(["docker", "exec", cont, "sh", "-c", cmd],
capture_output=True, text=True, timeout=30)
if r.returncode == 0:
ok = True
break
time.sleep(3)
if not ok:
print(f"[set_ssh_passwords] {cont}: FAILED after retries ({r.stderr.strip()[:100]})")
else:
print(f"[set_ssh_passwords] {cont}: OK")
def stop_team(idx: int):
team_dir = TEAMS_DIR / f"team{idx}"