From 1846d973a30f23fda9b2af00fd54bb609996e9b1 Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sat, 3 May 2025 03:24:40 -0700 Subject: [PATCH] wip2 --- README.md | 7 ++-- config/server.default | 10 ++++- scripts/provision.py | 89 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 23171db..b10f490 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,12 @@ Services deployed on [matthewtran.com](https://matthewtran.com). ## update +quick dev => scp dockerfiles => rebuild locally +final dev => reprovision +TODO fix setup_router DUID suff => may need to reset after each provision... 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`. @@ -60,7 +63,3 @@ Run `scripts/backup.py` and save the resultant `data.zip` somewhere. Also run th btrfs device stats btrfs scrub start -B ``` - -## security - -To protect against vulnerabilities, all services run as non-root users inside containers that are on separate networks by function and have all capabilities dropped. These non-root users have a UID that doesn't exist on the host and a GID that maps to their function. Hopefully, even in the event of a full container compromise and root escalation, there is little damage an attacker can do. The main security hole left is containers accessing the LAN and host, AppArmor might help with this. diff --git a/config/server.default b/config/server.default index 5e57bea..4bf665c 100644 --- a/config/server.default +++ b/config/server.default @@ -6,5 +6,13 @@ ], "var_key": "", "var_wipe": false - } + }, + "drives": [ + { + "devices": ["/dev/sda"], + "key": "", + "name": "stuff", + "wipe": false + } + ] } \ No newline at end of file diff --git a/scripts/provision.py b/scripts/provision.py index a99d1e8..0a042cd 100755 --- a/scripts/provision.py +++ b/scripts/provision.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 +import base64 import json +import secrets import subprocess import yaml @@ -11,6 +13,15 @@ if __name__ == "__main__": "version": "1.6.0", } + # recommend keys if needed + if "var_key" not in cfg["core"]: + print(f'cfg["core"]["var_key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"') + exit(1) + for i, d in enumerate(cfg["drives"]): + if "key" not in d: + print(f'cfg["drives"][{i}]["key"] doesn\'t exist, try "{base64.b64encode(secrets.token_bytes(64)).decode("utf-8")}"') + exit(1) + # configure root drive but["storage"] = { "disks": [ @@ -31,33 +42,68 @@ if __name__ == "__main__": ], }, ], + "raid": [], + "luks": [ + { + "name": "root", + "label": "luks-root", + "device": "/dev/disk/by-partlabel/root", + "wipe_volume": True, + "clevis": { "tpm2": True }, + }, + { + "name": "var", + "device": "/dev/disk/by-partlabel/var", + "wipe_volume": cfg["core"]["var_wipe"], + "key_file": { "inline": base64.b64decode(cfg["core"]["var_key"]) }, + }, + ], "filesystems": [ { - "device": "/dev/disk/by-partlabel/root", - "format": "btrfs", + "device": "/dev/mapper/root", + "format": "xfs", "wipe_filesystem": True, "label": "root", }, { "path": "/var", - "device": "/dev/disk/by-partlabel/var", - "format": "btrfs", + "device": "/dev/mapper/var", + "format": "xfs", "wipe_filesystem": cfg["core"]["var_wipe"], "with_mount_unit": True, }, ], + "files": [], + "directories": [], } - # set hostname - but["storage"]["files"] = [ - { - "path": "/etc/hostname", - "mode": 0o644, - "contents": { - "inline": cfg["core"]["hostname"], - }, - }, - ] + # add additional drives + for d in cfg["drives"]: + raid = len(d["devices"]) > 1 + if raid: + but["storage"]["raid"].append({ + "name": d["name"], + "level": "raid1", + "devices": d["devices"], + }) + but["storage"]["luks"].append({ + "name": d["name"], + "device": f"/dev/md/{d["name"]}" if raid else d["devices"][0], + "wipe_volume": d["wipe"], + "key_file": { "inline": base64.b64decode(d["key"]) }, + }) + but["storage"]["filesystems"].append({ + "path": f"/var/mnt/{d["name"]}", + "device": f"/dev/mapper/{d["name"]}", + "format": "ext4", + "wipe_filesystem": d["wipe"], + "with_mount_unit": True, + }) + but["storage"]["directories"].append({ + "path": f"/var/mnt/{d["name"]}", + "user": { "name": "core" }, + "group": { "name": "core" }, + }) # add SSH keys assert(len(cfg["core"]["ssh_keys"]) > 0) @@ -90,6 +136,7 @@ if __name__ == "__main__": "ExecStart=/usr/bin/rpm-ostree install -y --allow-inactive " + " ".join([ "avahi", "htop", + "vim", ]), "ExecStart=/bin/touch /etc/rpm/%N.stamp", "ExecStart=/bin/systemctl --no-block reboot", @@ -100,10 +147,20 @@ if __name__ == "__main__": ], } + # set hostname + but["storage"]["files"].append({ + "path": "/etc/hostname", + "mode": 0o644, + "contents": { "inline": cfg["core"]["hostname"] }, + }) + # allow unprivileged port access + but["storage"]["files"].append({ + "path": "/etc/sysctl.d/99-unprivileged-ports.conf", + "mode": 0o644, + "contents": { "inline": "net.ipv4.ip_unprivileged_port_start=80" }, + }) - # TODO encrypt /var w/ key (root w/ tpm) - # TODO add additional drives (raid?) # TODO make server build images on first boot? # TODO serve backup.zip to restore on first boot? only if wipe specified