#!/bin/bash # ===================================================================== # The only thing vmd is allowed to run as root. # # vmd runs as owuivmd and needs four privileged things: start and stop # the VM unit, create a project dataset with a quota, and start and stop # virtiofsd. Granting it `systemctl` or `zfs` outright would be granting # it every unit and every dataset on this host, so it gets this instead: # a fixed verb list, one optional argument, and a pattern that argument # must match before it is used for anything. # # The sudoers entry names this path and nothing else. Everything here is # an array-form command - there is no `eval`, no unquoted expansion and # no shell metacharacter anywhere a project name can reach. # # THE PATTERN IS DUPLICATED ON PURPOSE # # It also lives in vm/vmd/project.py, and tests/test_vm_tasks.py asserts # the two are identical. Importing it would mean root code reading a # module out of a tree that vmd's own user can write to, which is exactly # backwards. Two copies and a test that they agree is the cheaper risk. # ===================================================================== set -euo pipefail PROJECT_RE='^[a-z0-9][a-z0-9-]{0,29}[a-z0-9]$' PROJECTS_DATASET="Pool/vmprojects" # The pool root is filled in by install.sh. This runs under sudo, where # the environment is not preserved, so it cannot read a setting the way # vmd does - the value has to be baked in when it is installed. PROJECTS_ROOT="@POOL_ROOT@/vmprojects" QUOTA="10G" RESERVED=" base iso state run git gitea owuivm " die() { printf 'owuivm-ctl: %s\n' "$*" >&2; exit 64; } check_name() { local n="${1:-}" [[ "$n" =~ $PROJECT_RE ]] || die "'$n' is not a valid project name" [[ "$RESERVED" == *" $n "* ]] && die "'$n' is reserved" return 0 } verb="${1:-}"; shift || true case "$verb" in vm-start) exec /usr/bin/systemctl start owuivm.service ;; vm-stop) exec /usr/bin/systemctl stop owuivm.service ;; vm-status) exec /usr/bin/systemctl is-active owuivm.service ;; # `reset-failed` on its own, so a unit that died once cannot wedge # every later session behind "start request repeated too quickly". vm-reset) exec /usr/bin/systemctl reset-failed owuivm.service ;; project-ensure) name="${1:-}"; check_name "$name" if ! /usr/sbin/zfs list -H -o name -- "$PROJECTS_DATASET/$name" >/dev/null 2>&1; then # Via systemd-run, not directly. `zfs create` MOUNTS the new # dataset, and mount() is not in @system-service - which is the # syscall filter vmd runs under and which every child of vmd # inherits, sudo included. Run directly it dies with SIGSYS, and # "Bad system call" from zfs is a genuinely confusing way to learn # that. systemd-run spawns from PID 1 instead, so this lands in its # own unit outside vmd's cgroup, and vmd's own filter stays tight # rather than being widened to @mount for one call. /usr/bin/systemd-run --quiet --wait --collect \ --unit="owuivm-zfs-create" --property=Type=oneshot \ -- /usr/sbin/zfs create -o "quota=$QUOTA" -- "$PROJECTS_DATASET/$name" fi # setgid so everything the guest writes stays in a group vmd and # virtiofsd can both still read afterwards. chown owuivmd:owuivm -- "$PROJECTS_ROOT/$name" chmod 2770 -- "$PROJECTS_ROOT/$name" printf '%s\n' "$PROJECTS_ROOT/$name" ;; project-usage) name="${1:-}"; check_name "$name" exec /usr/sbin/zfs get -Hp -o value used,quota -- "$PROJECTS_DATASET/$name" ;; vfs-start) name="${1:-}"; check_name "$name" session="${2:-}" [[ "$session" =~ ^[0-9a-f]{32}$ ]] || die "session id is not 32 hex characters" # Type=exec, NOT notify: virtiofsd never sends sd_notify(READY=1), so # systemd waits the full start timeout and then kills a daemon that # was working perfectly - the log says "Waiting for vhost-user socket # connection..." right up until it is terminated. Readiness here is # the socket file appearing, which vmd waits for itself. exec /usr/bin/systemd-run \ --unit="owuivm-virtiofsd" --collect --property=Type=exec \ --property=Restart=no --property=NoNewPrivileges=yes \ --property=ProtectSystem=strict \ --property="ReadWritePaths=/run/owuivm @POOL_ROOT@/vmprojects/$name" \ --property=PrivateNetwork=yes \ -- /usr/libexec/virtiofsd \ --socket-path="/run/owuivm/$session/vfs.sock" \ --socket-group=owuivm \ --shared-dir="$PROJECTS_ROOT/$name" \ --sandbox=namespace \ --modcaps=-mknod:-setfcap # No --xattr: it is a bare flag in virtiofsd 1.10 (there is no # --xattr=false), and extended attributes are off unless it is given. # Leaving them off is what is wanted - an xattr is another thing the # guest can set on a file that then lives on the host's array. ;; vfs-stop) /usr/bin/systemctl stop owuivm-virtiofsd.service >/dev/null 2>&1 || true /usr/bin/systemctl reset-failed owuivm-virtiofsd.service >/dev/null 2>&1 || true ;; *) die "unknown verb '${verb}'" ;; esac