From: Daan De Meyer Date: Mon, 10 Feb 2025 20:24:37 +0000 (+0100) Subject: Make VM registration work unprivileged X-Git-Tag: v26~398^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc4df63ef8e681aa46caa2dd828945e175abd645;p=thirdparty%2Fmkosi.git Make VM registration work unprivileged Since systemd 257, it's possible for unprivileged users to register VMs with systemd-machined (gated behind polkit). So let's not require root privileges to register virtual machines with systemd-machined, but only do it as an unprivileged user if registration is explicitly requested to avoid the polkit prompt. To avoid having to detect which systemd version the system is running, we skip errors on varlink registration for now when running unprivileged. https://github.com/systemd/systemd/pull/36344 will allow us to detect if the machined varlink interface can be accessed as an unprivileged user or not. --- diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 7b57f6037..367972b08 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -994,7 +994,6 @@ def machine1_is_available(config: Config) -> bool: env=os.environ | config.environment, sandbox=config.sandbox(relaxed=True), stdout=subprocess.PIPE, - stderr=sys.stderr, ).stdout.strip() ) @@ -1005,28 +1004,33 @@ def finalize_register(config: Config) -> bool: if config.register == ConfigFeature.disabled: return False - if os.getuid() == 0 and ( - Path("/run/systemd/machine/io.systemd.Machine").is_socket() or machine1_is_available(config) - ): - return True + if config.register == ConfigFeature.auto and os.getuid() != 0: + return False - if config.register == ConfigFeature.enabled: - if os.getuid() != 0: - die("Container registration requires root privileges") - else: + # Unprivileged registration via polkit was added after the varlink interface was added, so if the varlink + # interface is not available, we can assume unprivileged registration is not available either. + if ( + not (p := Path("/run/systemd/machine/io.systemd.Machine")).is_socket() + or not os.access(p, os.R_OK | os.W_OK) + ) and (not machine1_is_available(config) or os.getuid() != 0): + if config.register == ConfigFeature.enabled: die( "Container registration was requested but systemd-machined is not available", - hint="Is the systemd-container package installed?", + hint="Is the systemd-container package installed and is systemd-machined running?", ) - return False + return False + + return True def register_machine(config: Config, pid: int, fname: Path, cid: Optional[int]) -> None: if not finalize_register(config): return - if (p := Path("/run/systemd/machine/io.systemd.Machine")).is_socket(): + if (p := Path("/run/systemd/machine/io.systemd.Machine")).is_socket() and os.access( + p, os.R_OK | os.W_OK + ): run( [ "varlinkctl", @@ -1048,9 +1052,14 @@ def register_machine(config: Config, pid: int, fname: Path, cid: Optional[int]) ], env=os.environ | config.environment, sandbox=config.sandbox(relaxed=True), - # Make sure varlinkctl doesn't write to stdout which messes up the terminal. + stdin=sys.stdin, + # Prevent varlinkctl's empty '{}' response from showing up in the terminal. stdout=subprocess.DEVNULL, - stderr=sys.stderr, + # systemd 256 exposes the systemd-machined varlink interface only to the root user, but makes the + # varlink socket world readable/writable, which means this will fail when executed as an + # unprivileged user, so ignore the error in that case. + # TODO: Remove when https://github.com/systemd/systemd/pull/36344 is in a stable release. + check=os.getuid() == 0, ) else: run( @@ -1072,10 +1081,8 @@ def register_machine(config: Config, pid: int, fname: Path, cid: Optional[int]) ], # fmt: skip env=os.environ | config.environment, sandbox=config.sandbox(relaxed=True), - # systemd-machined might not be installed so let's ignore any failures unless running in debug - # mode. - check=ARG_DEBUG.get(), - stderr=None if ARG_DEBUG.get() else subprocess.DEVNULL, + stdin=sys.stdin, + stdout=sys.stdout, )