fix: unblock the Ubuntu 26.04 installer (qemu-kvm, sudo-rs) (#665)

setup.sh aborts twice on resolute. qemu-kvm is a virtual package with two
providers there, so apt refuses to resolve it; the token was redundant anyway
since $QEMU_PACKAGES already installs qemu-system-x86/arm and spinifex execs
the qemu-system-* binaries directly.

sudo-rs, Ubuntu's default sudo since 25.10, then rejects the sudoers file: it
implements no pam_session Defaults setting, and forbids a wildcard inside a
command argument. pam_session is now emitted only for classic sudo, and the
sysctl grant names a fixed-verb helper instead of a wildcard.

The helper is narrower than the rule it replaces: it accepts one interface, a
key restricted to rp_filter or accept_local, and a value restricted to 0 or 1,
where the old grant permitted any net.ipv4.conf key at any value. Granting
sysctl with an unconstrained argument was rejected as root-equivalent.

Adds scripts/lint-apt-packages.sh and an Apt Packages workflow so the runtime
package list is resolved against each supported suite's real indices in CI.
This commit is contained in:
Josh Siviour
2026-08-03 08:57:50 +10:00
committed by GitHub
parent b9898a7e88
commit a2c451128d
11 changed files with 227 additions and 21 deletions
+93 -12
View File
@@ -24,6 +24,9 @@ set -e
INSTALL_SPINIFEX_CHANNEL="${INSTALL_SPINIFEX_CHANNEL:-latest}"
INSTALL_BASE_URL="${INSTALL_BASE_URL:-https://install.mulgadc.com}"
# Referenced by both the sudoers grant and the daemon, so the path is fixed here.
ENDPOINT_SYSCTL_HELPER="/usr/local/lib/spinifex/spinifex-set-endpoint-sysctl"
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -181,15 +184,76 @@ create_service_users() {
}
# --- Install scoped sudoers rules ---
# sudo-rs (Ubuntu's default sudo since 25.10) implements only a subset of
# Defaults and rejects an unknown one outright, so pam_session must be omitted
# there rather than emitted and ignored.
sudo_is_sudo_rs() {
sudo --version 2>/dev/null | head -1 | grep -qi 'sudo-rs'
}
# The daemon only ever sets rp_filter and accept_local on an endpoint it just
# created. A fixed-verb helper keeps that grant expressible without a wildcard,
# which sudo-rs forbids inside command arguments.
install_endpoint_sysctl_helper() {
$SUDO install -d -m 0755 /usr/local/lib/spinifex
$SUDO tee "${ENDPOINT_SYSCTL_HELPER}" > /dev/null << 'HELPER'
#!/bin/sh
# Sets one of two per-endpoint sysctls for the asymmetric IMDS reply path.
# Interface, key and value are all constrained: this runs as root under a
# NOPASSWD grant, so it must never pass an arbitrary key through to sysctl.
set -eu
if [ "$#" -ne 3 ]; then
echo "usage: $0 <interface> <rp_filter|accept_local> <0|1>" >&2
exit 2
fi
iface=$1
key=$2
value=$3
# Reject anything that is not a plain interface name, so the key cannot be
# steered elsewhere in the sysctl tree with a "." or a "/".
case "${iface}" in
''|*[!A-Za-z0-9_-]*) echo "invalid interface: ${iface}" >&2; exit 2 ;;
esac
case "${key}" in
rp_filter|accept_local) ;;
*) echo "key not permitted: ${key}" >&2; exit 2 ;;
esac
case "${value}" in
0|1) ;;
*) echo "value not permitted: ${value}" >&2; exit 2 ;;
esac
exec sysctl -qw "net.ipv4.conf.${iface}.${key}=${value}"
HELPER
$SUDO chown root:root "${ENDPOINT_SYSCTL_HELPER}"
$SUDO chmod 0755 "${ENDPOINT_SYSCTL_HELPER}"
}
install_sudoers() {
stage "installing scoped sudoers rules"
$SUDO tee /etc/sudoers.d/spinifex-network > /dev/null << 'SUDOERS'
install_endpoint_sysctl_helper
if sudo_is_sudo_rs; then
$SUDO tee /etc/sudoers.d/spinifex-network > /dev/null << 'SUDOERS'
# pam_session is omitted: this host runs sudo-rs, which does not implement it.
SUDOERS
else
$SUDO tee /etc/sudoers.d/spinifex-network > /dev/null << 'SUDOERS'
# No PAM session for the service users. Every sudo call otherwise logs
# "pam_limits: Could not set limit for 'core'": the units set LimitCORE=0 and
# their CapabilityBoundingSet omits CAP_SYS_RESOURCE, so pam_limits cannot apply
# the limits.conf default and warns. It also means sudo children inherit the
# unit's rlimits rather than the system defaults, which is the RG-9 intent.
Defaults:spinifex-daemon !pam_session
SUDOERS
fi
$SUDO tee -a /etc/sudoers.d/spinifex-network > /dev/null << 'SUDOERS'
# The OVS/OVN client tools are deliberately absent. They do all their work over
# control sockets that setup-ovn.sh group-owns to `spinifex`, so they run as the
@@ -208,11 +272,16 @@ Defaults:spinifex-daemon !pam_session
# Spinifex daemon: tap devices and OpenFlow rules. Its unit holds no
# CAP_NET_ADMIN, so unlike vpcd it cannot drop these. ovs-ofctl installs the
# per-tap IMDS datapath flows on br-imds; sysctl sets the per-endpoint
# rp_filter/accept_local the asymmetric reply path needs.
# per-tap IMDS datapath flows on br-imds; the sysctl helper sets the
# per-endpoint rp_filter/accept_local the asymmetric reply path needs.
spinifex-daemon ALL=(root) NOPASSWD: /sbin/ip, /usr/sbin/ip
spinifex-daemon ALL=(root) NOPASSWD: /usr/bin/ovs-ofctl
spinifex-daemon ALL=(root) NOPASSWD: /usr/sbin/sysctl -qw net.ipv4.conf.*
SUDOERS
# Separate append so the prose above can stay in a quoted heredoc: it carries
# backticks that an expanding one would run as command substitution.
$SUDO tee -a /etc/sudoers.d/spinifex-network > /dev/null << SUDOERS
spinifex-daemon ALL=(root) NOPASSWD: ${ENDPOINT_SYSCTL_HELPER}
SUDOERS
$SUDO chmod 0440 /etc/sudoers.d/spinifex-network
$SUDO visudo -cf /etc/sudoers.d/spinifex-network || fatal "Invalid sudoers syntax in spinifex-network"
@@ -224,6 +293,18 @@ SUDOERS
# scripts/iso-builder/build/packages.list in the mulga repo. When you add,
# remove, or change a runtime package here, review that file too — drift means
# the ISO and `curl | bash` paths install different software.
#
# Hoisted out of the apt-get call so scripts/lint-apt-packages.sh can resolve the
# same list against each supported suite. The arch-specific qemu package is
# added by install_apt_deps from detect_arch.
APT_RUNTIME_PACKAGES="nbdkit
qemu-utils gdisk ovmf qemu-efi-aarch64 less
libvirt-daemon-system libvirt-clients
pciutils
jq curl iproute2 netcat-openbsd wget unzip xz-utils file
ovn-central ovn-host openvswitch-switch openvswitch-ipsec strongswan-charon dhcpcd-base
chrony"
install_apt_deps() {
stage "installing apt dependencies"
if [ "${INSTALL_SPINIFEX_SKIP_APT}" = "1" ]; then
@@ -232,14 +313,10 @@ install_apt_deps() {
info "Installing system dependencies..."
$SUDO apt-get update -qq
# Unquoted on purpose: both variables are whitespace-separated package lists.
# shellcheck disable=SC2086
DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y -qq \
nbdkit \
$QEMU_PACKAGES qemu-utils gdisk qemu-kvm ovmf qemu-efi-aarch64 less \
libvirt-daemon-system libvirt-clients \
pciutils \
jq curl iproute2 netcat-openbsd wget unzip xz-utils file \
ovn-central ovn-host openvswitch-switch openvswitch-ipsec strongswan-charon dhcpcd-base \
chrony \
$QEMU_PACKAGES $APT_RUNTIME_PACKAGES \
> /dev/null
info "System dependencies installed"
@@ -879,4 +956,8 @@ main() {
fi
}
main "$@"
# Guarded so the package lint can source this file for APT_RUNTIME_PACKAGES
# without running the installer.
if [ "${INSTALL_SPINIFEX_LIB_ONLY:-0}" != "1" ]; then
main "$@"
fi