further cleanup and securing

This commit is contained in:
Matthew Tran 2025-02-19 00:30:35 -08:00
parent be0530cafe
commit dd072e76d8
6 changed files with 112 additions and 43 deletions

View File

@ -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. 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`. - 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. - Clone this repo and `cd` into it.
2. Set up the server. 2. Set up the server.
- `scripts/setup_server.py` - `scripts/setup_server.py`
3. Set up the OpenWrt 24.10 router. Copy SSH keys first to make it easier. 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` - `scripts/setup_router.py <interface>`
- For each WireGuard client, run `scripts/setup_peer.py <name>`. 4. Reboot the router and server.
- Reboot the router and server. 5. Configure, build, and start services.
4. Configure, build, and start services.
- Create `website/sendgrid.key` with a [SendGrid API key](https://app.sendgrid.com/settings/api_keys). - Create `website/sendgrid.key` with a [SendGrid API key](https://app.sendgrid.com/settings/api_keys).
- Create `terraria/password.txt` if needed. - Create `terraria/password.txt` if needed.
- Restore backups if needed. - Restore backups if needed.
- `scripts/setup_repo.py` - `scripts/setup_repo.py`
- `docker compose build` - `docker compose build`
- `docker compose up -d` - `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 <drive> <mount path>` - `scripts/setup_drive.py <drive> <mount path>`
6. Optionally, add the following DNS entries at the registrar. 7. Optionally, run `scripts/setup_peer.py <name>` for each WireGuard client.
8. Optionally, add the following DNS entries at the registrar.
| hosts | type | data | | hosts | type | data |
| ----------------------- | ------ | ------------------------------------- | | ----------------------- | ------ | ------------------------------------- |
| `@`, `git`, `wg`, `www` | `A` | `<public IPv4>` | | `@`, `git`, `wg`, `www` | `A` | `<public IPv4>` |

View File

@ -1,9 +1,22 @@
networks: networks:
default6: web:
enable_ipv6: true enable_ipv6: true
ipam: ipam:
config: 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: services:
website: website:
restart: always restart: always
@ -13,21 +26,27 @@ services:
- "80:80" - "80:80"
- "443:443" - "443:443"
networks: networks:
- default6 - web
volumes: volumes:
- ./website/letsencrypt:/etc/letsencrypt - ./website/letsencrypt:/etc/letsencrypt
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
gitea: gitea:
restart: always restart: always
image: gitea/gitea:latest-rootless image: gitea/gitea:latest-rootless
ports: ports:
- "2222:2222" - "2222:2222"
networks: networks:
- default6 - web
volumes: volumes:
- ./website/gitea/data:/var/lib/gitea - ./website/gitea/data:/var/lib/gitea
- ./website/gitea/config:/etc/gitea - ./website/gitea/config:/etc/gitea
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
cap_drop:
- ALL
monerod: monerod:
restart: always restart: always
build: monerod/. build: monerod/.
@ -38,9 +57,11 @@ services:
- "18080:18080" - "18080:18080"
- "18081:18081" - "18081:18081"
networks: networks:
- default6 - monero
volumes: volumes:
- ./monerod/.bitmonero:/home/ubuntu/.bitmonero - ./monerod/.bitmonero:/home/ubuntu/.bitmonero
cap_drop:
- ALL
p2pool: p2pool:
stop_grace_period: 1m # TODO reduce m_shutdownCountdown to reduce this stop_grace_period: 1m # TODO reduce m_shutdownCountdown to reduce this
restart: always restart: always
@ -53,10 +74,11 @@ services:
- "37888:37888" - "37888:37888"
- "37889:37889" - "37889:37889"
networks: networks:
- default6 - monero
volumes: volumes:
- ./p2pool/cache:/home/ubuntu/cache - ./p2pool/cache:/home/ubuntu/cache
- /dev/hugepages:/dev/hugepages cap_drop:
- ALL
minecraft: minecraft:
restart: always restart: always
build: minecraft/. build: minecraft/.
@ -64,9 +86,11 @@ services:
ports: ports:
- "25565:25565" - "25565:25565"
networks: networks:
- default6 - game
volumes: volumes:
- ./minecraft/worlds:/home/ubuntu/worlds - ./minecraft/worlds:/home/ubuntu/worlds
cap_drop:
- ALL
# minecraft_bedrock: # minecraft_bedrock:
# restart: always # restart: always
# build: minecraft_bedrock/. # build: minecraft_bedrock/.
@ -75,9 +99,11 @@ services:
# - "19132:19132/udp" # - "19132:19132/udp"
# - "19133:19133/udp" # - "19133:19133/udp"
# networks: # networks:
# - default6 # - game
# volumes: # volumes:
# - ./minecraft_bedrock/worlds:/home/ubuntu/worlds # - ./minecraft_bedrock/worlds:/home/ubuntu/worlds
# cap_drop:
# - ALL
# terraria: # terraria:
# restart: always # restart: always
# build: terraria/. # build: terraria/.
@ -85,6 +111,8 @@ services:
# ports: # ports:
# - "7777:7777" # - "7777:7777"
# networks: # networks:
# - default6 # - game
# volumes: # volumes:
# - ./terraria/worlds:/home/ubuntu/worlds # - ./terraria/worlds:/home/ubuntu/worlds
# cap_drop:
# - ALL

View File

@ -2,19 +2,22 @@
import hashlib import hashlib
import subprocess import subprocess
import sys
ETH = "enp5s0" DP_LEN = 64 # xfinity delegated prefix length
IPV4 = "192.168.1.69" WRT_ULA = "fd16:8f4d:f516::" # OpenWrt random
WG_ULA = "fd32:76a6:ec61:577a::" # WireGuard random
IPV4 = "192.168.1.69" # OpenWrt default
IPV6 = "69" IPV6 = "69"
WG_IPV4 = "192.168.2.1" WG_IPV4 = "192.168.2.1" # WireGuard chosen
WG_IPV6 = "fd32:76a6:ec61:577a::1" WG_IPV6 = WG_ULA + "1"
def run(cmds): def run(cmds):
# ssh-keygen -t ed25519 # ssh-keygen -t ed25519
subprocess.run(["ssh", "root@OpenWrt.lan", ";".join(cmds)], check=True) subprocess.run(["ssh", "root@OpenWrt.lan", ";".join(cmds)], check=True)
def mac(): def mac(eth):
return open(f"/sys/class/net/{ETH}/address", "r").read().strip() return open(f"/sys/class/net/{eth}/address", "r").read().strip()
def duid(): def duid():
# adapted from https://github.com/mss/nm-duid # adapted from https://github.com/mss/nm-duid
@ -27,16 +30,21 @@ def key():
return (pub.decode("utf-8"), priv.decode("utf-8")) return (pub.decode("utf-8"), priv.decode("utf-8"))
if __name__ == "__main__": if __name__ == "__main__":
# prevent access using WAN addresses ETH = sys.argv[1] # e.g. enp5s0
# basic setup
run([ run([
f"uci set network.globals.ula_prefix='{WRT_ULA}/48'"
"uci set dropbear.main.Interface='lan'", "uci set dropbear.main.Interface='lan'",
"uci commit network",
"uci commit dropbear",
]) ])
# static IP # static IP
run([ run([
"uci add dhcp host", "uci add dhcp host",
"uci set dhcp.@host[-1].name='matt-ryzen'", "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].ip='{IPV4}'",
f"uci set dhcp.@host[-1].duid='{duid()}'", f"uci set dhcp.@host[-1].duid='{duid()}'",
f"uci set dhcp.@host[-1].hostid='{IPV6}'", f"uci set dhcp.@host[-1].hostid='{IPV6}'",
@ -75,7 +83,7 @@ if __name__ == "__main__":
f"uci set firewall.@rule[-1].name='allow-{name}'", f"uci set firewall.@rule[-1].name='allow-{name}'",
"uci set firewall.@rule[-1].src='wan'", "uci set firewall.@rule[-1].src='wan'",
"uci set firewall.@rule[-1].dest='lan'", "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]}'", f"uci set firewall.@rule[-1].dest_port='{PORTS[name]}'",
"uci set firewall.@rule[-1].target='ACCEPT'", "uci set firewall.@rule[-1].target='ACCEPT'",
]) ])
@ -85,6 +93,7 @@ if __name__ == "__main__":
]) ])
# wireguard setup # wireguard setup
# TODO configure NAT66 to fix tunneling IPv6 traffic
pub, priv = key() pub, priv = key()
run([ run([
# install packages # install packages

View File

@ -4,7 +4,6 @@ import json
import os import os
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from setup_router import WG_IPV4, WG_IPV6
def run(cmd, capture=False): def run(cmd, capture=False):
if capture: if capture:
@ -15,30 +14,60 @@ def run(cmd, capture=False):
if __name__ == "__main__": if __name__ == "__main__":
# install dependencies and configure # install dependencies and configure
run("apt update") run("apt update")
run("apt upgrade") run("apt upgrade -y")
run("apt install -y avahi-daemon btrfs-progs python-is-python3 python3-pip wireguard zip") run("apt install -y avahi-daemon btrfs-progs openssh-server python-is-python3 python3-pip wireguard zip")
if run("ufw status", capture=True) == b"Status: inactive\n": with open("/etc/sysctl.conf", "a+") as f: # enable huge pages for local mining
run("ufw enable")
run("ufw allow OpenSSH")
with open("/etc/sysctl.conf", "a+") as f:
f.seek(0) f.seek(0)
if "vm.nr_hugepages=3072\n" not in f.readlines(): if "vm.nr_hugepages=1280\n" not in f.readlines():
f.write("vm.nr_hugepages=3072\n") # enable huge pages 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 # install docker and configure
run("snap install docker") run("snap install docker")
run("addgroup --system docker") run("addgroup --system docker")
run(f"adduser {os.getlogin()} 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: with open("/var/snap/docker/current/config/daemon.json", "r+") as f:
cfg = json.load(f) cfg = json.load(f)
cfg["ipv6"] = True 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) f.seek(0)
json.dump(cfg, f, indent=4) json.dump(cfg, f, indent=4)
run("systemctl restart snap.docker.dockerd.service") 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 # TODO modify /etc/crypttab instead once Ubuntu fixed
file = Path("/etc/systemd/system/luks.service") file = Path("/etc/systemd/system/luks.service")
if not file.exists(): if not file.exists():

View File

@ -11,6 +11,7 @@ RUN pip3 install sendgrid --break-system-packages
RUN rm /etc/nginx/sites-enabled/default RUN rm /etc/nginx/sites-enabled/default
# enable site # enable site
# TODO make the website code not terrible ;-;
COPY matthewtran.com /etc/nginx/sites-available COPY matthewtran.com /etc/nginx/sites-available
RUN ln -s /etc/nginx/sites-available/matthewtran.com /etc/nginx/sites-enabled/matthewtran.com RUN ln -s /etc/nginx/sites-available/matthewtran.com /etc/nginx/sites-enabled/matthewtran.com
COPY html /var/www/matthewtran.com/html COPY html /var/www/matthewtran.com/html

View File

@ -26,11 +26,12 @@ if __name__ == "__main__":
from_email="mtran319@gmail.com", from_email="mtran319@gmail.com",
to_emails="mtran319@gmail.com", to_emails="mtran319@gmail.com",
subject="pls update ip", subject="pls update ip",
html_content=f"<p>old ipv4: {old_ipv4}</p>" html_content=(
f"<p>old ipv4: {old_ipv4}</p>"
f"<p>old ipv6: {old_ipv6}</p>" f"<p>old ipv6: {old_ipv6}</p>"
f"<p>new ipv4: {ipv4}</p>" f"<p>new ipv4: {ipv4}</p>"
f"<p>new ipv6: {ipv6}</p>" f"<p>new ipv6: {ipv6}</p>"
) ))
try: try:
print(f"IP changed to {ipv4} and {ipv6}") print(f"IP changed to {ipv4} and {ipv6}")
resp = sg.send(msg) resp = sg.send(msg)