diff --git a/scripts/benchmark/drost.py b/scripts/benchmark/drost.py index 7d5c302..502ed46 100755 --- a/scripts/benchmark/drost.py +++ b/scripts/benchmark/drost.py @@ -36,7 +36,6 @@ FLAG_RE = re.compile(r"(?:FLAG\{[0-9a-f]{64}\}|flag\{[0-9a-f-]{36}\})") CHALLENGE_RE = re.compile(r"DBEN-v1-\d{3}") APP_SERVICE = "app" -APP_CONTAINER_PORT = "8080" def random_flag() -> str: @@ -80,13 +79,33 @@ def compose_project(challenge: str, run_id: str) -> str: def host_port(output: str) -> int: - """Parse the host port out of `docker compose port app 8080` output.""" + """Parse the host port out of `docker compose port` output.""" line = output.strip().splitlines()[-1] if output.strip() else "" if ":" not in line: raise SystemExit(f"could not parse a host port from docker compose output: {output!r}") return int(line.rsplit(":", 1)[1]) +def container_host_port(project: str, cdir: pathlib.Path) -> int: + """Host port published by the app service, whatever container port the + challenge declares (most challenges use 8080, some use 5000: never assume).""" + res = _compose(project, cdir, ["ps", "-q", APP_SERVICE], timeout=60) + cid = res.stdout.strip().splitlines()[0] if res.stdout.strip() else "" + if not cid: + raise SystemExit(f"no running {APP_SERVICE} container for project {project}") + insp = subprocess.run( + ["docker", "inspect", "--format", + "{{range $p, $conf := .NetworkSettings.Ports}}{{(index $conf 0).HostPort}}\n{{end}}", + cid], + capture_output=True, text=True, timeout=60) + for token in insp.stdout.split(): + if token.isdigit(): + return int(token) + raise SystemExit( + f"no published host port on container {cid} " + f"({insp.stderr.strip()[:200] or 'inspect returned no port mapping'})") + + def derive_ground_truth(repo: str, challenge: str) -> dict: """Ground truth from the challenge's own benchmark.json. @@ -168,8 +187,7 @@ def deploy(repo: str, challenge: str, flag: str, run_id: str) -> dict: if up.returncode != 0: print("[drost] up --wait failed; falling back to up -d and polling") _compose(project, cdir, ["up", "-d"], timeout=900) - res = _compose(project, cdir, ["port", APP_SERVICE, APP_CONTAINER_PORT], timeout=60) - url = f"http://127.0.0.1:{host_port(res.stdout)}" + url = f"http://127.0.0.1:{container_host_port(project, cdir)}" for _ in range(60): try: urllib.request.urlopen(url, timeout=5) diff --git a/scripts/benchmark/run_bench.py b/scripts/benchmark/run_bench.py index 35071cf..8fc4728 100755 --- a/scripts/benchmark/run_bench.py +++ b/scripts/benchmark/run_bench.py @@ -472,17 +472,24 @@ def main() -> None: gt = json.loads(gt_path.read_text()) gt_findings = gt["findings"] - dep = deploy_corpus(args) - corpus_url = dep["url"] - flag = dep.get("flag") - if args.deploy_only: - print(f"[drost] deploy-only: up at {corpus_url}, flag {flag}") - print(f"[drost] stop it later with: docker compose -p {dep['project']} down --remove-orphans" - f" (cwd {dep['challenge_dir']})") - return + # drost project/dir are deterministic, so they are known before deploy and + # the finally below can clean up even when the deploy itself fails halfway. + drost_project = drost_cdir = None + if args.corpus_type == "drost": + drost_project = drost.compose_project(args.drost_id, args.run_id) + drost_cdir = str(drost.challenge_dir(args.drost_repo, args.drost_id)) - task_url = corpus_url try: + dep = deploy_corpus(args) + corpus_url = dep["url"] + flag = dep.get("flag") + if args.deploy_only: + print(f"[drost] deploy-only: up at {corpus_url}, flag {flag}") + print(f"[drost] stop it later with: docker compose -p {dep['project']} down --remove-orphans" + f" (cwd {dep['challenge_dir']})") + return + + task_url = corpus_url if args.target_host != "127.0.0.1": task_url = f"http://{args.target_host}:{corpus_url.rsplit(':', 1)[1]}" print(f"[corpus] task target rewritten for ARTEX reachability: {task_url}") @@ -534,12 +541,12 @@ def main() -> None: write_report(out_dir, args.corpus, args, task_info, status, findings, verdicts, result) print(json.dumps(result, indent=2)[:1500]) finally: - # The corpus is torn down even when scoring fails halfway: a crash in - # the report phase must not leave the challenge running (--keep opts - # out deliberately). + # The corpus is torn down even when a step fails halfway: a crash in + # deploy, scoring or reporting must not leave the challenge running + # (--keep and --deploy-only opt out deliberately). if args.corpus_type == "drost": - if not args.keep: - drost.teardown(dep["project"], dep["challenge_dir"]) + if not (args.keep or args.deploy_only): + drost.teardown(drost_project, drost_cdir) elif not (args.keep or args.corpus_url): subprocess.run(["docker", "rm", "-f", f"bench-{args.corpus}-{args.run_id}"], capture_output=True)