summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorsommerfeld <sommerfeld@sommerfeld.dev>2026-09-17 15:05:37 +0100
committersommerfeld <sommerfeld@sommerfeld.dev>2026-09-17 15:05:37 +0100
commit2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d (patch)
tree95cbecc698a82ef8b9d9d31cbff75be91f7d9005
parentdd45f3ad88b544f065f27143801306a7d6dffd02 (diff)
downloaddotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.tar.gz
dotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.tar.bz2
dotfiles-2f97417d16224ca1f79b2fe41e70b8d7ddc0ab7d.zip
Configure LXD and AppArmor on the Arch host
-rw-r--r--docs/lxd-init.yaml28
-rw-r--r--docs/lxd.md106
-rw-r--r--etc/kernel/cmdline-linux-hardened.tmpl2
-rw-r--r--etc/kernel/cmdline.tmpl2
-rw-r--r--etc/nftables.conf8
-rw-r--r--meta/base.txt5
-rw-r--r--systemd-units/system.txt4
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