gates / gates (push) Successful in 1m56s
Evidence audits/night-fixes-2026-10-26/ (scanned: 0 secret hits).
101 lines
5.2 KiB
Python
101 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""r1187g_live.py — R-1187 (g) live on scratch guest 9202: a PostGIS app's database survives a `.fab` export → import.
|
|
|
|
A THROWAWAY AdventureLog (postgis/postgis). A superuser is made with the app's own `createsuperuser`; the app is exported
|
|
and imported through the endpoints the UI invokes; then the user is read back through the app's FRONT DOOR — the
|
|
Django admin login page — and through its own ORM. Before the fix the import did not recognise postgis as a database,
|
|
so the dump was never loaded. Teardown through the product. The throwaway password lives only in this process.
|
|
|
|
usage: SC=<0600 dir with .ctlpw> SWEEP_EV=<evidence dir> TARGET=9202 DRIVE_ROOT=/mnt/felhom-drives/scratch_hdd python3 -u r1187g_live.py
|
|
"""
|
|
import os
|
|
import re
|
|
import secrets
|
|
import signal
|
|
import urllib.parse
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools"))
|
|
import sweep as sw # noqa: E402
|
|
|
|
signal.signal(signal.SIGTERM, lambda *a: (_ for _ in ()).throw(SystemExit("SIGTERM — cleaning up")))
|
|
w = sw.w
|
|
APP, SUB, DRIVE = "adventurelog", "advd53", os.environ.get("DRIVE_ROOT", "/mnt/felhom-drives/scratch_hdd")
|
|
say = print
|
|
|
|
|
|
def g(cmd):
|
|
return sw.g(cmd).strip()
|
|
|
|
|
|
def job(path):
|
|
for _ in range(180):
|
|
time.sleep(5)
|
|
code, d = w.ctl("GET", path)
|
|
if isinstance(d, dict) and d.get("done"):
|
|
return d
|
|
return {"timeout": True}
|
|
|
|
|
|
def orm_has(user):
|
|
out = g(f"docker exec adventurelog python manage.py shell -c "
|
|
f"\"from django.contrib.auth import get_user_model; print('ANS=' + str(get_user_model().objects.filter(username='{user}').exists()))\" 2>&1")
|
|
m = re.search(r"ANS=(True|False)", out)
|
|
return m.group(1) if m else "unreadable: " + out[-160:]
|
|
|
|
|
|
def admin_login(user, pw):
|
|
"""The front door, the household's route (box_walk.app_curl carries the setup gate): GET /admin/login/ for the
|
|
form and its csrftoken cookie, POST the form (the password on stdin, never an argument). Success = 302 to /admin/."""
|
|
rc, code, body = w.app_curl(SUB, "/admin/login/", "-D", "-")
|
|
tok = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', body)
|
|
ck = re.search(r"(?im)^set-cookie:\s*csrftoken=([^;]+)", body)
|
|
if not tok or not ck:
|
|
return f"no login form ({code})"
|
|
form = urllib.parse.urlencode({"csrfmiddlewaretoken": tok.group(1), "username": user, "password": pw, "next": "/admin/"})
|
|
rc, code, body = w.app_curl(SUB, "/admin/login/?next=/admin/", "-H", f"Cookie: csrftoken={ck.group(1)}",
|
|
"-H", "Content-Type: application/x-www-form-urlencoded",
|
|
"-H", f"Referer: https://{SUB}.{w.DOMAIN}/admin/login/", method="POST", data=form)
|
|
return code + (" (logged in)" if code == "302" else " (refused)")
|
|
|
|
|
|
def main():
|
|
w.say = say
|
|
w.login()
|
|
user, pw = "cc" + secrets.token_hex(3), "Cc-" + secrets.token_hex(12)
|
|
try:
|
|
ok = w.deploy(APP, SUB) and w.wait_app(SUB, "/", tries=120, delay=5)
|
|
say("1. adventurelog installed and answering:", ok)
|
|
out = g(f"docker exec -e DJANGO_SUPERUSER_PASSWORD='{pw}' adventurelog python manage.py createsuperuser --noinput "
|
|
f"--username {user} --email {user}@cc.invalid 2>&1 | tail -1")
|
|
say("2. createsuperuser:", out[:120], "| in the ORM:", orm_has(user))
|
|
say(" front door before:", admin_login(user, pw))
|
|
code, d = w.ctl("POST", "/api/export/start", {"stack_name": APP, "dest_drive": DRIVE, "stop_app": False})
|
|
if code != "200":
|
|
raise SystemExit(f"INSTRUMENT: export start refused {code} {d}")
|
|
j = job("/api/export/status")
|
|
say("3. export:", j.get("error") or "ok", j.get("output_size"), j.get("output_path"))
|
|
fab = j.get("output_path", "")
|
|
say(" the bundle's database:", g(f"tar -tzf {fab} | grep -E 'database/|manifest' | head -4"))
|
|
g("docker exec adventurelog python manage.py shell -c \"from django.contrib.auth import get_user_model; "
|
|
f"get_user_model().objects.filter(username='{user}').delete()\" >/dev/null 2>&1")
|
|
say("4. the user deleted in the live app (so only the import can bring it back) — in the ORM:", orm_has(user))
|
|
code, d = w.ctl("POST", "/api/export/import", {"path": fab})
|
|
if code != "200":
|
|
raise SystemExit(f"INSTRUMENT: import start refused {code} {d}")
|
|
j = job("/api/export/import/status")
|
|
say("5. import:", j.get("error") or "ok", "| steps:", [(s.get("label"), s.get("status")) for s in j.get("steps", [])])
|
|
say(" app answers:", w.wait_app(SUB, "/", tries=120, delay=5))
|
|
say(" R-1187 g — the user back in the ORM:", orm_has(user))
|
|
say(" R-1187 g — the front door after import:", admin_login(user, pw))
|
|
say(" the import's DB line:", g("docker logs --since 15m felhom-controller 2>&1 | grep -E 'restoring database|found DB service|DB dump imported|psql' | tail -3 | cut -c1-200"))
|
|
finally:
|
|
sw.remove(APP, keep=False, say=say)
|
|
say("6. leftovers:", sw.leftovers(APP, SUB))
|
|
say(" bundles removed:", g(f"rm -f {DRIVE}/exports/adventurelog*.fab; ls {DRIVE}/exports 2>/dev/null | grep -c adventurelog"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|