From patchwork Sun Jul 28 15:29:24 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 66536 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 1A4E327BBEA; Sun, 28 Jul 2024 16:31:10 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE,SPF_HELO_PASS, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id C374027BBE9 for ; Sun, 28 Jul 2024 16:31:08 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sY5rJ-0002Zh-DZ; Sun, 28 Jul 2024 11:30:53 -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 1sY5rH-0002Z3-L1 for guix-patches@gnu.org; Sun, 28 Jul 2024 11:30:51 -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 1sY5rH-0006Ki-C3 for guix-patches@gnu.org; Sun, 28 Jul 2024 11:30:51 -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:Date:From:To:In-Reply-To:References:Subject; bh=57uLy6k2r3iCl10Cv++Iv100kCJ0wJGKOHfjuP5LCkI=; b=mO6GX7sK8zSv3u6Lt/H1932nbNXtZg4W3fwl9LX+NUXJ1rEoJ2WlWBBrNQ/om/27mpKq+mD5zMnK83Lp6whf+QR2xGqPFd0th8LTE/iLVjJFyHc4GvBFkqJ/26PrSJAE5eQLUMOuDOAi+l9K7dk0sGO24zJ+itjqZfwJVftBKMKi91+jxVH+xFCdFAzYSrQASEoUScyy09O37jGfuqLyf0XTq8Wpxg3PBe1ldd+Eo6iWxAY4yNw5etoKbpxb6pFaCEHJyJhX++shpuT7W+CwXZxEPydYQeHqaMMXRkfmwX8V0heAOkqVziA/PKIcmCeNWODw77fuDCuU4VcNFYMosQ==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sY5rS-00042k-QU for guix-patches@gnu.org; Sun, 28 Jul 2024 11:31:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH 1/3] accounts: Add /etc/subuid and /etc/subgid support. References: In-Reply-To: Resent-From: Giacomo Leidi Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sun, 28 Jul 2024 15:31:02 +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 Received: via spool by 72337-submit@debbugs.gnu.org id=B72337.172218062315461 (code B ref 72337); Sun, 28 Jul 2024 15:31:02 +0000 Received: (at 72337) by debbugs.gnu.org; 28 Jul 2024 15:30:23 +0000 Received: from localhost ([127.0.0.1]:44237 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qo-00041J-BC for submit@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:22 -0400 Received: from confino.investici.org ([93.190.126.19]:47669) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qi-00040r-HT for 72337@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:19 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1722180603; bh=57uLy6k2r3iCl10Cv++Iv100kCJ0wJGKOHfjuP5LCkI=; h=From:To:Cc:Subject:Date:From; b=TLMD40PqUJDqTcSOJD9S/KmlhqIKkHMlFKPhZX+1a+1tklDs3cBbufpibGXluJqwC BQuABy4e8pLzwYEIKXcl2MYbOf0FzcUrutVYmSUkKW0nTazCdSPa/hJL9yQnh2rbEC LlcIqJr5I1+LTKudpC9DYkmFcVMiVJC/bn+Rpwn0= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4WX55l641Jz112h; Sun, 28 Jul 2024 15:30: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 4WX55l5JqJz112f; Sun, 28 Jul 2024 15:30:03 +0000 (UTC) Date: Sun, 28 Jul 2024 17:29:24 +0200 Message-ID: <1901209e4998ad29192b6f73b1e2828bc5d6f90e.1722180566.git.goodoldpaul@autistici.org> X-Mailer: git-send-email 2.45.2 MIME-Version: 1.0 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 X-Patchwork-Original-From: Giacomo Leidi via Guix-patches via From: Giacomo Leidi Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches This commit adds a new record type, and serializers and deserializers for it in (gnu build accounts). Each instance of this record represents one line in either /etc/subuid or /etc/subgid. Since Shadow uses the same representation for both files, it should be ok if we do it as well. This commit adds also , a user facing representation of . It is supposed to be usable directly in OS configurations. * gnu/build/accounts.scm (subid-entry): New record; (write-subgid): add serializer for subgids; (write-subuid): add serializer for subuids; (read-subgid): add serializer for subgids; (read-subuid): add serializer for subuids. * gnu/system/accounts.scm (subid-range): New record. * test/accounts.scm: Test them. Change-Id: I6b037e40e354c069bf556412bb5b626bd3ea1b2c --- gnu/build/accounts.scm | 37 ++++++++++++++++++++++++--- gnu/system/accounts.scm | 17 +++++++++++++ tests/accounts.scm | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) base-commit: 8c6e724686ac37ff7955b97d9bfd03176b14d82a diff --git a/gnu/build/accounts.scm b/gnu/build/accounts.scm index fa6f454b5e..ea8c69f205 100644 --- a/gnu/build/accounts.scm +++ b/gnu/build/accounts.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2019, 2021, 2023 Ludovic Courtès +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -51,13 +52,23 @@ (define-module (gnu build accounts) group-entry-gid group-entry-members + subid-entry + subid-entry? + subid-entry-name + subid-entry-start + subid-entry-count + %password-lock-file write-group write-passwd write-shadow + write-subgid + write-subuid read-group read-passwd read-shadow + read-subgid + read-subuid %id-min %id-max @@ -68,11 +79,12 @@ (define-module (gnu build accounts) ;;; Commentary: ;;; -;;; This modules provides functionality equivalent to the C library's +;;; This module provides functionality equivalent to the C library's ;;; , , and routines, as well as a subset of the ;;; functionality of the Shadow command-line tools. It can parse and write -;;; /etc/passwd, /etc/shadow, and /etc/group. It can also take care of UID -;;; and GID allocation in a way similar to what 'useradd' does. +;;; /etc/passwd, /etc/shadow, /etc/group, /etc/subuid and /etc/subgid. It can +;;; also take care of UID and GID allocation in a way similar to what 'useradd' +;;; does. The same goes for sub UID and sub GID allocation. ;;; ;;; The benefit is twofold: less code is involved, and the ID allocation ;;; strategy and state preservation is made explicit. @@ -225,6 +237,17 @@ (define-database-entry ; (serialization list->comma-separated comma-separated->list) (default '()))) +(define-database-entry ; + subid-entry make-subid-entry + subid-entry? + (serialization #\: subid-entry->string string->subid-entry) + + (name subid-entry-name) + (start subid-entry-start + (serialization number->string string->number)) + (count subid-entry-count + (serialization number->string string->number))) + (define %password-lock-file ;; The password database lock file used by libc's 'lckpwdf'. Users should ;; grab this lock with 'with-file-lock' when they access the databases. @@ -265,6 +288,10 @@ (define write-shadow (database-writer "/etc/shadow" #o600 shadow-entry->string)) (define write-group (database-writer "/etc/group" #o644 group-entry->string)) +(define write-subuid + (database-writer "/etc/subuid" #o644 subid-entry->string)) +(define write-subgid + (database-writer "/etc/subgid" #o644 subid-entry->string)) (define (database-reader file string->entry) (lambda* (#:optional (file-or-port file)) @@ -287,6 +314,10 @@ (define read-shadow (database-reader "/etc/shadow" string->shadow-entry)) (define read-group (database-reader "/etc/group" string->group-entry)) +(define read-subuid + (database-reader "/etc/subuid" string->subid-entry)) +(define read-subgid + (database-reader "/etc/subgid" string->subid-entry)) ;;; diff --git a/gnu/system/accounts.scm b/gnu/system/accounts.scm index 586cff1842..9a006c188d 100644 --- a/gnu/system/accounts.scm +++ b/gnu/system/accounts.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -39,6 +40,12 @@ (define-module (gnu system accounts) user-group-id user-group-system? + subid-range + subid-range? + subid-range-name + subid-range-start + subid-range-count + sexp->user-account sexp->user-group @@ -85,6 +92,16 @@ (define-record-type* (system? user-group-system? ; Boolean (default #f))) +(define-record-type* + subid-range make-subid-range + subid-range? + (name subid-range-name) + (start subid-range-start (default #f)) ; number + (count subid-range-count ; number + ; from find_new_sub_gids.c and + ; find_new_sub_uids.c + (default 65536))) + (define (default-home-directory account) "Return the default home directory for ACCOUNT." (string-append "/home/" (user-account-name account))) diff --git a/tests/accounts.scm b/tests/accounts.scm index 78136390bb..4944c22f49 100644 --- a/tests/accounts.scm +++ b/tests/accounts.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2019 Ludovic Courtès +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -41,6 +42,16 @@ (define %shadow-sample charlie:" (crypt "hey!" "$6$abc") ":17169:::::: nobody:!:0::::::\n")) +(define %subuid-sample + "\ +root:100000:300 +ada:100300:300\n") + +(define %subgid-sample + "\ +root:100000:600 +ada:100600:300\n") + (test-begin "accounts") @@ -135,6 +146,50 @@ (define %shadow-sample read-shadow) port)))) +(test-equal "write-subuid" + %subuid-sample + (call-with-output-string + (lambda (port) + (write-subuid (list (subid-entry + (name "root") + (start 100000) + (count 300)) + (subid-entry + (name "ada") + (start 100300) + (count 300))) + port)))) + +(test-equal "read-subuid + write-subuid" + %subuid-sample + (call-with-output-string + (lambda (port) + (write-subuid (call-with-input-string %subuid-sample + read-subuid) + port)))) + +(test-equal "write-subgid" + %subgid-sample + (call-with-output-string + (lambda (port) + (write-subgid (list (subid-entry + (name "root") + (start 100000) + (count 600)) + (subid-entry + (name "ada") + (start 100600) + (count 300))) + port)))) + +(test-equal "read-subgid + write-subgid" + %subgid-sample + (call-with-output-string + (lambda (port) + (write-subgid (call-with-input-string %subgid-sample + read-subgid) + port)))) + (define allocate-groups (@@ (gnu build accounts) allocate-groups)) (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) From patchwork Sun Jul 28 15:29:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 66538 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id CE4CE27BBE9; Sun, 28 Jul 2024 16:31:28 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE,SPF_HELO_PASS, URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id B6C1C27BBE2 for ; Sun, 28 Jul 2024 16:31:25 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sY5rI-0002ZB-BP; Sun, 28 Jul 2024 11:30:52 -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 1sY5rH-0002Ys-4L for guix-patches@gnu.org; Sun, 28 Jul 2024 11:30:51 -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 1sY5rG-0006Kc-Rq for guix-patches@gnu.org; Sun, 28 Jul 2024 11:30:50 -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=fnoegWIFjnuS7mQqES8XFN0q6+mWfKZDt6xrwN84Cvc=; b=ObVyywZfct86gVIQuG1s8NTQ1ajRH0n+P69/mwsp1UiWY0bTqzKKxy2BLoWhF/oa1SHfEg11E1Ub1m5FP+CtVjoz1tzk038BedUH01JmkPbN+1wTzlolLkTVLpiAj/ozVmYDrHclLPtVKIhx4Hr3BkRRLyy4gkLMo9Yn9XRWJbs28cnoZgV0Gvj9B2yz9SqTJv/hRa6kxX+Ed8L9VDJJSohBghe4R+rFnB3YAfNBLh6QNl+ih/KrpIbTWo86CMmDrcjtf8oFZrOvTG7vqPO1SUZqGaPTct/AwIT1UgpBdRmXAF8/eO0phleNIMA6k1ZpUq23Sq+bm0prqZs1472EAg==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sY5rS-00042a-AX for guix-patches@gnu.org; Sun, 28 Jul 2024 11:31:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH 2/3] account: Add /etc/subid and /etc/subgid allocation logic. Resent-From: Giacomo Leidi Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sun, 28 Jul 2024 15:31:02 +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 Received: via spool by 72337-submit@debbugs.gnu.org id=B72337.172218061915444 (code B ref 72337); Sun, 28 Jul 2024 15:31:02 +0000 Received: (at 72337) by debbugs.gnu.org; 28 Jul 2024 15:30:19 +0000 Received: from localhost ([127.0.0.1]:44235 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qk-000411-8t for submit@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:19 -0400 Received: from confino.investici.org ([93.190.126.19]:20453) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qi-00040s-HS for 72337@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:17 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1722180604; bh=fnoegWIFjnuS7mQqES8XFN0q6+mWfKZDt6xrwN84Cvc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uJiMOEd00zp1980vrbka1lfB0a7dVZKwve7Un3yb3jXbjoqoVt23LDEgJTqhg4Fmj TTSqSdhdaQxOvFbV0ZYobbsG17OEYIbIlUgJhn5EizVJIE85WUz2WuBdJD2DwiVodU L/Kix0rbqZ5iHkHB/MBojBuA0/doF5oElyq+FM8w= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4WX55m11TFz112j; Sun, 28 Jul 2024 15:30:04 +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 4WX55m0BsFz112f; Sun, 28 Jul 2024 15:30:03 +0000 (UTC) Date: Sun, 28 Jul 2024 17:29:25 +0200 Message-ID: <56cc1f5f7544fe85aeedab1afc05b2f8ea33a7d6.1722180566.git.goodoldpaul@autistici.org> X-Mailer: git-send-email 2.45.2 In-Reply-To: <1901209e4998ad29192b6f73b1e2828bc5d6f90e.1722180566.git.goodoldpaul@autistici.org> References: <1901209e4998ad29192b6f73b1e2828bc5d6f90e.1722180566.git.goodoldpaul@autistici.org> MIME-Version: 1.0 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 X-Patchwork-Original-From: Giacomo Leidi via Guix-patches via From: Giacomo Leidi Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches * gnu/build/accounts.scm (list-set): New variable; (%sub-id-min): new variable; (%sub-id-max): new variable; (%sub-id-count): new variable; (sub-id?): new variable; (subid-range-fits?): new variable; (subid-range-fits-between?): new variable; (insert-subid-range): new variable; (reserve-subids): new variable; (range->entry): new variable; (entry->range): new variable; (allocate-subids): new variable; (subuid+subgid-databases): new variable. * gnu/system/accounts.scm (subid-range-end): New variable; (subid-range-has-start?): new variable; (subid-range-less): new variable. * test/accounts.scm: Test them. Change-Id: I8de1fd7cfe508b9c76408064d6f498471da0752d --- gnu/build/accounts.scm | 229 +++++++++++++++++++++++++++++++++++++++- gnu/system/accounts.scm | 30 ++++++ tests/accounts.scm | 108 +++++++++++++++++++ 3 files changed, 366 insertions(+), 1 deletion(-) diff --git a/gnu/build/accounts.scm b/gnu/build/accounts.scm index ea8c69f205..3cbbacfaee 100644 --- a/gnu/build/accounts.scm +++ b/gnu/build/accounts.scm @@ -74,8 +74,12 @@ (define-module (gnu build accounts) %id-max %system-id-min %system-id-max + %sub-id-min + %sub-id-max + %sub-id-count - user+group-databases)) + user+group-databases + subuid+subgid-databases)) ;;; Commentary: ;;; @@ -91,6 +95,18 @@ (define-module (gnu build accounts) ;;; ;;; Code: + +;;; +;;; General utilities. +;;; + +(define (list-set lst el k) + (if (>= k (length lst)) + `(,@lst ,el) + `(,@(list-head lst k) + ,el + ,@(list-tail lst k)))) + ;;; ;;; Machinery to define user and group databases. @@ -342,6 +358,12 @@ (define %id-max 60000) (define %system-id-min 100) (define %system-id-max 999) +;; According to Shadow's libmisc/find_new_sub_uids.c and +;; libmisc/find_new_sub_gids.c. +(define %sub-id-min 100000) +(define %sub-id-max 600100000) +(define %sub-id-count 65536) + (define (system-id? id) (and (> id %system-id-min) (<= id %system-id-max))) @@ -350,6 +372,10 @@ (define (user-id? id) (and (>= id %id-min) (< id %id-max))) +(define (sub-id? id) + (and (>= id %sub-id-min) + (< id %sub-id-max))) + (define* (allocate-id assignment #:key system?) "Return two values: a newly allocated ID, and an updated record based on ASSIGNMENT. If SYSTEM? is true, return a system ID." @@ -405,6 +431,156 @@ (define* (reserve-ids allocation ids #:key (skip? #t)) (allocation-ids allocation) ids)))) +(define (subid-range-fits? r interval-start interval-end) + (and (<= interval-start + (subid-range-start r)) + (<= (subid-range-end r) + interval-end))) + +(define (subid-range-fits-between? r a b) + (subid-range-fits? r + (+ (subid-range-start a) 1) + (- (subid-range-end b) 1))) + +(define (insert-subid-range range lst) + (define* (actualize r #:key (start %sub-id-min)) + (if (subid-range-has-start? r) + r + (subid-range + (inherit r) + (start start)))) + (define lst-length (length lst)) + (define range-name (subid-range-name range)) + (define range-start (subid-range-start range)) + (define has-start? (subid-range-has-start? range)) + (define range-end (subid-range-end range)) + + (when has-start? + (unless (and (sub-id? range-start) + (sub-id? range-end)) + (raise + (string-append "Subid range of " range-name + " from " range-start " to " range-end + " spans over illegal subids. Max allowed is " + %sub-id-max ", min is " %sub-id-min ".")))) + + (if (<= lst-length 1) + (if (= lst-length 0) + (list (actualize range)) + (if (subid-range-less range (first lst)) + (list-set lst (actualize range) 0) + (list-set lst + (actualize + range + #:start (and (subid-range-has-start? (first lst)) + (+ (subid-range-end (first lst)) 1))) + 1))) + (let loop ((i 0)) + (define next-i (+ i 1)) + (define ith-range + (list-ref lst i)) + (define ith-start + (subid-range-start ith-range)) + (define ith-has-start? + (subid-range-has-start? ith-range)) + (define ith-name + (subid-range-name ith-range)) + + (if (and + (= next-i lst-length) + (subid-range-less ith-range range)) + (let ((actual-range + (actualize + range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))))) + (list-set lst + actual-range + lst-length)) + (let* ((next-range + (list-ref lst next-i)) + (next-has-start? + (subid-range-has-start? next-range))) + (cond + + ((and has-start? (= range-start ith-start)) + (raise + (string-append "Subid range of " range-name + " has same start " + (number->string range-start) + " of the one " + "from " ith-name "."))) + + ((and (= i 0) + (subid-range-less range ith-range) + (or + (and + has-start? ith-has-start? + (subid-range-fits? (actualize range) + %sub-id-min + (- (subid-range-start + (actualize ith-range)) + 1))) + (not (and has-start? ith-has-start?)))) + (list-set lst (actualize range) 0)) + + ((subid-range-less range ith-range) + (raise + (string-append "Subid range of " range-name + " overlaps with the one of " + ith-name "."))) + + ((and (subid-range-less ith-range range) + (subid-range-less range next-range)) + (if (or (not (and has-start? + ith-has-start? + next-has-start?)) + + (and has-start? + ith-has-start? + next-has-start? + (subid-range-fits-between? range + ith-range + next-range))) + (list-set lst + (actualize range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))) + next-i) + (if (>= i lst-length) + (if (and (subid-range-less next-range range) + (let ((actual-next + (actualize next-range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))))) + (or (not (subid-range-has-start? actual-next)) + (subid-range-fits? + (actualize range + #:start (and next-has-start? + (+ (subid-range-end next-range) 1))) + (+ (subid-range-end actual-next) 1) + %sub-id-max)))) + (list-set lst range lst-length) + (raise + (string-append "Couldn't fit " range-name ", reached end of list."))) + (loop next-i)))) + + ((or + (not has-start?) + (subid-range-less next-range range)) + (loop next-i)) + + (else + (raise (string-append "Couldn't fit " range-name ", this should never happen."))))))))) + +(define* (reserve-subids allocation ranges) + "Mark the subid ranges listed in RANGES as reserved in ALLOCATION. +ALLOCATION is supposed to be sorted by SUBID-RANGE-LESS." + (fold insert-subid-range + allocation + (sort-list ranges + subid-range-less))) + (define (allocated? allocation id) "Return true if ID is already allocated as part of ALLOCATION." (->bool (vhash-assv id (allocation-ids allocation)))) @@ -540,6 +716,31 @@ (define* (allocate-passwd users groups #:optional (current-passwd '())) uids users))) +(define (range->entry range) + (subid-entry + (name (subid-range-name range)) + (start (subid-range-start range)) + (count (subid-range-count range)))) + +(define (entry->range entry) + (subid-range + (name (subid-entry-name entry)) + (start (subid-entry-start entry)) + (count (subid-entry-count entry)))) + +(define* (allocate-subids ranges #:optional (current-ranges '())) + "Return a list of subids entries for RANGES, a list of . Members +for each group are taken from MEMBERS, a vhash that maps ranges names to member +names. IDs found in CURRENT-RANGES, a list of subid entries, are reused." + (define subids + ;; Mark all the currently used IDs and the explicitly requested IDs as + ;; reserved. + (reserve-subids (reserve-subids (list) + current-ranges) + ranges)) + + (map range->entry subids)) + (define* (days-since-epoch #:optional (current-time current-time)) "Return the number of days elapsed since the 1st of January, 1970." (let* ((now (current-time time-utc)) @@ -615,3 +816,29 @@ (define* (user+group-databases users groups #:current-time current-time)) (values group-entries passwd-entries shadow-entries)) + +(define* (subuid+subgid-databases subuids subgids + #:key + (current-subuids + (map entry->range + (empty-if-not-found read-subuid))) + (current-subgids + (map entry->range + (empty-if-not-found read-subgid)))) + "Return two values: the list of subgid entries, and the list of subuid entries +corresponding to SUBUIDS and SUBGIDS. +Preserve stateful bits from CURRENT-SUBUIDS and CURRENT-SUBGIDS." + + (define (range-eqv? a b) + (string=? (subid-range-name a) + (subid-range-name b))) + + (define subuid-entries + (allocate-subids + (lset-difference range-eqv? subuids current-subuids) current-subuids)) + + (define subgid-entries + (allocate-subids + (lset-difference range-eqv? subgids current-subgids) current-subgids)) + + (values subuid-entries subgid-entries)) diff --git a/gnu/system/accounts.scm b/gnu/system/accounts.scm index 9a006c188d..1b88ca301f 100644 --- a/gnu/system/accounts.scm +++ b/gnu/system/accounts.scm @@ -45,6 +45,9 @@ (define-module (gnu system accounts) subid-range-name subid-range-start subid-range-count + subid-range-end + subid-range-has-start? + subid-range-less sexp->user-account sexp->user-group @@ -102,6 +105,33 @@ (define-record-type* ; find_new_sub_uids.c (default 65536))) +(define (subid-range-end range) + "Returns the last subid referenced in RANGE." + (and + (subid-range-has-start? range) + (+ (subid-range-start range) + (subid-range-count range) + -1))) + +(define (subid-range-has-start? range) + "Returns #t when RANGE's start is a number." + (number? (subid-range-start range))) + +(define (subid-range-less a b) + "Returns #t when subid range A either starts before, or is more specific +than B. When it is not possible to determine whether a range is more specific +w.r.t. another range their names are compared alphabetically." + (define start-a (subid-range-start a)) + (define start-b (subid-range-start b)) + (cond ((and (not start-a) (not start-b)) + (string< (subid-range-name a) + (subid-range-name b))) + ((and start-a start-b) + (< start-a start-b)) + (else + (and start-a + (not start-b))))) + (define (default-home-directory account) "Return the default home directory for ACCOUNT." (string-append "/home/" (user-account-name account))) diff --git a/tests/accounts.scm b/tests/accounts.scm index 4944c22f49..2fbebfaf56 100644 --- a/tests/accounts.scm +++ b/tests/accounts.scm @@ -193,6 +193,7 @@ (define %subgid-sample (define allocate-groups (@@ (gnu build accounts) allocate-groups)) (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) +(define allocate-subids (@@ (gnu build accounts) allocate-subids)) (test-equal "allocate-groups" ;; Allocate GIDs in a stateless fashion. @@ -257,6 +258,69 @@ (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) (list (group-entry (name "d") (gid (- %id-max 2)))))) +(test-equal "allocate-subids" + ;; Allocate sub IDs in a stateless fashion. + (list (subid-entry (name "root") (start %sub-id-min) (count 100)) + (subid-entry (name "t") (start 100100) (count 899)) + (subid-entry (name "x") (start 100999) (count 200))) + (allocate-subids (list + (subid-range (name "x") (count 200)) + (subid-range (name "t") (count 899))) + (list (subid-range (name "root") (count 100))))) + +(test-equal "allocate-subids with requested IDs ranges" + ;; Make sure the requested sub ID for "t" and "x" are honored. + (list (subid-entry (name "x") (start %sub-id-min) (count 200)) + (subid-entry (name "t") (start 1000000) (count 899)) + (subid-entry (name "l") (start 1000899) (count 100)) + (subid-entry (name "root") (start 1000999) (count 100))) + (allocate-subids (list + (subid-range (name "root") (count 100)) + (subid-range (name "l") (count 100))) + (list + (subid-range (name "x") (start %sub-id-min) (count 200)) + (subid-range (name "t") (start 1000000) (count 899))))) + +(test-equal "allocate-subids with interleaving" + ;; Make sure the requested sub ID for "m" is honored. + (list (subid-entry (name "x") (start %sub-id-min) (count 200)) + (subid-entry (name "t") (start 1000000) (count 899)) + (subid-entry (name "i") (start 1100000) (count 1)) + (subid-entry (name "root") (start 1100001) (count 100)) + (subid-entry (name "m") (start 1200000) (count 27))) + (allocate-subids (list (subid-range (name "m") (start 1200000) (count 27))) + (list + (subid-range (name "x") (start %sub-id-min) (count 200)) + (subid-range (name "t") (start 1000000) (count 899)) + (subid-range (name "i") (start 1100000) (count 1)) + (subid-range (name "root") (count 100))))) + +(let ((inputs+currents + (list + ;; Try impossible before + (list + (list (subid-range (name "m") (start 100100) (count 27))) + (list + (subid-range (name "x") (start %sub-id-min) (count 150)))) + ;; Try impossible after + (list + (list (subid-range (name "m") (start %sub-id-min) (count 30))) + (list + (subid-range (name "x") (start (+ 29 %sub-id-min)) (count 150)))) + ;; Try impossible between + (list + (list (subid-range (name "m") (start 100200) (count 500))) + (list + (subid-range (name "root") (start %sub-id-min) (count 100)) + (subid-range (name "x") (start (+ %sub-id-min 500)) (count 100))))))) + (test-error "allocate-subids with interleaving, impossible interleaving" + "error" + ;; Make sure it's impossible to explicitly request impossible allocations + (for-each + (lambda (lst) + (allocate-subids (first lst) (second lst))) + inputs+currents))) + (test-equal "allocate-passwd" ;; Allocate UIDs in a stateless fashion. (list (password-entry (name "alice") (uid %id-min) (gid 1000) @@ -376,4 +440,48 @@ (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) (make-time type 0 (* 24 3600 100))))) list)) +(test-equal "subuid+subgid-databases" + ;; The whole process. + (list (list (subid-entry (name "root") + (start %sub-id-min) + (count 100)) + (subid-entry (name "alice") + (start (+ %sub-id-min 100)) + (count 200)) + (subid-entry (name "bob") + (start (+ %sub-id-min 100 200)) + (count 200))) + (list + (subid-entry (name "root") + (start %sub-id-min) + (count 200)) + (subid-entry (name "alice") + (start (+ %sub-id-min 200)) + (count 400)) + (subid-entry (name "charlie") + (start (+ %sub-id-min 200 400)) + (count 300)))) + (call-with-values + (lambda () + (subuid+subgid-databases + (list (subid-range (name "root") + (start %sub-id-min) + (count 100)) + (subid-range (name "alice") + (start (+ %sub-id-min 100)) + (count 200)) + (subid-range (name "bob") + (count 200))) + (list + (subid-range (name "alice") + (count 400)) + (subid-range (name "charlie") + (count 300))) + #:current-subgids + (list (subid-range (name "root") + (start %sub-id-min) + (count 200))) + #:current-subuids '())) + list)) + (test-end "accounts") From patchwork Sun Jul 28 15:29:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 66537 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id E5EE527BBEA; Sun, 28 Jul 2024 16:31:23 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE,SPF_HELO_PASS, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id 4B8B127BBE2 for ; Sun, 28 Jul 2024 16:31:21 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sY5rN-0002aK-LK; Sun, 28 Jul 2024 11:30:57 -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 1sY5rL-0002Zu-Mi for guix-patches@gnu.org; Sun, 28 Jul 2024 11:30:55 -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 1sY5rL-0006Kv-Cp; Sun, 28 Jul 2024 11:30:55 -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=oLTegUcVJfuhvYpKCRVt4khbkZ45x/yMeGCr8B/bx3A=; b=uWwVp0PFXrNe3udyilxdDo1AGNcqr6ifbRDHcKbPqBrLFRAz6neN+deCsmMN5Z5T0eEJSEC+7BPOZ6P9VjP9o6Y6ALGrrP8bbh9bEIHe13Q7USL1gQQy2nJFFBkBBya/SvWC0XIIANjoEurN/eE8UEdAlzkac1Cw4cy8h9NhJTnKAvOm6R/ZNL8D1jPRxSS33wHzo13gkQkA9ChXHI4+1Y3tNSKEIsXrX61XjyAWIMyTKPwSTm8q+uYAKuM5aPe3b1jsmKy7QLd+OqQ9hqBZVfo1S9PLdVdXOaQbdgKaXfzlCnTh09rBr1mnGjeTEppyMyvcZSWcAF+eeqaE6bqQUQ==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sY5rT-00042r-93; Sun, 28 Jul 2024 11:31:03 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH 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, matt@excalamus.com, maxim.cournoyer@gmail.com, guix-patches@gnu.org Resent-Date: Sun, 28 Jul 2024 15:31: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?= , Matthew Trzcinski , Maxim Cournoyer X-Debbugs-Original-Xcc: Florian Pelz , Ludovic =?utf-8?q?Court=C3=A8s?= , Matthew Trzcinski , Maxim Cournoyer Received: via spool by 72337-submit@debbugs.gnu.org id=B72337.172218062815480 (code B ref 72337); Sun, 28 Jul 2024 15:31:03 +0000 Received: (at 72337) by debbugs.gnu.org; 28 Jul 2024 15:30:28 +0000 Received: from localhost ([127.0.0.1]:44240 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qs-00041a-W0 for submit@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:28 -0400 Received: from confino.investici.org ([93.190.126.19]:56797) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sY5qp-00041R-Q5 for 72337@debbugs.gnu.org; Sun, 28 Jul 2024 11:30:25 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1722180604; bh=oLTegUcVJfuhvYpKCRVt4khbkZ45x/yMeGCr8B/bx3A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VuxE4p9iV45vsTYnZWBM/5msezNOs15+UUep3psLz/WA/JKSOd5Kqt3VUIneFnNjJ NP0+9BaybKBMH223BNYuvGLsPylGo7cRpA3iFZoNNZhHrkqiWzf1XiuQP/7raZ94ho 7DLE9MIAnDRN7BAWgvBCjJl1XimQlhfgV+2acDSg= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4WX55m3Pzwz113C; Sun, 28 Jul 2024 15:30:04 +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 4WX55m2VWDz112f; Sun, 28 Jul 2024 15:30:04 +0000 (UTC) Date: Sun, 28 Jul 2024 17:29:26 +0200 Message-ID: <6b97096800ebf51a666ab2ee93fd2fdec3c2c65c.1722180566.git.goodoldpaul@autistici.org> X-Mailer: git-send-email 2.45.2 In-Reply-To: <1901209e4998ad29192b6f73b1e2828bc5d6f90e.1722180566.git.goodoldpaul@autistici.org> References: <1901209e4998ad29192b6f73b1e2828bc5d6f90e.1722180566.git.goodoldpaul@autistici.org> MIME-Version: 1.0 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 X-Patchwork-Original-From: Giacomo Leidi via Guix-patches via From: Giacomo Leidi Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches 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 --- doc/guix.texi | 171 ++++++++++++++++++++++++++++++++ gnu/build/activation.scm | 19 ++++ gnu/local.mk | 1 + gnu/system/accounts.scm | 10 ++ gnu/system/shadow.scm | 207 ++++++++++++++++++++++++++++++++++++++- gnu/tests/shadow.scm | 128 ++++++++++++++++++++++++ 6 files changed, 534 insertions(+), 2 deletions(-) create mode 100644 gnu/tests/shadow.scm diff --git a/doc/guix.texi b/doc/guix.texi index 9ba96af459..d0b2a5284c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -41582,6 +41582,177 @@ Miscellaneous Services @end deftp +@c %end of fragment + +@cindex Subids +@subsubheading Subid Service + +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 @code{/etc/subuid} and @code{/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 @code{/etc/subuid} and @code{/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 Setuid Programs diff --git a/gnu/build/activation.scm b/gnu/build/activation.scm index d8c0cd22a3..943d72694f 100644 --- a/gnu/build/activation.scm +++ b/gnu/build/activation.scm @@ -9,6 +9,7 @@ ;;; Copyright © 2020 Christine Lemmer-Webber ;;; Copyright © 2021 Brice Waegeneire ;;; Copyright © 2024 Nicolas Graves +;;; Copyright © 2024 Giacomo Leidi ;;; ;;; This file is part of GNU Guix. ;;; @@ -39,6 +40,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-setuid-programs @@ -202,6 +204,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 ef1e82eb04..3019747328 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -835,6 +835,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..84b5de660b 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. ;;; @@ -77,7 +78,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 +394,7 @@ (define (assert-valid-users/groups users groups) ;;; -;;; Service. +;;; Accounts Service. ;;; (define (user-group->gexp group) @@ -521,4 +535,193 @@ (define (account-service accounts+groups skeletons) (service account-service-type (append skeletons accounts+groups))) + +;;; +;;; Subids Service. +;;; + +(define %sub-id-min + (@@ (gnu build accounts) %sub-id-min)) +(define %sub-id-max + (@@ (gnu build accounts) %sub-id-max)) +(define %sub-id-count + (@@ (gnu build accounts) %sub-id-count)) + +(define* (%root-subid #:optional (start %sub-id-min) (count %sub-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 + %sub-id-count))) + +(define (assert-valid-subids ranges) + (cond ((>= (fold + 0 (map subid-range-count ranges)) + (- %sub-id-max %sub-id-min -1)) + (raise + (string-append + "The configured ranges are more than the " + (- %sub-id-max %sub-id-min -1) " max allowed."))) + ((any (lambda (r) + (define start (subid-range-start r)) + (and start + (< start %sub-id-min))) + ranges) + (raise + (string-append + "One subid-range starts before the minimum allowed sub id " + %sub-id-min "."))) + ((any (lambda (r) + (define end (subid-range-end r)) + (and end + (> end %sub-id-max))) + ranges) + (raise + (string-append + "One subid-range ends after the maximum allowed sub id " + %sub-id-max "."))) + ((any (compose null? subid-range-name) + ranges) + (raise + "One subid-range has a null name.")) + ((any (compose string-null? subid-range-name) + ranges) + (raise + "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 %sub-id-min) + %sub-id-min) + %sub-id-min)) + (define root-count + (if first-has-start? + (- first-start %sub-id-min) + %sub-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..1e755b5438 --- /dev/null +++ b/gnu/tests/shadow.scm @@ -0,0 +1,128 @@ +;;; 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 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-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" + "root:100000:65536\nalice:165536:65536") + (marionette-eval + `(begin + (use-modules (ice-9 popen) + (ice-9 match) + (ice-9 rdelim)) + + (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 args)) + (output (read-lines port)) + (status (close-pipe port))) + output))) + (let* ((response1 (slurp + ,(string-append #$coreutils "/bin/cat") + "/etc/subgid")) + (response2 (slurp + ,(string-append #$coreutils "/bin/cat") + "/etc/subuid"))) + (list (string-join response1 "\n") (string-join response2 "\n")))) + 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))))