From dd072e76d8f28d0c1c66f4160d2ad122691a5edd Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Wed, 19 Feb 2025 00:30:35 -0800 Subject: [PATCH] further cleanup and securing --- README.md | 15 ++++++------ compose.yml | 48 +++++++++++++++++++++++++++++-------- scripts/setup_router.py | 27 ++++++++++++++------- scripts/setup_server.py | 53 +++++++++++++++++++++++++++++++---------- website/Dockerfile | 1 + website/ip_update.py | 11 +++++---- 6 files changed, 112 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index f3cb97b..38ae610 100644 --- a/README.md +++ b/README.md @@ -15,23 +15,24 @@ Stuff that's deployed on [matthewtran.com](https://matthewtran.com). Currently r 1. Install [Ubuntu Desktop 24.04.1 LTS](https://ubuntu.com/download/desktop) with TPM-backed FDE. Server currently has a [bug](https://bugs.launchpad.net/ubuntu/+source/cryptsetup/+bug/1980018) that makes TPM-backed FDE hard. - You may need to manually enable IPv6 on the network connection. Use `Automatic` not `Automatic, DHCP only`. + - Add an SSH key if you need remote access, setup will disable password authentication. - Clone this repo and `cd` into it. 2. Set up the server. - `scripts/setup_server.py` -3. Set up the OpenWrt 24.10 router. Copy SSH keys first to make it easier. - - `scripts/setup_router.py` - - For each WireGuard client, run `scripts/setup_peer.py `. - - Reboot the router and server. -4. Configure, build, and start services. +3. Set up the OpenWrt 24.10 router. Copy SSH keys first to make it easier. Use a strong root password. + - `scripts/setup_router.py ` +4. Reboot the router and server. +5. Configure, build, and start services. - Create `website/sendgrid.key` with a [SendGrid API key](https://app.sendgrid.com/settings/api_keys). - Create `terraria/password.txt` if needed. - Restore backups if needed. - `scripts/setup_repo.py` - `docker compose build` - `docker compose up -d` -5. Optionally, add additional drives. This script formats the drive as LUKS/BTRFS with the key file stored in `/opt/luks` and auto-mounts on boot. Make sure to backup the key file elsewhere. +6. Optionally, add additional drives. This script formats the drive as LUKS/BTRFS with the key file stored in `/opt/luks` and auto-mounts on boot. Make sure to backup the key file elsewhere. - `scripts/setup_drive.py ` -6. Optionally, add the following DNS entries at the registrar. +7. Optionally, run `scripts/setup_peer.py ` for each WireGuard client. +8. Optionally, add the following DNS entries at the registrar. | hosts | type | data | | ----------------------- | ------ | ------------------------------------- | | `@`, `git`, `wg`, `www` | `A` | `` | diff --git a/compose.yml b/compose.yml index 98c87ba..283548b 100644 --- a/compose.yml +++ b/compose.yml @@ -1,9 +1,22 @@ networks: - default6: + web: enable_ipv6: true ipam: config: - - subnet: fd3a:138e:8fd0:0001::/64 + - subnet: "172.20.0.0/16" + - subnet: "fd3a:138e:8fd0:0020::/64" + monero: + enable_ipv6: true + ipam: + config: + - subnet: "172.21.0.0/16" + - subnet: "fd3a:138e:8fd0:0021::/64" + game: + enable_ipv6: true + ipam: + config: + - subnet: "172.22.0.0/16" + - subnet: "fd3a:138e:8fd0:0022::/64" services: website: restart: always @@ -13,21 +26,27 @@ services: - "80:80" - "443:443" networks: - - default6 + - web volumes: - ./website/letsencrypt:/etc/letsencrypt + cap_drop: + - ALL + cap_add: + - NET_BIND_SERVICE gitea: restart: always image: gitea/gitea:latest-rootless ports: - "2222:2222" networks: - - default6 + - web volumes: - ./website/gitea/data:/var/lib/gitea - ./website/gitea/config:/etc/gitea - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro + cap_drop: + - ALL monerod: restart: always build: monerod/. @@ -38,9 +57,11 @@ services: - "18080:18080" - "18081:18081" networks: - - default6 + - monero volumes: - ./monerod/.bitmonero:/home/ubuntu/.bitmonero + cap_drop: + - ALL p2pool: stop_grace_period: 1m # TODO reduce m_shutdownCountdown to reduce this restart: always @@ -53,10 +74,11 @@ services: - "37888:37888" - "37889:37889" networks: - - default6 + - monero volumes: - ./p2pool/cache:/home/ubuntu/cache - - /dev/hugepages:/dev/hugepages + cap_drop: + - ALL minecraft: restart: always build: minecraft/. @@ -64,9 +86,11 @@ services: ports: - "25565:25565" networks: - - default6 + - game volumes: - ./minecraft/worlds:/home/ubuntu/worlds + cap_drop: + - ALL # minecraft_bedrock: # restart: always # build: minecraft_bedrock/. @@ -75,9 +99,11 @@ services: # - "19132:19132/udp" # - "19133:19133/udp" # networks: - # - default6 + # - game # volumes: # - ./minecraft_bedrock/worlds:/home/ubuntu/worlds + # cap_drop: + # - ALL # terraria: # restart: always # build: terraria/. @@ -85,6 +111,8 @@ services: # ports: # - "7777:7777" # networks: - # - default6 + # - game # volumes: # - ./terraria/worlds:/home/ubuntu/worlds + # cap_drop: + # - ALL diff --git a/scripts/setup_router.py b/scripts/setup_router.py index 2b9cfc7..1a3e069 100755 --- a/scripts/setup_router.py +++ b/scripts/setup_router.py @@ -2,19 +2,22 @@ import hashlib import subprocess +import sys -ETH = "enp5s0" -IPV4 = "192.168.1.69" +DP_LEN = 64 # xfinity delegated prefix length +WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random +WG_ULA = "fd32:76a6:ec61:577a::" # WireGuard random +IPV4 = "192.168.1.69" # OpenWrt default IPV6 = "69" -WG_IPV4 = "192.168.2.1" -WG_IPV6 = "fd32:76a6:ec61:577a::1" +WG_IPV4 = "192.168.2.1" # WireGuard chosen +WG_IPV6 = WG_ULA + "1" def run(cmds): # ssh-keygen -t ed25519 subprocess.run(["ssh", "root@OpenWrt.lan", ";".join(cmds)], check=True) -def mac(): - return open(f"/sys/class/net/{ETH}/address", "r").read().strip() +def mac(eth): + return open(f"/sys/class/net/{eth}/address", "r").read().strip() def duid(): # adapted from https://github.com/mss/nm-duid @@ -27,16 +30,21 @@ def key(): return (pub.decode("utf-8"), priv.decode("utf-8")) if __name__ == "__main__": - # prevent access using WAN addresses + ETH = sys.argv[1] # e.g. enp5s0 + + # basic setup run([ + f"uci set network.globals.ula_prefix='{WRT_ULA}/48'" "uci set dropbear.main.Interface='lan'", + "uci commit network", + "uci commit dropbear", ]) # static IP run([ "uci add dhcp host", "uci set dhcp.@host[-1].name='matt-ryzen'", - f"uci set dhcp.@host[-1].mac='{mac()}'", + f"uci set dhcp.@host[-1].mac='{mac(ETH)}'", f"uci set dhcp.@host[-1].ip='{IPV4}'", f"uci set dhcp.@host[-1].duid='{duid()}'", f"uci set dhcp.@host[-1].hostid='{IPV6}'", @@ -75,7 +83,7 @@ if __name__ == "__main__": f"uci set firewall.@rule[-1].name='allow-{name}'", "uci set firewall.@rule[-1].src='wan'", "uci set firewall.@rule[-1].dest='lan'", - f"uci set firewall.@rule[-1].dest_ip='::{IPV6}/-64'", # xfinity provides /64 => /-64 match + f"uci set firewall.@rule[-1].dest_ip='::{IPV6}/{DP_LEN-128}'", f"uci set firewall.@rule[-1].dest_port='{PORTS[name]}'", "uci set firewall.@rule[-1].target='ACCEPT'", ]) @@ -85,6 +93,7 @@ if __name__ == "__main__": ]) # wireguard setup + # TODO configure NAT66 to fix tunneling IPv6 traffic pub, priv = key() run([ # install packages diff --git a/scripts/setup_server.py b/scripts/setup_server.py index 1982b7f..b6e80da 100755 --- a/scripts/setup_server.py +++ b/scripts/setup_server.py @@ -4,7 +4,6 @@ import json import os import subprocess from pathlib import Path -from setup_router import WG_IPV4, WG_IPV6 def run(cmd, capture=False): if capture: @@ -15,30 +14,60 @@ def run(cmd, capture=False): if __name__ == "__main__": # install dependencies and configure run("apt update") - run("apt upgrade") - run("apt install -y avahi-daemon btrfs-progs python-is-python3 python3-pip wireguard zip") - if run("ufw status", capture=True) == b"Status: inactive\n": - run("ufw enable") - run("ufw allow OpenSSH") - with open("/etc/sysctl.conf", "a+") as f: + run("apt upgrade -y") + run("apt install -y avahi-daemon btrfs-progs openssh-server python-is-python3 python3-pip wireguard zip") + with open("/etc/sysctl.conf", "a+") as f: # enable huge pages for local mining f.seek(0) - if "vm.nr_hugepages=3072\n" not in f.readlines(): - f.write("vm.nr_hugepages=3072\n") # enable huge pages + if "vm.nr_hugepages=1280\n" not in f.readlines(): + f.write("vm.nr_hugepages=1280\n") + file = Path("/etc/ssh/sshd_config.d/restrict.conf") # only allow public key login + if not file.exists(): + with file.open("w") as f: + f.write("PasswordAuthentication no\n") # install docker and configure run("snap install docker") run("addgroup --system docker") run(f"adduser {os.getlogin()} docker") - run("snap disable docker") - run("snap enable docker") with open("/var/snap/docker/current/config/daemon.json", "r+") as f: cfg = json.load(f) cfg["ipv6"] = True - cfg["fixed-cidr-v6"] = "fd3a:138e:8fd0:0000::/64" + cfg["fixed-cidr-v6"] = "fd3a:138e:8fd0:0000::/64" # Docker ULA f.seek(0) json.dump(cfg, f, indent=4) run("systemctl restart snap.docker.dockerd.service") + # restrict network access from containers + file = Path("/etc/systemd/system/docker-restrict.service") + if not file.exists(): + with file.open("w") as f: + f.writelines(s + "\n" for s in [ + "[Unit]", + "Description=Restrict Docker network access", + "Before=network.target", + "After=network-pre.target", + "", + "[Service]", + "Type=oneshot", + "ExecStart=/opt/docker-restrict.sh", + "RemainAfterExit=yes", + "", + "[Install]", + "WantedBy=multi-user.target", + ]) + file = Path("/opt/docker-restrict.sh") + if not file.exists(): + with file.open("w") as f: + f.writelines(s + "\n" for s in [ + "#!/bin/sh", + "iptables -N DOCKER-USER || true", + "iptables -I DOCKER-USER -p tcp --dport 22 -j DROP", # SSH + "ip6tables -N DOCKER-USER || true", + "ip6tables -I DOCKER-USER -p tcp --dport 22 -j DROP", # SSH + ]) + file.chmod(0o755) + run("systemctl enable docker-restrict.service") + # TODO modify /etc/crypttab instead once Ubuntu fixed file = Path("/etc/systemd/system/luks.service") if not file.exists(): diff --git a/website/Dockerfile b/website/Dockerfile index f0927f2..b66b2d6 100644 --- a/website/Dockerfile +++ b/website/Dockerfile @@ -11,6 +11,7 @@ RUN pip3 install sendgrid --break-system-packages RUN rm /etc/nginx/sites-enabled/default # enable site +# TODO make the website code not terrible ;-; COPY matthewtran.com /etc/nginx/sites-available RUN ln -s /etc/nginx/sites-available/matthewtran.com /etc/nginx/sites-enabled/matthewtran.com COPY html /var/www/matthewtran.com/html diff --git a/website/ip_update.py b/website/ip_update.py index 50327ba..b28845e 100755 --- a/website/ip_update.py +++ b/website/ip_update.py @@ -26,11 +26,12 @@ if __name__ == "__main__": from_email="mtran319@gmail.com", to_emails="mtran319@gmail.com", subject="pls update ip", - html_content=f"

old ipv4: {old_ipv4}

" - f"

old ipv6: {old_ipv6}

" - f"

new ipv4: {ipv4}

" - f"

new ipv6: {ipv6}

" - ) + html_content=( + f"

old ipv4: {old_ipv4}

" + f"

old ipv6: {old_ipv6}

" + f"

new ipv4: {ipv4}

" + f"

new ipv6: {ipv6}

" + )) try: print(f"IP changed to {ipv4} and {ipv6}") resp = sg.send(msg)