mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
fix(bench): discover the challenge port dynamically and tear down on deploy failure
The suite does not use one container port: most challenges publish 8080 but some (051, 091, 100) publish 5000, and the adapter had 8080 hardcoded, so deploy failed on those and the failure happened before the teardown block, leaving the challenge running. - the host port now comes from the app container port mapping (any container port, via docker inspect), never from an assumed 8080 - the teardown finally now wraps the deploy as well: a failure halfway through deploy no longer leaves containers behind (--keep and --deploy-only still opt out)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user