diff options
-rw-r--r-- | doc/guix.texi | 189 | ||||
-rw-r--r-- | gnu/build/activation.scm | 19 | ||||
-rw-r--r-- | gnu/local.mk | 1 | ||||
-rw-r--r-- | gnu/system/accounts.scm | 10 | ||||
-rw-r--r-- | gnu/system/shadow.scm | 211 |
5 files changed, 428 insertions, 2 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index ca74afa3ce..fe84b52052 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -18848,6 +18848,13 @@ Note that the ``root'' account is not included here. It is a special-case and is automatically added whether or not it is specified. @end defvar +@cindex containers, subordinate IDs +The Linux kernel also implements @dfn{subordinate user and group IDs}, +or ``subids'', which are used to map the ID of a user and group to +several IDs inside separate name spaces---inside ``containers''. +@xref{subordinate-user-group-ids, the subordinate user and group ID +service}, for information on how to configure it. + @node Keyboard Layout @section Keyboard Layout @@ -41524,6 +41531,188 @@ Whether to allow grafting or not in the pack build. @c %end of fragment +@anchor{subordinate-user-group-ids} +@cindex subordinate user and group IDs +@cindex subid, subordinate user and group IDs +@subsubheading Subordinate User and Group ID Service + +Among the virtualization facilities implemented by the Linux kernel is the +concept of @dfn{subordinate IDs}. Subordinate IDs allow for mapping user and group +IDs inside process namespaces to user and group IDs of the host system. +Subordinate user ID ranges (subuids) allow users to map virtual user IDs inside +containers to the user ID of an unprivileged user of the host system. +Subordinate group ID ranges (subgids), instead map virtual group IDs to the +group ID of an unprivileged user on the host system. You can access +@code{subuid(5)} and @code{subgid(5)} Linux man pages for more details. + +The @code{(gnu system shadow)} module exposes the +@code{subids-service-type}, its configuration record +@code{subids-configuration} and its extension record +@code{subids-extension}. + +With @code{subids-service-type}, subuids and subgids ranges can be reserved for +users that desire so: + +@lisp +(use-modules (gnu system shadow) ;for 'subids-service-type' + (gnu system accounts) ;for 'subid-range' + @dots{}) + +(operating-system + ;; @dots{} + (services + (list + (simple-service 'alice-bob-subids + subids-service-type + (subids-extension + (subgids + (list + (subid-range (name "alice")))) + (subuids + (list + (subid-range (name "alice")) + (subid-range (name "bob") + (start 100700))))))))) +@end lisp + +Users (definitely other services), usually, are supposed to extend the service +instead of adding subids directly to @code{subids-configuration}, unless the +want to change the default behavior for root. With default settings the +@code{subids-service-type} adds, if it's not already there, a configuration +for the root account to both @file{/etc/subuid} and @file{/etc/subgid}, possibly +starting at the minimum possible subid. Otherwise the root subuids and subgids +ranges are fitted wherever possible. + +The above configuration will yield the following: + +@example +# cat /etc/subgid +root:100000:65536 +alice:165536:65536 +# cat /etc/subuid +root:100000:700 +bob:100700:65536 +alice:166236:65536 +@end example + +@c %start of fragment + +@deftp {Data Type} subids-configuration + +With default settings the +@code{subids-service-type} adds, if it's not already there, a configuration +for the root account to both @file{/etc/subuid} and @file{/etc/subgid}, possibly +starting at the minimum possible subid. To disable the default behavior and +provide your own definition for the root subid ranges you can set to @code{#f} +the @code{add-root?} field: + +@lisp +(use-modules (gnu system shadow) ;for 'subids-service-type' + (gnu system accounts) ;for 'subid-range' + @dots{}) + +(operating-system + ;; @dots{} + (services + (list + (service subids-service-type + (subids-configuration + (add-root? #f) + (subgids + (subid-range (name "root") + (start 120000) + (count 100))) + (subuids + (subid-range (name "root") + (start 120000) + (count 100))))) + (simple-service 'alice-bob-subids + subids-service-type + (subids-extension + (subgids + (list + (subid-range (name "alice")))) + (subuids + (list + (subid-range (name "alice")) + (subid-range (name "bob") + (start 100700))))))))) +@end lisp + +Available @code{subids-configuration} fields are: + +@table @asis +@item @code{add-root?} (default: @code{#t}) (type: boolean) +Whether to automatically configure subuids and subgids for root. + +@item @code{subgids} (default: @code{'()}) (type: list-of-subid-ranges) +The list of @code{subid-range}s that will be serialized to @code{/etc/subgid}. +If a range doesn't specify a start it will be fitted based on its number of +requrested subids. If a range doesn't specify a count the default size +of 65536 will be assumed. + +@item @code{subuids} (default: @code{'()}) (type: list-of-subid-ranges) +The list of @code{subid-range}s that will be serialized to @code{/etc/subuid}. +If a range doesn't specify a start it will be fitted based on its number of +requrested subids. If a range doesn't specify a count the default size +of 65536 will be assumed. + +@end table + +@end deftp + +@c %end of fragment + +@c %start of fragment + +@deftp {Data Type} subids-extension + +Available @code{subids-extension} fields are: + +@table @asis + +@item @code{subgids} (default: @code{'()}) (type: list-of-subid-ranges) +The list of @code{subid-range}s that will be appended to +@code{subids-configuration-subgids}. Entries with the same name are deduplicated +upon merging. + +@item @code{subuids} (default: @code{'()}) (type: list-of-subid-ranges) +The list of @code{subid-range}s that will be appended to +@code{subids-configuration-subuids}. Entries with the same name are deduplicated +upon merging. + +@end table + +@end deftp + +@c %end of fragment + +@c %start of fragment + +@deftp {Data Type} subid-range + +The @code{subid-range} record is defined at @code{(gnu system accounts)}. +Available fields are: + +@table @asis + +@item @code{name} (type: string) +The name of the user or group that will own this range. + +@item @code{start} (default: @code{#f}) (type: integer) +The first requested subid. When false the first available subid with enough +contiguous subids will be assigned. + +@item @code{count} (default: @code{#f}) (type: integer) +The number of total allocated subids. When #f the default of 65536 will be +assumed . + +@end table + +@end deftp + +@c %end of fragment + @cindex Audit @subsubheading Auditd Service diff --git a/gnu/build/activation.scm b/gnu/build/activation.scm index a450578c24..11f7c82d67 100644 --- a/gnu/build/activation.scm +++ b/gnu/build/activation.scm @@ -10,6 +10,7 @@ ;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re> ;;; Copyright © 2022 Tobias Geerinckx-Rice <me@tobias.gr> ;;; Copyright © 2024 Nicolas Graves <ngraves@ngraves.fr> +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> ;;; ;;; This file is part of GNU Guix. ;;; @@ -40,6 +41,7 @@ #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:export (activate-users+groups + activate-subuids+subgids activate-user-home activate-etc activate-privileged-programs @@ -229,6 +231,23 @@ group records) are all available." (chmod directory #o555)) (duplicates (map user-account-home-directory system-accounts)))) +(define (activate-subuids+subgids subuids subgids) + "Make sure SUBUIDS (a list of subid range records) and SUBGIDS (a list of +subid range records) are all available." + + ;; Take same lock as Shadow while we read + ;; and write the databases. This ensures there's no race condition with + ;; other tools that might be accessing it at the same time. + (with-file-lock "/etc/subgid.lock" + (let-values (((subuid subgid) + (subuid+subgid-databases subuids subgids))) + (write-subgid subgid))) + + (with-file-lock "/etc/subuid.lock" + (let-values (((subuid subgid) + (subuid+subgid-databases subuids subgids))) + (write-subuid subuid)))) + (define (activate-user-home users) "Create and populate the home directory of USERS, a list of tuples, unless they already exist." diff --git a/gnu/local.mk b/gnu/local.mk index e3539e0068..d5be5e0198 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -851,6 +851,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/samba.scm \ %D%/tests/security.scm \ %D%/tests/security-token.scm \ + %D%/tests/shadow.scm \ %D%/tests/singularity.scm \ %D%/tests/ssh.scm \ %D%/tests/telephony.scm \ diff --git a/gnu/system/accounts.scm b/gnu/system/accounts.scm index 1b88ca301f..f63d7f96bd 100644 --- a/gnu/system/accounts.scm +++ b/gnu/system/accounts.scm @@ -51,6 +51,7 @@ sexp->user-account sexp->user-group + sexp->subid-range default-shell)) @@ -159,3 +160,12 @@ user-account record." (create-home-directory? create-home-directory?) (shell shell) (password password) (system? system?))))) + +(define (sexp->subid-range sexp) + "Take SEXP, a tuple as returned by 'subid-range->gexp', and turn it into a +subid-range record." + (match sexp + ((name start count) + (subid-range (name name) + (start start) + (count count))))) diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm index d9f13271d8..48eca2564f 100644 --- a/gnu/system/shadow.scm +++ b/gnu/system/shadow.scm @@ -4,6 +4,7 @@ ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ;;; Copyright © 2020, 2023 Efraim Flashner <efraim@flashner.co.il> ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com> +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> ;;; ;;; This file is part of GNU Guix. ;;; @@ -28,6 +29,10 @@ #:use-module (guix modules) #:use-module (guix sets) #:use-module (guix ui) + #:use-module ((gnu build accounts) + #:select (%subordinate-id-count + %subordinate-id-max + %subordinate-id-min)) #:use-module (gnu system accounts) #:use-module (gnu services) #:use-module (gnu services shepherd) @@ -77,7 +82,20 @@ %base-user-accounts account-service-type - account-service)) + account-service + + subids-configuration + subids-configuration? + subids-configuration-add-root? + subids-configuration-subgids + subids-configuration-subuids + + subids-extension + subids-extension? + subids-extension-subgids + subids-extension-subuids + + subids-service-type)) ;;; Commentary: ;;; @@ -380,7 +398,7 @@ of user '~a' is undeclared") ;;; -;;; Service. +;;; Accounts Service. ;;; (define (user-group->gexp group) @@ -521,4 +539,193 @@ ACCOUNTS+GROUPS as its initial list of accounts and groups." (service account-service-type (append skeletons accounts+groups))) + +;;; +;;; Subids Service. +;;; + +(define* (%root-subid #:optional (start %subordinate-id-min) (count %subordinate-id-count)) + (subid-range + (name "root") + (start start) + (count count))) + +(define-record-type* <subids-configuration> + subids-configuration make-subids-configuration + subids-configuration? + this-subids-configuration + + (add-root? subids-configuration-add-root? ; boolean + (default #t)) + (subgids subids-configuration-subgids ; list of <subid-range> + (default '())) + (subuids subids-configuration-subuids ; list of <subid-range> + (default '()))) + +(define (subid-range->gexp range) + "Turn RANGE, a <subid-range> object, into a list-valued gexp suitable for +'activate-subuids+subgids'." + (define count (subid-range-count range)) + #~`(#$(subid-range-name range) + #$(subid-range-start range) + #$(if (and (number? count) + (> count 0)) + count + %subordinate-id-count))) + +(define (assert-valid-subids ranges) + (cond ((>= (fold + 0 (map subid-range-count ranges)) + (- %subordinate-id-max %subordinate-id-min -1)) + (raise + (formatted-message + (G_ + "The configured ranges are more than the ~a max allowed.") + (- %subordinate-id-max %subordinate-id-min -1)))) + ((any (lambda (r) + (define start (subid-range-start r)) + (and start + (< start %subordinate-id-min))) + ranges) + (raise + (formatted-message + (G_ + "One subid-range starts before the minimum allowed sub id ~a.") + %subordinate-id-min))) + ((any (lambda (r) + (define end (subid-range-end r)) + (and end + (> end %subordinate-id-max))) + ranges) + (raise + (formatted-message + (G_ + "One subid-range ends after the maximum allowed sub id ~a.") + %subordinate-id-max))) + ((any (compose null? subid-range-name) + ranges) + (raise + (formatted-message + (G_ + "One subid-range has a null name.")))) + ((any (compose string-null? subid-range-name) + ranges) + (raise + (formatted-message + (G_ + "One subid-range has a name equal to the empty string.")))) + (else #t))) + +(define (delete-duplicate-ranges ranges) + (delete-duplicates ranges + (lambda args + (apply string=? (map subid-range-name ranges))))) + +(define (subids-activation config) + "Return a gexp that activates SUBUIDS+SUBGIDS, a list of <subid-range> +objects." + (define (add-root-when-missing ranges) + (define sorted-ranges + (sort-list ranges subid-range-less)) + (define root-missing? + (not + (find (lambda (r) + (string=? "root" + (subid-range-name r))) + sorted-ranges))) + (define first-start + (and (> (length sorted-ranges) 0) + (subid-range-start (first sorted-ranges)))) + (define first-has-start? + (number? first-start)) + (define root-start + (if first-has-start? + (and + (> first-start %subordinate-id-min) + %subordinate-id-min) + %subordinate-id-min)) + (define root-count + (if first-has-start? + (- first-start %subordinate-id-min) + %subordinate-id-count)) + (if (and root-missing? + (subids-configuration-add-root? config)) + (append (list (%root-subid root-start root-count)) + sorted-ranges) + sorted-ranges)) + + (define subuids + (delete-duplicate-ranges (subids-configuration-subuids config))) + + (define subuids-specs + (map subid-range->gexp (add-root-when-missing subuids))) + + (define subgids + (delete-duplicate-ranges (subids-configuration-subgids config))) + + (define subgids-specs + (map subid-range->gexp (add-root-when-missing subgids))) + + (assert-valid-subids subgids) + (assert-valid-subids subuids) + + ;; Add subuids and subgids. + (with-imported-modules (source-module-closure '((gnu system accounts))) + #~(begin + (use-modules (gnu system accounts)) + + (activate-subuids+subgids (map sexp->subid-range (list #$@subuids-specs)) + (map sexp->subid-range (list #$@subgids-specs)))))) + +(define-record-type* <subids-extension> + subids-extension make-subids-extension + subids-extension? + this-subids-extension + + (subgids subids-extension-subgids ; list of <subid-range> + (default '())) + (subuids subids-extension-subuids ; list of <subid-range> + (default '()))) + +(define append-subid-ranges + (lambda args + (delete-duplicate-ranges + (apply append args)))) + +(define (subids-extension-merge a b) + (subids-extension + (subgids (append-subid-ranges + (subids-extension-subgids a) + (subids-extension-subgids b))) + (subuids (append-subid-ranges + (subids-extension-subuids a) + (subids-extension-subuids b))))) + +(define subids-service-type + (service-type (name 'subids) + ;; Concatenate <subid-range> lists. + (compose (lambda (args) + (fold subids-extension-merge + (subids-extension) + args))) + (extend + (lambda (config extension) + (subids-configuration + (inherit config) + (subgids + (append-subid-ranges + (subids-configuration-subgids config) + (subids-extension-subgids extension))) + (subuids + (append-subid-ranges + (subids-configuration-subuids config) + (subids-extension-subuids extension)))))) + (extensions + (list (service-extension activation-service-type + subids-activation))) + (default-value + (subids-configuration)) + (description + "Ensure the specified sub UIDs and sub GIDs exist in +/etc/subuid and /etc/subgid."))) + ;;; shadow.scm ends here |