Harden deployment, authentication, firewall and media storage failure paths

This commit is contained in:
rope 2026-09-14 11:50:22 +01:00
parent 64ede7bd05
commit 7145a35927
35 changed files with 2529 additions and 499 deletions

View file

@ -0,0 +1,142 @@
"""Exercise evaluated router rules in private user/network namespaces.
Run: python3 scripts/router-firewall-check.py /tmp/router-rules.nft
Requires ip, nft, nsenter and unshare on PATH. Never runs against the host network.
The extra NAT table models Docker publishing allowed and unlisted host ports.
"""
import os
from pathlib import Path
import subprocess as sp
import sys
original = os.environ.get("FRED_TEST_ORIGINAL_NETNS")
if not original:
os.execvpe(
"unshare",
["unshare", "-Urn", sys.executable, *sys.argv],
{**os.environ, "FRED_TEST_ORIGINAL_NETNS": os.readlink("/proc/self/ns/net")},
)
if os.readlink("/proc/self/ns/net") == original:
sys.exit("Refusing to run outside a private network namespace")
children = []
def run(*args, **kw):
return sp.run(args, check=True, text=True, **kw)
def net(pid, *args, **kw):
return run("nsenter", "-t", str(pid), "-n", *args, **kw)
def peer(interface, address, peer_address):
proc = sp.Popen(["unshare", "-n", "sleep", "300"])
children.append(proc)
# unshare must finish before moving the peer into its namespace.
import time
for _ in range(100):
if os.readlink(f"/proc/{proc.pid}/ns/net") != os.readlink("/proc/self/ns/net"):
break
time.sleep(0.01)
run("ip", "link", "add", interface, "type", "veth", "peer", "name", "peer0")
run("ip", "link", "set", "peer0", "netns", str(proc.pid))
run("ip", "addr", "add", address + "/24", "dev", interface)
run("ip", "link", "set", interface, "up")
net(proc.pid, "ip", "addr", "add", peer_address + "/24", "dev", "peer0")
net(proc.pid, "ip", "link", "set", "peer0", "up")
net(proc.pid, "ip", "link", "set", "lo", "up")
net(proc.pid, "ip", "route", "add", "default", "via", address)
return proc.pid
server = """
import socket,threading,time
ports=PORTS
for port in ports:
for kind in (socket.SOCK_STREAM,socket.SOCK_DGRAM):
s=socket.socket(socket.AF_INET,kind);s.bind(('0.0.0.0',port))
if kind==socket.SOCK_STREAM:s.listen()
def serve(s=s,kind=kind):
while True:
if kind==socket.SOCK_STREAM:
c,a=s.accept()
with c:
data=c.recv(16)
if data:c.sendall(data)
else:
data,a=s.recvfrom(16);s.sendto(data,a)
threading.Thread(target=serve,daemon=True).start()
print('ready',flush=True)
time.sleep(300)
"""
def serve(pid, ports):
cmd = [sys.executable, "-u", "-c", server.replace("PORTS", repr(ports))]
if pid:
cmd = ["nsenter", "-t", str(pid), "-n"] + cmd
proc = sp.Popen(cmd, stdout=sp.PIPE, text=True)
children.append(proc)
assert proc.stdout.readline().strip() == "ready"
def check(pid, port, proto, expected):
client = f"""
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_{"STREAM" if proto == "tcp" else "DGRAM"})
s.settimeout(.4)
try:
s.connect(('{"10.0.0.1" if pid == lan else "198.18.0.1"}',{port}))
s.send(b'hello')
ok=s.recv(16)==b'hello'
except OSError:ok=False
assert ok=={expected!r}, 'unexpected reachability: {proto}/{port}, expected {expected}'
"""
net(pid, sys.executable, "-c", client)
print(
f"{'WAN' if pid == wan else 'LAN'} {proto}/{port}: {'allowed' if expected else 'blocked'}"
)
try:
run("ip", "link", "set", "lo", "up")
wan = peer("eno1", "198.18.0.1", "198.18.0.2")
lan = peer("eth0", "10.0.0.1", "10.0.0.2")
container = peer("docker0", "172.18.0.1", "172.18.0.2")
Path("/proc/sys/net/ipv4/ip_forward").write_text("1")
run("nft", "-f", sys.argv[1])
run(
"nft",
"-f",
"-",
input="""
table ip docker_test {
chain prerouting {
type nat hook prerouting priority -101; policy accept;
iifname "eno1" tcp dport { 25565, 25600, 2022 } dnat to 172.18.0.2
iifname "eno1" udp dport { 25565, 25600, 2022 } dnat to 172.18.0.2
iifname "eno1" tcp dport { 25564, 25601, 9999 } dnat to 172.18.0.2:9000
}
chain forward { type filter hook forward priority 0; policy accept; }
}
""",
)
serve(None, [22, 443, 2022])
serve(container, [25565, 25600, 2022, 9000])
for port in [22, 443, 25565, 25600]:
check(wan, port, "tcp", True)
for port in [25565, 25600]:
check(wan, port, "udp", True)
for port in [2022, 25564, 25601, 9999]:
check(wan, port, "tcp", False)
check(wan, 2022, "udp", False)
check(lan, 2022, "tcp", True)
print("Router namespace checks passed")
finally:
for proc in reversed(children):
proc.terminate()
for proc in children:
proc.wait()