- Rename repo/domain: attack-defense-platform / attackdefense.imrnes.team (all refs replaced)
- challenge_registry.json: single source of truth (28 challs across gemastik18/xvi/xvii)
- teams.py: registry-driven CHALLENGES, set_challenge_enabled, sync_challenge_runtime
(apply enable/disable to live teams: build/up or stop/remove + receiver restart)
- compose_gen.py: render per-team compose from canonical per-challenge templates
(image reuse, per-team ports 30xxx, flag mounts, passwords)
- gen_canonical_composes.py: canonical docker-compose.yml for all services
- import_new_challenges.py: import XVI/XVII services + EOL base image fixes
(debian:buster→bookworm, node:14→20, python:3.7-slim→3.11)
- receiver: xvi package (10 checkers) + xvii package (12 generic checkers),
Challenge base reads PASSWORD_<team_port> from env; gen_receiver_main.py
generates per-team main.py from registry
- main.py: /api/challenges returns full registry; PATCH /api/challenges/<name>
toggles enabled + applies to live teams
- index.html: 🏗️ Challenge Manager tab (toggle per challenge, grouped by set)
- SLA bonus now dynamic (all enabled challenges, not hardcoded 6)
120 lines
4.8 KiB
Python
120 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""One-time importer: copy XVI/XVII challenge sources into the platform
|
|
services/ tree, with EOL base-image fixes and a canonical docker-compose.yml
|
|
(per-challenge template) that the compose generator can render per team.
|
|
|
|
Run after cloning the upstream repos:
|
|
python3 import_new_challenges.py <xvi_dir> <xvii_dir>
|
|
|
|
For each imported challenge it writes services/<name>/:
|
|
- source files (Dockerfile, src, start.sh, requirements, db dumps...)
|
|
- docker-compose.yml (canonical template: single main service + sidecars)
|
|
- apt-insecure.conf (2026-clock GPG fix)
|
|
"""
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
BASE = Path("/opt/gemastik18-final")
|
|
SVC = BASE / "services"
|
|
|
|
# ---------------------------------------------------------------- helpers
|
|
def copy_tree(src: Path, dst: Path, ignore=None):
|
|
if dst.exists():
|
|
shutil.rmtree(dst)
|
|
shutil.copytree(src, dst, ignore=ignore)
|
|
|
|
def add_apt_insecure(d: Path):
|
|
conf = "Acquire::AllowInsecureRepositories \"true\";\nAcquire::AllowDowngradeToInsecureRepositories \"true\";\nApt::Get::AllowUnauthenticated \"true\";\n"
|
|
(d / "apt-insecure.conf").write_text(conf)
|
|
|
|
def fix_base_image(d: Path, old: str, new: str):
|
|
"""Swap the FROM line in a Dockerfile (EOL base -> supported)."""
|
|
df = d / "Dockerfile"
|
|
if not df.exists():
|
|
return False
|
|
t = df.read_text()
|
|
if old in t:
|
|
df.write_text(t.replace(old, new, 1))
|
|
print(f" [fix base] {d.name}: {old} -> {new}")
|
|
return True
|
|
return False
|
|
|
|
DROP_FROM = [
|
|
(r"public\.ecr\.aws/docker/library/(python:3\.11-slim-buster|python:3\.11-slim-bullseye)\b", "python:3.11-slim-bookworm"),
|
|
(r"public\.ecr\.aws/docker/library/ruby:2\.7\.2\b", "ruby:3.2-slim-bookworm"),
|
|
(r"public\.ecr\.aws/docker/library/php:8\.0-apache\b", "php:8.2-apache-bookworm"),
|
|
(r"public\.ecr\.aws/docker/library/golang:bullseye\b", "golang:1.22-bookworm"),
|
|
(r"public\.ecr\.aws/docker/library/ubuntu:20\.04\b", "ubuntu:24.04"),
|
|
(r"public\.ecr\.aws/docker/library/ubuntu:22\.04\b", "ubuntu:24.04"),
|
|
(r"public\.ecr\.aws/docker/library/node(:[0-9]+)?\b", "node:20-slim-bookworm"),
|
|
(r"public\.ecr\.aws/docker/library/python:3\.10\.6\b", "python:3.10-slim-bookworm"),
|
|
]
|
|
|
|
def fix_dockerfile(d: Path):
|
|
df = d / "Dockerfile"
|
|
if not df.exists():
|
|
return
|
|
t = df.read_text()
|
|
for pat, new in DROP_FROM:
|
|
t2 = re.sub(pat, new, t)
|
|
if t2 != t:
|
|
print(f" [fix base] {d.name}: {pat} -> {new}")
|
|
t = t2
|
|
# apt-insecure for every apt-get run
|
|
if "apt-get" in t and "99gemastik-insecure" not in t:
|
|
t = t.replace(
|
|
"RUN apt-get update",
|
|
"COPY apt-insecure.conf /etc/apt/apt.conf.d/99gemastik-insecure\nRUN apt-get -o Acquire::AllowInsecureRepositories=true update",
|
|
1)
|
|
t = t.replace(
|
|
"RUN apt-get install",
|
|
"RUN apt-get -y --allow-unauthenticated install",
|
|
1)
|
|
# ensure openssh + ctfuser-ish (the standard template)
|
|
df.write_text(t)
|
|
|
|
# ---------------------------------------------------------------- import
|
|
def import_xvi(src: Path):
|
|
print(f"=== Importing XVI from {src} ===")
|
|
services = src / "services"
|
|
for d in sorted(services.iterdir()):
|
|
if not d.is_dir() or d.name == ".git":
|
|
continue
|
|
name = d.name
|
|
dst = SVC / name
|
|
print(f" - {name}")
|
|
copy_tree(d, dst, ignore=shutil.ignore_patterns("__pycache__", ".git"))
|
|
add_apt_insecure(dst)
|
|
fix_dockerfile(dst)
|
|
(dst / "docker-compose.yml").unlink(missing_ok=True)
|
|
# special: gemas-notes, gemas-fetcher need their src subdirs — already copied whole dir.
|
|
|
|
def import_xvii(src: Path):
|
|
print(f"=== Importing XVII from {src} ===")
|
|
for d in sorted(src.iterdir()):
|
|
if not d.is_dir() or d.name.startswith("."):
|
|
continue
|
|
name = d.name
|
|
dst = SVC / name
|
|
print(f" - {name}")
|
|
copy_tree(d, dst, ignore=shutil.ignore_patterns("__pycache__", ".git", "docker-compose.yml", "README.md", "test"))
|
|
add_apt_insecure(dst)
|
|
fix_dockerfile(dst)
|
|
(dst / "docker-compose.yml").unlink(missing_ok=True)
|
|
if name == "tempest-poc":
|
|
# compose is frontend+backend split; keep both Dockerfiles
|
|
for sub in ("backend", "frontend"):
|
|
subd = dst / sub
|
|
if subd.exists():
|
|
if (subd / "Dockerfile").exists():
|
|
fix_dockerfile(subd)
|
|
add_apt_insecure(subd)
|
|
|
|
if __name__ == "__main__":
|
|
xvi = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/opt/gemastik-xvi-final")
|
|
xvii = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("/opt/gemastik-xvii-final")
|
|
import_xvi(xvi)
|
|
import_xvii(xvii)
|
|
print("Done. Next: write canonical docker-compose.yml templates per challenge.") |