diff options
| author | sommerfeld <sommerfeld@sommerfeld.dev> | 2026-09-17 15:05:37 +0100 |
|---|---|---|
| committer | sommerfeld <sommerfeld@sommerfeld.dev> | 2026-09-17 15:05:37 +0100 |
| commit | 2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d (patch) | |
| tree | 95cbecc698a82ef8b9d9d31cbff75be91f7d9005 | |
| parent | dd45f3ad88b544f065f27143801306a7d6dffd02 (diff) | |
| download | dotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.tar.gz dotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.tar.bz2 dotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.zip | |
Configure LXD and AppArmor on the Arch host
| -rw-r--r-- | docs/lxd-init.yaml | 28 | ||||
| -rw-r--r-- | docs/lxd.md | 106 | ||||
| -rw-r--r-- | etc/kernel/cmdline-linux-hardened.tmpl | 2 | ||||
| -rw-r--r-- | etc/kernel/cmdline.tmpl | 2 | ||||
| -rw-r--r-- | etc/nftables.conf | 8 | ||||
| -rw-r--r-- | meta/base.txt | 5 | ||||
| -rw-r--r-- | systemd-units/system.txt | 4 |
7 files changed, 153 insertions, 2 deletions
diff --git a/docs/lxd-init.yaml b/docs/lxd-init.yaml new file mode 100644 index 0000000..455c2af --- /dev/null +++ b/docs/lxd-init.yaml @@ -0,0 +1,28 @@ +config: {} +networks: + - name: lxdbr0 + type: bridge + project: default + config: + ipv4.address: auto + ipv4.nat: "true" + ipv6.address: auto + ipv6.nat: "true" +storage_pools: + - name: default + driver: btrfs + config: + source: /var/lib/lxd/storage-pools/default +profiles: + - name: default + devices: + eth0: + name: eth0 + network: lxdbr0 + type: nic + root: + path: / + pool: default + type: disk +projects: [] +cluster: null diff --git a/docs/lxd.md b/docs/lxd.md new file mode 100644 index 0000000..4c0a676 --- /dev/null +++ b/docs/lxd.md @@ -0,0 +1,106 @@ +# LXD on Arch + +The host package list includes LXD. The system unit list enables `lxd.socket`. +Use `sudo lxc` to manage instances. Do not expose the LXD socket to aibox or +grant agents permanent membership in the `lxd` group. The +[Canonical VM test](canonical-vm.md) permits a separately authorized host +session with temporary LXD access. This access is root-equivalent. + +## First Setup + +Before starting LXD, complete the AppArmor setup below. Check `/etc/subuid` and +`/etc/subgid` for existing allocations. Native LXD needs subordinate IDs for +root; retain all Podman allocations. On this host, root uses +`1000000-1000999999`, and sommerfeld uses `100000-165535`. + +On a new host, only if the root range is free and not yet allocated, run: + +```sh +sudo usermod --add-subuids 1000000-1000999999 --add-subgids 1000000-1000999999 root +``` + +Restart `lxd.service` if it was already running when the allocations changed. + +On a new Arch host with Btrfs storage, run: + +```sh +just pkg-apply base +just unit-apply +sudo lxd init --preseed < docs/lxd-init.yaml +``` + +Do not apply the preseed to an existing installation. It selects IPv4 and IPv6 +subnets automatically. Check them against LAN and VPN routes before use: + +```sh +sudo lxc network show lxdbr0 +ip -4 route +ip -6 route +``` + +## Firewall + +Run these commands on the host, outside aibox: + +```sh +sudo nft --check --file etc/nftables.conf +just apply +sudo nft --file /etc/nftables.conf +``` + +The rules permit DNS and DHCP from `lxdbr0`, connections from instances, and +return traffic to instances. Existing ICMP rules permit IPv6 neighbour discovery. +Instances can initiate connections to the LAN and VPN networks as well as the +internet. Unsolicited forwarded connections into the bridge remain blocked. + +LXD owns the bridge, address allocation, NAT, and its own firewall table. The repo +replaces only `inet filter`. Do not flush the complete ruleset or restart nftables +with a stop action that flushes LXD's rules. + +An IPv6 bridge address does not provide IPv6 internet access without a suitable +host route. + +## AppArmor + +The package and unit lists include AppArmor. Both kernel command-line templates +enable it alongside the existing security modules. On the host, run: + +```sh +just pkg-apply base +just apply +sudo systemctl enable apparmor.service +sudo mkinitcpio -P +``` + +Do not reboot if image generation fails. If Secure Boot is enabled, ensure the +rebuilt UKIs are signed using the host's existing signing setup before rebooting. +After a successful rebuild, reboot and verify: + +```sh +cat /sys/module/apparmor/parameters/enabled +sudo cat /sys/kernel/security/lsm +sudo aa-status +sudo lxc start ubuntu-dev +sudo lxc exec ubuntu-dev -- cat /proc/1/attr/current +``` + +Skip `lxc start` if the instance is already running. AppArmor must report `Y`, +and the container's process label must show an enforced LXD profile rather than +`unconfined`. If it does not, inspect `journalctl -b -u lxd.service` and kernel +AppArmor messages. Do not disable confinement to bypass a failed start. + +## First Container + +```sh +sudo lxc launch ubuntu:24.04 ubuntu-dev +sudo lxc exec ubuntu-dev -- cloud-init status --wait +sudo lxc exec ubuntu-dev -- apt-get update +sudo lxc exec ubuntu-dev -- su - ubuntu +``` + +The container shares the host kernel. Use a VM for kernel and bootloader tests. +The host list includes `cdrtools` for LXD VM support. Pacman supplies LXD's +QEMU and OVMF dependencies. + +References: [LXD installation](https://canonical.com/lxd/docs/latest/installing/) +and [firewall configuration](https://canonical.com/lxd/docs/latest/howto/network_bridge_firewalld/). diff --git a/etc/kernel/cmdline-linux-hardened.tmpl b/etc/kernel/cmdline-linux-hardened.tmpl index be99a9c..674904f 100644 --- a/etc/kernel/cmdline-linux-hardened.tmpl +++ b/etc/kernel/cmdline-linux-hardened.tmpl @@ -1 +1 @@ -rd.luks.name={{ output "lsblk" "-dno" "UUID" (printf "/dev/%s" .luksRootPartition) | trim }}=root root=/dev/mapper/root rw quiet mem_sleep_default=s2idle +rd.luks.name={{ output "lsblk" "-dno" "UUID" (printf "/dev/%s" .luksRootPartition) | trim }}=root root=/dev/mapper/root rw quiet mem_sleep_default=s2idle apparmor=1 lsm=landlock,lockdown,yama,integrity,apparmor,bpf diff --git a/etc/kernel/cmdline.tmpl b/etc/kernel/cmdline.tmpl index d7512dd..555083a 100644 --- a/etc/kernel/cmdline.tmpl +++ b/etc/kernel/cmdline.tmpl @@ -1 +1 @@ -rd.luks.name={{ output "lsblk" "-dno" "UUID" (printf "/dev/%s" .luksRootPartition) | trim }}=root root=/dev/mapper/root rw quiet +rd.luks.name={{ output "lsblk" "-dno" "UUID" (printf "/dev/%s" .luksRootPartition) | trim }}=root root=/dev/mapper/root rw quiet apparmor=1 lsm=landlock,lockdown,yama,integrity,apparmor,bpf diff --git a/etc/nftables.conf b/etc/nftables.conf index fc2d1e7..465c9f7 100644 --- a/etc/nftables.conf +++ b/etc/nftables.conf @@ -15,6 +15,11 @@ table inet filter { meta l4proto { icmp, icmpv6 } accept comment "allow icmp" meta pkttype multicast udp dport 5353 accept comment "allow mDNS discovery" + iifname "lxdbr0" udp dport 53 accept comment "LXD DNS" + iifname "lxdbr0" tcp dport 53 accept comment "LXD DNS" + iifname "lxdbr0" meta nfproto ipv4 udp dport 67 accept comment "LXD DHCPv4" + iifname "lxdbr0" meta nfproto ipv6 udp dport 547 accept comment "LXD DHCPv6" + pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited counter } @@ -22,5 +27,8 @@ table inet filter { type filter hook forward priority filter policy drop + ct state invalid drop + iifname "lxdbr0" accept comment "allow connections from LXD instances" + oifname "lxdbr0" ct state {established, related} accept comment "allow replies to LXD instances" } } diff --git a/meta/base.txt b/meta/base.txt index 6a12a4a..1e7fda3 100644 --- a/meta/base.txt +++ b/meta/base.txt @@ -1,5 +1,6 @@ # --- core system --- acpid +apparmor arch-audit base btrfs-progs @@ -42,6 +43,10 @@ nix # --- kernel-coupled dev/debug --- perf # running-kernel ABI +# --- system containers and VMs --- +lxd +cdrtools + # --- sound --- alsa-utils pipewire diff --git a/systemd-units/system.txt b/systemd-units/system.txt index f9a44d4..99b71cb 100644 --- a/systemd-units/system.txt +++ b/systemd-units/system.txt @@ -2,6 +2,7 @@ # Enabled by `just unit-apply` via `sudo systemctl enable`. # --- core --- +apparmor.service systemd-timesyncd.service systemd-resolved.service systemd-oomd.service @@ -35,5 +36,8 @@ tor.service # client connect, the .socket is what gets enabled) --- nix-daemon.socket +# --- system containers and VMs --- +lxd.socket + # --- file sync (system template; binary comes from the user's nix profile) --- syncthing@sommerfeld.service |
