From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms13.migadu.com with LMTPS id mNrjMEW93GaP9QAA62LTzQ:P1 (envelope-from ) for ; Sat, 07 Sep 2024 20:53:25 +0000 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1.migadu.com with LMTPS id mNrjMEW93GaP9QAA62LTzQ (envelope-from ) for ; Sat, 07 Sep 2024 22:53:25 +0200 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=debbugs.gnu.org header.s=debbugs-gnu-org header.b=NSGOi+WU; dkim=fail ("body hash did not verify") header.d=autistici.org header.s=stigmate header.b=JO+Z68u1; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1725742405; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:resent-cc: resent-from:resent-sender:resent-message-id:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=ozK2DqB4bn1vjnDNk44pCWzyAz9bqvtrcOtupjEj2Fc=; b=sp7++wA/JxBVwpdjnMd2HIxVdeszWcsVmRfLey1C3WwSeQwHXJ6y/hfE8gBoTqWuezFLSL 8p2in5OFYCsYCy7ibYED8XWVjgNcKxtm8wdMLYp0t+YjBGDCmkO9B7o/TNHW2n1G9HaP5t 2X8p+6xh5+EBRg+/WWsV6GKdqVRBAkuA6R4mgdgf7ukwYUNaTKXEcXI+sF6qCitLf6FewG aMPkJYIf636Dof8hXNrcpkbmHfKrU7aAdO7AsaC+DHn3FRzQgp+CqECOjN46+hkUyRdXI2 CQ8XxC/7WVzPxy0EldUyfg777ZiMXdiHdBehFuTC2dySVpPBZ83Fr86v40u7Ew== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1725742405; a=rsa-sha256; cv=none; b=LxajQ4rMAsJ34BDINqikz5SUBpritZ+VxR9gSM0ZefDQU+sTZMZx/9DdcY98dZUiFUrfoW A67slw+sUZXck1tfNhHsZfOT6rv9iWFUSn4aLSRPiZRmRwSBKwHouPJkaE0P+wKyMfSp++ RKCBo0+QhXniw0N/Q4DDK4EXzk9XC1pu0gk1NvrAEDyw43qExjLPvhLboo41nbUYnOY3tb /SfnP/BPRM1HtHqCZlukkrwqP7oQQHolvrshXUA3D+j+ggq6d6eRW3rO8lsP65ZLk9u6Q/ ABBQ7GJ77gBbsKeQ5+qyBEpOQ1WABYyK44cINq1SEkiVfs65P+llbwQr9sco1Q== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=debbugs.gnu.org header.s=debbugs-gnu-org header.b=NSGOi+WU; dkim=fail ("body hash did not verify") header.d=autistici.org header.s=stigmate header.b=JO+Z68u1; dmarc=pass (policy=none) header.from=gnu.org; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id EB77373CD2 for ; Sat, 07 Sep 2024 22:53:24 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sn2Qb-0004cn-Fr; Sat, 07 Sep 2024 16:53:05 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sn2Qa-0004cS-QM for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:04 -0400 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sn2Qa-0001wi-FY; Sat, 07 Sep 2024 16:53:04 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debbugs.gnu.org; s=debbugs-gnu-org; h=MIME-Version:References:In-Reply-To:Date:From:To:Subject; bh=v01i0/jI6+YBJY4LQkaWgUGU5YX2R8Tom7IxU30tvrU=; b=NSGOi+WUV47XtS7K7kj9lubvUnFkZpt0uwR2uP/yI4HyPO1dvng53T818Mb/s9sehZ4LfbPeVsGWeeFGRAm5TUVOoOK9gSXq10CW7VszxuOS44jhGkQmzTZ7tcp4jP+fOzUzh1MxuisBEgeKU8OqRJB5iSbqq3AWI/IpACU+EKI4EDkYCtaYgNjqL7crbiw018YDRtAmhq+A2NjS7+HGROMZJUpd1ZuIkG4JzaakQZTENoklAyozR0Gtqwi/K0xlRlH0uykgoL5Ruh1p/FPZWqNIdXgnObiW2rz1iHHKAEzBwIBy3KuC4hV2NAwvPXwQ+E9UW3sA5ShQ/0M623yErw==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sn2QZ-0003gW-9O; Sat, 07 Sep 2024 16:53:03 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH v4 3/3] system: Add /etc/subuid and /etc/subgid support. Resent-From: Giacomo Leidi Original-Sender: "Debbugs-submit" Resent-CC: pelzflorian@pelzflorian.de, ludo@gnu.org, maxim.cournoyer@gmail.com, guix-patches@gnu.org Resent-Date: Sat, 07 Sep 2024 20:53:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 72337 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: 72337@debbugs.gnu.org Cc: Giacomo Leidi , Florian Pelz , Ludovic =?UTF-8?Q?Court=C3=A8s?= , Maxim Cournoyer X-Debbugs-Original-Xcc: Florian Pelz , Ludovic =?UTF-8?Q?Court=C3=A8s?= , Maxim Cournoyer Received: via spool by 72337-submit@debbugs.gnu.org id=B72337.172574233714078 (code B ref 72337); Sat, 07 Sep 2024 20:53:03 +0000 Received: (at 72337) by debbugs.gnu.org; 7 Sep 2024 20:52:17 +0000 Received: from localhost ([127.0.0.1]:57684 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pn-0003ey-Jw for submit@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:16 -0400 Received: from confino.investici.org ([93.190.126.19]:22707) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pk-0003ep-G6 for 72337@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:14 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1725742323; bh=v01i0/jI6+YBJY4LQkaWgUGU5YX2R8Tom7IxU30tvrU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JO+Z68u1pXTeBmOAy1ELN3QtZosqC+OXU9Y191g4qcLE9Zb68V7BbJify74M6AB0i 2i4o2eKBef5BmGrS/LcORtfj448g6oUy8J2TsAHweSzfSQZ/TGv9tNPkxcjjRFhsOB XkxG2fwPnj+CwaiMcqNrER0DaXHZxuZQ3cJtw24M= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4X1QJM3M77z11Fq; Sat, 7 Sep 2024 20:52:03 +0000 (UTC) Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19]) (Authenticated sender: goodoldpaul@autistici.org) by localhost (Postfix) with ESMTPSA id 4X1QJM2RB6z11FW; Sat, 7 Sep 2024 20:52:03 +0000 (UTC) Date: Sat, 7 Sep 2024 22:51:49 +0200 Message-ID: <479d5a6eb25e4a4156fa04774ad8800f38ea08ec.1725742309.git.goodoldpaul@autistici.org> X-Mailer: git-send-email 2.45.2 In-Reply-To: <8737329a065c5436643c6e5e7d52ec760f069725.1725742309.git.goodoldpaul@autistici.org> References: <8737329a065c5436643c6e5e7d52ec760f069725.1725742309.git.goodoldpaul@autistici.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Giacomo Leidi X-ACL-Warn: , Giacomo Leidi via Guix-patches From: Giacomo Leidi via Guix-patches via Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: guix-patches-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US X-Migadu-Spam-Score: -2.59 X-Spam-Score: -2.59 X-Migadu-Queue-Id: EB77373CD2 X-Migadu-Scanner: mx11.migadu.com X-TUID: x8aPKOprESwk This commit adds a Guix System service to handle allocation of subuid and subgid requests. Users that don't care can just add themselves as a subid-range and don't need to specify anything but their user name. Users that care about specific ranges, such as possibly LXD, can specify a start and a count. * doc/guix.texi: Document the new service. * gnu/build/activation.scm (activate-subuids+subgids): New variable. * gnu/local.mk: Add gnu/tests/shadow.scm. * gnu/system/accounts.scm (sexp->subid-range): New variable. * gnu/system/shadow.scm (%root-subid): New variable; (subids-configuration): new record; (subid-range->gexp): new variable; (assert-valid-subids): new variable; (delete-duplicate-ranges): new variable; (subids-activation): new variable; (subids-extension): new record; (append-subid-ranges): new variable; (subids-extension-merge): new variable; (subids-service-type): new variable. * gnu/tests/shadow.scm (subids): New system test. Change-Id: I3755e1c75771220c74fe8ae5de1a7d90f2376635 Signed-off-by: Giacomo Leidi --- doc/guix.texi | 180 +++++++++++++++++++++++++++++++++ gnu/build/activation.scm | 19 ++++ gnu/local.mk | 1 + gnu/system/accounts.scm | 10 ++ gnu/system/shadow.scm | 211 ++++++++++++++++++++++++++++++++++++++- gnu/tests/shadow.scm | 180 +++++++++++++++++++++++++++++++++ 6 files changed, 599 insertions(+), 2 deletions(-) create mode 100644 gnu/tests/shadow.scm diff --git a/doc/guix.texi b/doc/guix.texi index 981ffb8c58..16fd415b32 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -41683,6 +41683,186 @@ Miscellaneous Services @end deftp +@c %end of fragment + +@cindex Subids +@subsubheading Subid Service + +Among the virtualization facilities implemented by the Linux kernel, the is the +concept of 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 (subids) allow 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 @node Privileged Programs diff --git a/gnu/build/activation.scm b/gnu/build/activation.scm index d1a2876a96..5236fbb403 100644 --- a/gnu/build/activation.scm +++ b/gnu/build/activation.scm @@ -10,6 +10,7 @@ ;;; Copyright © 2021 Brice Waegeneire ;;; Copyright © 2022 Tobias Geerinckx-Rice ;;; Copyright © 2024 Nicolas Graves +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -40,6 +41,7 @@ (define-module (gnu build activation) #: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 @@ -227,6 +229,23 @@ (define (activate-users+groups users groups) (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 ed630041ff..b36873f28a 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -841,6 +841,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 @@ (define-module (gnu system accounts) sexp->user-account sexp->user-group + sexp->subid-range default-shell)) @@ -159,3 +160,12 @@ (define (sexp->user-account sexp) (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 ;;; Copyright © 2020, 2023 Efraim Flashner ;;; Copyright © 2020 Maxim Cournoyer +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -28,6 +29,10 @@ (define-module (gnu system shadow) #: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 @@ (define-module (gnu system shadow) %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 @@ (define (assert-valid-users/groups users groups) ;;; -;;; Service. +;;; Accounts Service. ;;; (define (user-group->gexp group) @@ -521,4 +539,193 @@ (define (account-service accounts+groups skeletons) (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 make-subids-configuration + subids-configuration? + this-subids-configuration + + (add-root? subids-configuration-add-root? ; boolean + (default #t)) + (subgids subids-configuration-subgids ; list of + (default '())) + (subuids subids-configuration-subuids ; list of + (default '()))) + +(define (subid-range->gexp range) + "Turn RANGE, a 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 +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 make-subids-extension + subids-extension? + this-subids-extension + + (subgids subids-extension-subgids ; list of + (default '())) + (subuids subids-extension-subuids ; list of + (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 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 diff --git a/gnu/tests/shadow.scm b/gnu/tests/shadow.scm new file mode 100644 index 0000000000..849b7b8af0 --- /dev/null +++ b/gnu/tests/shadow.scm @@ -0,0 +1,180 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu tests shadow) + #:use-module (gnu packages base) + #:use-module (gnu packages containers) + #:use-module (gnu tests) + #:use-module (gnu services) + #:use-module (gnu system) + #:use-module (gnu system accounts) + #:use-module (gnu system shadow) + #:use-module (gnu system vm) + #:use-module (guix gexp) + #:export (%test-subids)) + + +(define %subids-os + (simple-operating-system + (simple-service + 'simple-profile + profile-service-type + (list podman)) + (simple-service + 'simple-subids + subids-service-type + (subids-extension + (subgids + (list + (subid-range + (name "alice")) + (subid-range + (name "bob") + (start 100700)))) + (subuids + (list + (subid-range + (name "alice")))))))) + +(define (run-subids-test) + "Run IMAGE as an OCI backed Shepherd service, inside OS." + + (define os + (marionette-operating-system + (operating-system-with-gc-roots + %subids-os + (list)) + #:imported-modules '((gnu services herd) + (guix combinators)))) + + (define vm + (virtual-machine + (operating-system os) + (volatile? #f) + (memory-size 1024) + (disk-image-size (* 3000 (expt 2 20))) + (port-forwardings '()))) + + (define test + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (srfi srfi-11) (srfi srfi-64) + (gnu build marionette)) + + (define marionette + ;; Relax timeout to accommodate older systems and + ;; allow for pulling the image. + (make-marionette (list #$vm) #:timeout 60)) + + (test-runner-current (system-test-runner #$output)) + (test-begin "subids") + + (test-equal "/etc/subid and /etc/subgid are created and their content is sound" + '("root:100000:700\nbob:100700:65536\nalice:166236:65536\n" + "root:100000:65536\nalice:165536:65536\n") + (marionette-eval + `(begin + (use-modules (ice-9 textual-ports)) + + (define (read-file file-name) + (call-with-input-file file-name get-string-all)) + + (let* ((response1 (read-file "/etc/subgid")) + (response2 (read-file "/etc/subuid"))) + (list response1 response2))) + marionette)) + + (test-equal "podman unshare runs for unprivileged users" + " 0 1000 1\n 1 165536 65536" + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (ice-9 popen) + (ice-9 match) + (ice-9 rdelim) + (ice-9 textual-ports)) + (define out-dir "/tmp") + (define (read-file file-name) + (call-with-input-file file-name get-string-all)) + + (define (wait-for-file file) + ;; Wait until FILE shows up. + (let loop ((i 60)) + (cond ((file-exists? file) + #t) + ((zero? i) + (error "file didn't show up" file)) + (else + (sleep 1) + (loop (- i 1)))))) + + (define (read-lines file-or-port) + (define (loop-lines port) + (let loop ((lines '())) + (match (read-line port) + ((? eof-object?) + (reverse lines)) + (line + (loop (cons line lines)))))) + + (if (port? file-or-port) + (loop-lines file-or-port) + (call-with-input-file file-or-port + loop-lines))) + + (define slurp + (lambda args + (let* ((port (apply open-pipe* OPEN_READ + (list "sh" "-l" "-c" + (string-join + args + " ")))) + (output (read-lines port)) + (status (close-pipe port))) + output))) + + (match (primitive-fork) + (0 + (dynamic-wind + (const #f) + (lambda () + (setgid (passwd:gid (getpwnam "alice"))) + (setuid (passwd:uid (getpw "alice"))) + + (let* ((response1 (slurp + "podman" "unshare" "cat" "/proc/self/uid_map"))) + (call-with-output-file (string-append out-dir "/response1") + (lambda (port) + (display (string-join response1 "\n") port))))) + (lambda () + (primitive-exit 127)))) + (pid + (cdr (waitpid pid)))) + (wait-for-file (string-append out-dir "/response1")) + (read-file (string-append out-dir "/response1"))) + marionette)) + + (test-end)))) + + (gexp->derivation "subids-test" test)) + +(define %test-subids + (system-test + (name "subids") + (description "Test sub UIDs and sub GIDs provisioning service.") + (value (run-subids-test)))) -- 2.45.2