mirror of
https://codeberg.org/guix/guix.git
synced 2026-01-25 12:05:19 -06:00
services: guix: Allow ‘guix-daemon’ to run without root privileges.
* gnu/services/base.scm (run-with-writable-store) (guix-ownership-change-program): New procedures. (<guix-configuration>)[privileged?]: New field. (guix-shepherd-service): Rename to… (guix-shepherd-services): … this. Add the ‘guix-ownership’ service. Change ‘guix-daemon’ service to depend on it; when unprivileged, prefix ‘daemon-command’ by ‘run-with-writable-store’ and omit ‘--build-users-group’; adjust socket activation endpoints. (guix-accounts): When unprivileged, create the “guix-daemon” user and group in addition to the others. (guix-service-type)[extensions]: Adjust to name change. * gnu/tests/base.scm (run-guix-daemon-test): Add ‘name’ parameter. (%test-guix-daemon): Adjust accordingly. (%test-guix-daemon-unprivileged): New test. * doc/guix.texi (Base Services): Document ‘privileged?’. (Migrating to the Unprivileged Daemon): Explain that this is automatic on Guix System. Reviewed-by: Maxim Cournoyer <maxim.cournoyer@gmail.com> Change-Id: I28a9a22e617416c551dccb24e43a253b544ba163
This commit is contained in:
parent
2c7c059e0b
commit
e2583b5a17
3 changed files with 253 additions and 20 deletions
|
|
@ -1037,6 +1037,14 @@ number of steps must be taken: creating a new dedicated
|
|||
files to @code{guix-daemon}, and ensuring that the @command{guix-daemon}
|
||||
program runs as @code{guix-daemon}.
|
||||
|
||||
On Guix System, these steps are carried out automatically when you set
|
||||
the @code{privileged?} field of the @code{guix-configuration} record to
|
||||
@code{#f} and reconfigure (@pxref{guix-configuration-type,
|
||||
@code{guix-configuration}}).
|
||||
|
||||
However, on a foreign distribution, the process is manual. The
|
||||
following paragraphs describe what you need to do.
|
||||
|
||||
@quotation Warning
|
||||
Follow the instructions below only after making sure you have a recent
|
||||
version of @command{guix-daemon} with support for unprivileged
|
||||
|
|
@ -20109,6 +20117,36 @@ This data type represents the configuration of the Guix build daemon.
|
|||
The Guix package to use. @xref{Customizing the System-Wide Guix} to
|
||||
learn how to provide a package with a pre-configured set of channels.
|
||||
|
||||
@cindex unprivileged @command{guix-daemon}
|
||||
@cindex rootless @command{guix-daemon}
|
||||
@item @code{privileged?} (default: @code{#t})
|
||||
Whether to run @command{guix-daemon} as root.
|
||||
|
||||
When true, @command{guix-daemon} runs with root privileges and build
|
||||
processes run under unprivileged user accounts as specified by
|
||||
@code{build-group} and @code{build-accounts} (see below); when false,
|
||||
@command{guix-daemon} run as the @code{guix-daemon} user, which is
|
||||
unprivileged, and so do build processes. The unprivileged or
|
||||
``rootless'' mode can reduce the impact of some classes of
|
||||
vulnerabilities that could affect the daemon.
|
||||
|
||||
The default is currently @code{#t} (@command{guix-daemon} runs with root
|
||||
privileges) but may eventually be changed to @code{#f}.
|
||||
|
||||
@quotation Warning
|
||||
When changing this option, @file{/gnu/store}, @file{/var/guix}, and
|
||||
@file{/etc/guix} have their ownership automatically changed by the
|
||||
@code{guix-ownership} service to either the @code{guix-daemon} user or
|
||||
the @code{root} user (@pxref{unprivileged-daemon-migration}).
|
||||
|
||||
This can take a while, especially if @file{/gnu/store} is big; it cannot
|
||||
be interrupted and @command{guix-daemon} cannot be used until it has
|
||||
completed.
|
||||
@end quotation
|
||||
|
||||
@xref{Build Environment Setup}, for more information on the two ways to
|
||||
run @command{guix-daemon}.
|
||||
|
||||
@item @code{build-group} (default: @code{"guixbuild"})
|
||||
Name of the group for build user accounts.
|
||||
|
||||
|
|
|
|||
|
|
@ -1918,6 +1918,102 @@ archive' public keys, with GUIX."
|
|||
#$machines))
|
||||
machines-file))))
|
||||
|
||||
(define (run-with-writable-store)
|
||||
"Return a wrapper that runs the given command under the specified UID and
|
||||
GID in a context where the store is writable, even if it was bind-mounted
|
||||
read-only via %IMMUTABLE-STORE (this wrapper must run as root)."
|
||||
(program-file "run-with-writable-store"
|
||||
(with-imported-modules (source-module-closure
|
||||
'((guix build syscalls)))
|
||||
#~(begin
|
||||
(use-modules (guix build syscalls)
|
||||
(ice-9 match))
|
||||
|
||||
(define (ensure-writable-store store)
|
||||
;; Create a new mount namespace and remount STORE with
|
||||
;; write permissions if it's read-only.
|
||||
(unshare CLONE_NEWNS)
|
||||
(let ((fs (statfs store)))
|
||||
(unless (zero? (logand (file-system-mount-flags fs)
|
||||
ST_RDONLY))
|
||||
(mount store store "none"
|
||||
(logior MS_BIND MS_REMOUNT)))))
|
||||
|
||||
(match (command-line)
|
||||
((_ user group command args ...)
|
||||
(ensure-writable-store #$(%store-prefix))
|
||||
(let ((uid (or (string->number user)
|
||||
(passwd:uid (getpwnam user))))
|
||||
(gid (or (string->number group)
|
||||
(group:gid (getgrnam group)))))
|
||||
(setgroups #())
|
||||
(setgid gid)
|
||||
(setuid uid)
|
||||
(apply execl command command args))))))))
|
||||
|
||||
(define (guix-ownership-change-program)
|
||||
"Return a program that changes ownership of the store and other data files
|
||||
of Guix to the given UID and GID."
|
||||
(program-file
|
||||
"validate-guix-ownership"
|
||||
(with-imported-modules (source-module-closure
|
||||
'((guix build utils)))
|
||||
#~(begin
|
||||
(use-modules (guix build utils)
|
||||
(ice-9 ftw)
|
||||
(ice-9 match))
|
||||
|
||||
(define (lchown file uid gid)
|
||||
(let ((parent (open (dirname file) O_DIRECTORY)))
|
||||
(chown-at parent (basename file) uid gid
|
||||
AT_SYMLINK_NOFOLLOW)
|
||||
(close-port parent)))
|
||||
|
||||
(define (change-ownership directory uid gid)
|
||||
;; chown -R UID:GID DIRECTORY
|
||||
(file-system-fold (const #t) ;enter?
|
||||
(lambda (file stat result) ;leaf
|
||||
(if (eq? 'symlink (stat:type stat))
|
||||
(lchown file uid gid)
|
||||
(chown file uid gid)))
|
||||
(const #t) ;down
|
||||
(lambda (directory stat result) ;up
|
||||
(chown directory uid gid))
|
||||
(const #t) ;skip
|
||||
(lambda (file stat errno result)
|
||||
(format (current-error-port)
|
||||
"i/o error: ~a: ~a~%"
|
||||
file (strerror errno))
|
||||
#f)
|
||||
#t ;seed
|
||||
directory
|
||||
lstat))
|
||||
|
||||
(define (claim-data-ownership uid gid)
|
||||
(format #t "Changing file ownership for /gnu/store \
|
||||
and data directories to ~a:~a...~%"
|
||||
uid gid)
|
||||
(change-ownership #$(%store-prefix) uid gid)
|
||||
(let ((excluded '("." ".." "profiles" "userpool")))
|
||||
(for-each (lambda (directory)
|
||||
(change-ownership (in-vicinity "/var/guix" directory)
|
||||
uid gid))
|
||||
(scandir "/var/guix"
|
||||
(lambda (file)
|
||||
(not (member file
|
||||
excluded))))))
|
||||
(chown "/var/guix" uid gid)
|
||||
(change-ownership "/etc/guix" uid gid)
|
||||
(mkdir-p "/var/log/guix")
|
||||
(change-ownership "/var/log/guix" uid gid))
|
||||
|
||||
(match (command-line)
|
||||
((_ (= string->number (? integer? uid))
|
||||
(= string->number (? integer? gid)))
|
||||
(setlocale LC_ALL "C.UTF-8") ;for file name decoding
|
||||
(setvbuf (current-output-port) 'line)
|
||||
(claim-data-ownership uid gid)))))))
|
||||
|
||||
(define-record-type* <guix-configuration>
|
||||
guix-configuration make-guix-configuration
|
||||
guix-configuration?
|
||||
|
|
@ -1959,6 +2055,8 @@ archive' public keys, with GUIX."
|
|||
(default #f))
|
||||
(tmpdir guix-tmpdir ;string | #f
|
||||
(default #f))
|
||||
(privileged? guix-configuration-privileged?
|
||||
(default #t))
|
||||
(build-machines guix-configuration-build-machines ;list of gexps | '()
|
||||
(default '()))
|
||||
(environment guix-configuration-environment ;list of strings
|
||||
|
|
@ -2021,7 +2119,7 @@ proxy of 'guix-daemon'...~%")
|
|||
(environ environment)
|
||||
#t)))))
|
||||
|
||||
(define (guix-shepherd-service config)
|
||||
(define (guix-shepherd-services config)
|
||||
"Return a <shepherd-service> for the Guix daemon service with CONFIG."
|
||||
(define locales
|
||||
(let-system (system target)
|
||||
|
|
@ -2030,16 +2128,57 @@ proxy of 'guix-daemon'...~%")
|
|||
glibc-utf8-locales)))
|
||||
|
||||
(match-record config <guix-configuration>
|
||||
(guix build-group build-accounts chroot? authorize-key? authorized-keys
|
||||
(guix privileged?
|
||||
build-group build-accounts chroot? authorize-key? authorized-keys
|
||||
use-substitutes? substitute-urls max-silent-time timeout
|
||||
log-compression discover? extra-options log-file
|
||||
http-proxy tmpdir chroot-directories environment
|
||||
socket-directory-permissions socket-directory-group
|
||||
socket-directory-user)
|
||||
(list (shepherd-service
|
||||
(provision '(guix-ownership))
|
||||
(requirement '(user-processes user-homes))
|
||||
(one-shot? #t)
|
||||
(start #~(lambda ()
|
||||
(let* ((store #$(%store-prefix))
|
||||
(stat (lstat store))
|
||||
(privileged? #$(guix-configuration-privileged?
|
||||
config))
|
||||
(change-ownership #$(guix-ownership-change-program))
|
||||
(with-writable-store #$(run-with-writable-store)))
|
||||
;; Check whether we're switching from privileged to
|
||||
;; unprivileged guix-daemon, or vice versa, and adjust
|
||||
;; file ownership accordingly. Spawn a child process
|
||||
;; if and only if something needs to be changed.
|
||||
;;
|
||||
;; Note: This service remains in 'starting' state for
|
||||
;; as long as CHANGE-OWNERSHIP is running. That way,
|
||||
;; 'guix-daemon' starts only once we're done.
|
||||
(cond ((and (not privileged?)
|
||||
(or (zero? (stat:uid stat))
|
||||
(zero? (stat:gid stat))))
|
||||
(let ((user (getpwnam "guix-daemon")))
|
||||
(format #t "Changing to unprivileged guix-daemon.~%")
|
||||
(zero?
|
||||
(system* with-writable-store "0" "0"
|
||||
change-ownership
|
||||
(number->string (passwd:uid user))
|
||||
(number->string (passwd:gid user))))))
|
||||
((and privileged?
|
||||
(and (not (zero? (stat:uid stat)))
|
||||
(not (zero? (stat:gid stat)))))
|
||||
(format #t "Changing to privileged guix-daemon.~%")
|
||||
(zero? (system* with-writable-store "0" "0"
|
||||
change-ownership "0" "0")))
|
||||
(else #t)))))
|
||||
(documentation "Ensure that the store and other data files used by
|
||||
guix-daemon have the right ownership."))
|
||||
|
||||
(shepherd-service
|
||||
(documentation "Run the Guix daemon.")
|
||||
(provision '(guix-daemon))
|
||||
(requirement `(user-processes
|
||||
guix-ownership
|
||||
,@(if discover? '(avahi-daemon) '())))
|
||||
(actions (list shepherd-set-http-proxy-action
|
||||
shepherd-discover-action))
|
||||
|
|
@ -2063,8 +2202,15 @@ proxy of 'guix-daemon'...~%")
|
|||
(or (getenv "discover") #$discover?))
|
||||
|
||||
(define daemon-command
|
||||
(cons* #$(file-append guix "/bin/guix-daemon")
|
||||
"--build-users-group" #$build-group
|
||||
(cons* #$@(if privileged?
|
||||
#~()
|
||||
#~(#$(run-with-writable-store)
|
||||
"guix-daemon" "guix-daemon"))
|
||||
|
||||
#$(file-append guix "/bin/guix-daemon")
|
||||
#$@(if privileged?
|
||||
#~("--build-users-group" #$build-group)
|
||||
#~())
|
||||
"--max-silent-time"
|
||||
#$(number->string max-silent-time)
|
||||
"--timeout" #$(number->string timeout)
|
||||
|
|
@ -2145,9 +2291,11 @@ proxy of 'guix-daemon'...~%")
|
|||
"/var/guix/daemon-socket/socket")
|
||||
#:name "socket"
|
||||
#:socket-owner
|
||||
(or #$socket-directory-user 0)
|
||||
(or #$socket-directory-user
|
||||
#$(if privileged? 0 "guix-daemon"))
|
||||
#:socket-group
|
||||
(or #$socket-directory-group 0)
|
||||
(or #$socket-directory-group
|
||||
#$(if privileged? 0 "guix-daemon"))
|
||||
#:socket-directory-permissions
|
||||
#$socket-directory-permissions)))
|
||||
((make-systemd-constructor daemon-command
|
||||
|
|
@ -2162,15 +2310,31 @@ proxy of 'guix-daemon'...~%")
|
|||
|
||||
(define (guix-accounts config)
|
||||
"Return the user accounts and user groups for CONFIG."
|
||||
(cons (user-group
|
||||
(name (guix-configuration-build-group config))
|
||||
(system? #t)
|
||||
`(,@(if (guix-configuration-privileged? config)
|
||||
'()
|
||||
(list (user-group (name "guix-daemon") (system? #t))
|
||||
(user-account
|
||||
(name "guix-daemon")
|
||||
(group "guix-daemon")
|
||||
(system? #t)
|
||||
(supplementary-groups '("kvm"))
|
||||
(comment "Guix Daemon User")
|
||||
(home-directory "/var/empty")
|
||||
(shell (file-append shadow "/sbin/nologin")))))
|
||||
|
||||
;; Use a fixed GID so that we can create the store with the right
|
||||
;; owner.
|
||||
(id 30000))
|
||||
(guix-build-accounts (guix-configuration-build-accounts config)
|
||||
#:group (guix-configuration-build-group config))))
|
||||
;; When reconfiguring from privileged to unprivileged, the running daemon
|
||||
;; (privileged) relies on the availability of the build accounts and build
|
||||
;; group until 'guix system reconfigure' has completed. The simplest way
|
||||
;; to meet this requirement is to create these accounts unconditionally so
|
||||
;; they are not removed in the middle of the 'reconfigure' process.
|
||||
,(user-group
|
||||
(name (guix-configuration-build-group config))
|
||||
(system? #t)
|
||||
|
||||
;; Use a fixed GID so that we can create the store with the right owner.
|
||||
(id 30000))
|
||||
,@(guix-build-accounts (guix-configuration-build-accounts config)
|
||||
#:group (guix-configuration-build-group config))))
|
||||
|
||||
(define (guix-activation config)
|
||||
"Return the activation gexp for CONFIG."
|
||||
|
|
@ -2228,7 +2392,7 @@ proxy of 'guix-daemon'...~%")
|
|||
(service-type
|
||||
(name 'guix)
|
||||
(extensions
|
||||
(list (service-extension shepherd-root-service-type guix-shepherd-service)
|
||||
(list (service-extension shepherd-root-service-type guix-shepherd-services)
|
||||
(service-extension account-service-type guix-accounts)
|
||||
(service-extension activation-service-type guix-activation)
|
||||
(service-extension profile-service-type
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
;;; GNU Guix --- Functional package management for GNU
|
||||
;;; Copyright © 2016-2020, 2022, 2024 Ludovic Courtès <ludo@gnu.org>
|
||||
;;; Copyright © 2016-2020, 2022, 2024-2025 Ludovic Courtès <ludo@gnu.org>
|
||||
;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
|
||||
;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
|
||||
;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
|
||||
|
|
@ -63,7 +63,8 @@
|
|||
|
||||
%hello-dependencies-manifest
|
||||
guix-daemon-test-cases
|
||||
%test-guix-daemon))
|
||||
%test-guix-daemon
|
||||
%test-guix-daemon-unprivileged))
|
||||
|
||||
(define %simple-os
|
||||
(simple-operating-system))
|
||||
|
|
@ -1121,7 +1122,7 @@ test."
|
|||
(system-error-errno args)))
|
||||
#$marionette))))
|
||||
|
||||
(define (run-guix-daemon-test os)
|
||||
(define (run-guix-daemon-test os name)
|
||||
(define test-image
|
||||
(image (operating-system os)
|
||||
(format 'compressed-qcow2)
|
||||
|
|
@ -1168,7 +1169,7 @@ test."
|
|||
|
||||
(test-end))))
|
||||
|
||||
(gexp->derivation "guix-daemon-test" test))
|
||||
(gexp->derivation name test))
|
||||
|
||||
(define %test-guix-daemon
|
||||
(system-test
|
||||
|
|
@ -1190,4 +1191,34 @@ test."
|
|||
%base-user-accounts)))
|
||||
#:imported-modules '((gnu services herd)
|
||||
(guix combinators)))))
|
||||
(run-guix-daemon-test os)))))
|
||||
(run-guix-daemon-test os "guix-daemon-test")))))
|
||||
|
||||
(define %test-guix-daemon-unprivileged
|
||||
(system-test
|
||||
(name "guix-daemon-unprivileged")
|
||||
(description
|
||||
"Test 'guix-daemon' behavior on a multi-user system, where 'guix-daemon'
|
||||
runs unprivileged.")
|
||||
(value
|
||||
(let ((os (marionette-operating-system
|
||||
(let ((base (operating-system-with-gc-roots
|
||||
%daemon-os
|
||||
(list (profile
|
||||
(name "hello-build-dependencies")
|
||||
(content %hello-dependencies-manifest))))))
|
||||
(operating-system
|
||||
(inherit base)
|
||||
(kernel-arguments '("console=ttyS0"))
|
||||
(users (cons (user-account
|
||||
(name "user")
|
||||
(group "users"))
|
||||
%base-user-accounts))
|
||||
(services
|
||||
(modify-services (operating-system-user-services base)
|
||||
(guix-service-type
|
||||
config => (guix-configuration
|
||||
(inherit config)
|
||||
(privileged? #f)))))))
|
||||
#:imported-modules '((gnu services herd)
|
||||
(guix combinators)))))
|
||||
(run-guix-daemon-test os "guix-daemon-unprivileged-test")))))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue