Harden deployment, authentication, firewall and media storage failure paths
This commit is contained in:
parent
64ede7bd05
commit
7145a35927
35 changed files with 2529 additions and 499 deletions
142
scripts/router-firewall-check.py
Normal file
142
scripts/router-firewall-check.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue