Files
felhom.eu/documentation/audits/burndown-3-2026-10-17/tools/samba1109.py
T
admin e1dd1cadd3
gates / gates (push) Successful in 7m2s
burndown 3: report, evidence, register (174 -> 168), 07 Redis RDB [DESIGN], 04 §6.3 the box's password doors
Closed R-1115, R-1111, R-1112, R-1113, R-1109, R-1110, R-1114, R-1096; opened R-1116 (decoy suites in the other
runners), R-1117 (Immich/Nextcloud Redis volume, deferred). Controller 0.325.0 is the floor since 08:21 UTC.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-17 10:28:03 +02:00

101 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""samba1109.py — R-1109 live on demo-hp 9201 through the dashboard's own form posts (the „Megosztás" page).
Env: SC (0600 dir: .ctlpw, and .smbpw written here — never printed), EV, TARGET=9201. Steps:
setup sharing on (server name FELHOMHP), a household SMB password, share r1109a in a NEW folder on hdd_1
read NAME smbclient //192.168.0.155/NAME from SCRATCH GUEST 9202 (a throwaway --rm felhom-samba client
container there — nothing runs on DooPlex): write, list, read back, delete; plus a wrong password
rename A B the household's rename: delete share A, add share B on the SAME folder („existing")
add NAME add a second share NAME on the same folder as r1109b
delete NAME delete a share
facts felhom-samba's Created + label, the smb.conf share sections, the compose's binds
teardown delete the test shares, sharing off, remove the test folder
"""
import os, secrets, subprocess, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/night-restore-2026-10-17/tools")
import box_walk as w # noqa: E402
EV = os.environ["EV"]
LOG = open(os.path.join(EV, "r1109-samba-live.txt"), "a", buffering=1)
_say = w.say
def say(*a):
_say(*a); LOG.write(time.strftime("%H:%M:%S ") + " ".join(str(x) for x in a) + "\n")
w.say = say
import drill # noqa: E402 (form(): a dashboard FORM post with the session's CSRF)
import re # noqa: E402
# drill.form logs the fields it posts — a password field is redacted BEFORE the line is written anywhere (2026-10-17:
# the first run logged the throwaway test password; scrubbed, and the password replaced at teardown).
drill.say = lambda *a: say(*[re.sub(r"('smb_password(?:_confirm)?': )'[^']*'", r"\1'<redacted>'", str(x)) for x in a])
ROOT = "/mnt/felhom-drives/hdd_1"
DIR = ROOT + "/shares/r1109a"
IMG = "gitea.dooplex.hu/admin/felhom-samba:1.2.0"
def facts():
out = w.guest("""docker inspect felhom-samba -f 'samba: created {{.Created}} label={{index .Config.Labels "felhom.smb-conf-sha256"}} status={{.State.Status}}' 2>&1
d=/opt/docker/stacks/felhom-samba; [ -d $d ] || d=$(dirname $(ls /opt/docker/stacks/*/smb.conf | head -1))
echo "smb.conf shares: $(grep -E '^\\[' $d/smb.conf | tr '\\n' ' ')"
echo "smb.conf hash: $(sha256sum $d/smb.conf | cut -c1-16)… compose binds: $(grep -E '^ - /mnt' $d/docker-compose.yml | tr -s ' ' | tr '\\n' ' ')"
echo "inside the container: $(docker exec felhom-samba grep -E '^\\[' /etc/samba/smb.conf | tr '\\n' ' ')" """)
say(out.strip())
def read(name):
auth = f"/root/r1109-auth-{os.getpid()}"
pw = open(f"{w.SC}/.smbpw").read().strip()
script = (f"umask 077; printf 'username=felhom\\npassword=%s\\n' \"$(cat)\" > {auth}; "
f"printf 'username=felhom\\npassword=not-the-password\\n' > {auth}.bad; docker pull -q {IMG} >/dev/null; "
f"docker run --rm --network host -v {auth}:/auth:ro --entrypoint sh {IMG} -c "
f"\"echo r1109-{name} > /tmp/p.txt && smbclient //192.168.0.155/{name} -A /auth -m SMB3 -c 'put /tmp/p.txt probe-{name}.txt; ls probe-*; get probe-{name}.txt /tmp/b.txt; rm probe-{name}.txt' 2>&1 | grep -vE '^\\s*$|blocks of size'; echo \\\"read back: \\$(cat /tmp/b.txt 2>/dev/null || echo NOTHING)\\\"\"; "
f"echo 'wrong password:' $(docker run --rm --network host -v {auth}.bad:/auth:ro --entrypoint smbclient {IMG} //192.168.0.155/{name} -A /auth -m SMB3 -c ls 2>&1 | tail -1); "
f"rm -f {auth} {auth}.bad")
# The password travels on STDIN only: the first line is read into a shell variable on the Proxmox host and piped
# to the guest script by printf (a builtin — never an argv, never a file); the script follows on the same stdin.
r = subprocess.run(w.ssh_args() + ["umask 077; IFS= read -r PW; cat > /tmp/r1109.sh; pct push 9202 /tmp/r1109.sh /root/r1109.sh; "
"rm -f /tmp/r1109.sh; printf '%s' \"$PW\" | pct exec 9202 -- bash /root/r1109.sh; "
"pct exec 9202 -- rm -f /root/r1109.sh"],
input=pw + "\n" + script, capture_output=True, text=True, timeout=300)
say(f"read //{name} from 9202:\n" + "\n".join(l for l in (r.stdout + r.stderr).splitlines() if not l.startswith("perl:") and "locale" not in l and "LC_" not in l and "LANG" not in l and "are supported" not in l))
def main():
step, args = sys.argv[1], sys.argv[2:]
w.login()
if step == "setup":
drill.form("/sharing/enable", {"enabled": "on", "server_name": "FELHOMHP"})
pw = secrets.token_urlsafe(18)
fd = os.open(f"{w.SC}/.smbpw", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600); os.write(fd, pw.encode()); os.close(fd)
drill.form("/sharing/password", {"smb_password": pw, "smb_password_confirm": pw})
for _ in range(60):
time.sleep(5)
c, d = w.ctl("GET", "/sharing/status")
if "running" in str(d).lower() and "prepar" not in str(d).lower():
break
say("status: " + str(d)[:300])
drill.form("/sharing/shares", {"name": "r1109a", "mode": "new", "storage_root": ROOT})
time.sleep(8); facts()
elif step == "read":
read(args[0])
elif step == "rename":
a, b = args
drill.form("/sharing/shares/delete", {"name": a}); time.sleep(6)
drill.form("/sharing/shares", {"name": b, "mode": "existing", "path": DIR}); time.sleep(10); facts()
elif step == "add":
drill.form("/sharing/shares", {"name": args[0], "mode": "existing", "path": DIR}); time.sleep(10); facts()
say("controller log: " + w.guest("docker logs --since 2m felhom-controller 2>&1 | grep -iE 'samba|sharing' | grep -v DEBUG | tail -6"))
elif step == "delete":
drill.form("/sharing/shares/delete", {"name": args[0]}); time.sleep(10); facts()
elif step == "facts":
facts()
elif step == "teardown":
dead = secrets.token_urlsafe(18) # the household password replaced by an unlogged one before sharing goes off
drill.form("/sharing/password", {"smb_password": dead, "smb_password_confirm": dead})
time.sleep(5)
for n in args:
drill.form("/sharing/shares/delete", {"name": n})
drill.form("/sharing/enable", {"enabled": "off", "server_name": "FELHOMHP"})
time.sleep(10)
say("teardown: " + w.guest(f"rm -rf {DIR}; rmdir {ROOT}/shares 2>/dev/null; docker ps -a --filter name=felhom-samba --format '{{{{.Names}}}} {{{{.Status}}}}'; ls {ROOT}/shares 2>&1 | head -2"))
os.remove(f"{w.SC}/.smbpw")
else:
sys.exit("unknown step")
main()