From patchwork Sat Sep 7 20:51:47 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 67745 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 B678A27BBEA; Sat, 7 Sep 2024 21:53:32 +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 0A48127BBE2 for ; Sat, 7 Sep 2024 21:53:32 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sn2QY-0004bz-QA; Sat, 07 Sep 2024 16:53:02 -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 1sn2QX-0004be-G5 for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:01 -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 1sn2QX-0001wQ-6q for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:01 -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=eb+5clUKO/1jGXfV4k9QcgTp8DbrUa9GLwE/7rDoo10=; b=EZuF4SlbYFZUb67lTjqbqq73euFdLIIurS4vk/cyMdDh9Y5hf+0IiA9poY6A7gxAWWaMjymD8ossBevcQ/AxUoC1Mi7v7gHcTrDLAdfBqvzyd1VmDranESlMbGzDWzZbTDRoU82ITm2TvUKnNlWohUFsXCKnCNi1wsUJUPv4IfQ/ukw2sg/p3nVvtwxkzTCzIqGt+zb+0SV0LM0oLd064Wl5BYexNaFdPmju5f80PkuQoZVCR2QKFf6wo6OnBcB+dhJ7ZfOxJMLE+9iOwDNOMQzaChhyTGRiLWs8DjrQj/PLCWInxukqG0uI389QZZncvSETlsR6pPWo+BGdfTUcNQ==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sn2QY-0003gH-Cn for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH v4 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: Sat, 07 Sep 2024 20:53: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.172574232714048 (code B ref 72337); Sat, 07 Sep 2024 20:53:02 +0000 Received: (at 72337) by debbugs.gnu.org; 7 Sep 2024 20:52:07 +0000 Received: from localhost ([127.0.0.1]:57679 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pe-0003eT-Nd for submit@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:07 -0400 Received: from confino.investici.org ([93.190.126.19]:62493) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pc-0003eF-Jt for 72337@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:05 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1725742322; bh=eb+5clUKO/1jGXfV4k9QcgTp8DbrUa9GLwE/7rDoo10=; h=From:To:Cc:Subject:Date:From; b=jA7HTGnSdygI48ICJZgxgJP6M3zSBF35XvS7Sz8MgDhi+vHZwLkI0FSREUzBf/ggA 0HtTDBDFVAOGx4Hw8smllHxY2eOwO8Bkm+erqhyK+s+0g5tMyOgF+5rK2fqJSY+jWc VwGT/pppo2aeQj+1wabHALIJLvOaVcxSHH7kEre8= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4X1QJL5wT6z11Fd; Sat, 7 Sep 2024 20:52:02 +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 4X1QJL537Fz11FW; Sat, 7 Sep 2024 20:52:02 +0000 (UTC) Date: Sat, 7 Sep 2024 22:51:47 +0200 Message-ID: <8737329a065c5436643c6e5e7d52ec760f069725.1725742309.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 Signed-off-by: Giacomo Leidi --- gnu/build/accounts.scm | 37 ++++++++++++++++++++++++--- gnu/system/accounts.scm | 17 +++++++++++++ tests/accounts.scm | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) base-commit: 4ba9f3e0f1484524f91ca1f7ec3a4ce7cb8873ff 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 Sat Sep 7 20:51:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 67744 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 0111027BBE2; Sat, 7 Sep 2024 21:53:29 +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 ECB7227BBEA for ; Sat, 7 Sep 2024 21:53:25 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sn2QZ-0004cH-Ky; Sat, 07 Sep 2024 16:53:03 -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 1sn2QX-0004bm-VP for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:01 -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 1sn2QX-0001wY-MH for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:01 -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=6ugOMZGSXDJjz+6Ma9hN3Meqh+YRJ4sYm4AGwbmHS1M=; b=JoB8t/nqGODm6DlrCyLg2gd0KKbN9YdFAkszMnlSj59HRR5VB0WaeFpVK32z9dABgQbp4oEJKE1Bmp50bG3z+IKthd040pJrAEQB67kCvC8ctU3urErc3FnvYHk7+9oCyKvVU15k6fMnKKWUrfBmAew5QzjChcuInFOCDlrwzHuWadXOk748abat1xUJt6XJo3lg3X11CF4WPNA8TLVNQPqFkVWx1cRK87HOE4QECLxmW/NHkrGR5C7bPvcZZnFOSrEUa0+yBjB9U8UDvbLz2lM/B78YkXObpl5s3L+f+xDTFMqDMermnHiSbf0k3hnKgaEXRD+ToR3xYv8vTKFgJg==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sn2QY-0003gP-Sg for guix-patches@gnu.org; Sat, 07 Sep 2024 16:53:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH v4 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: Sat, 07 Sep 2024 20:53: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.172574232814055 (code B ref 72337); Sat, 07 Sep 2024 20:53:02 +0000 Received: (at 72337) by debbugs.gnu.org; 7 Sep 2024 20:52:08 +0000 Received: from localhost ([127.0.0.1]:57681 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pf-0003eX-EP for submit@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:08 -0400 Received: from confino.investici.org ([93.190.126.19]:59135) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sn2Pc-0003eH-T4 for 72337@debbugs.gnu.org; Sat, 07 Sep 2024 16:52:06 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1725742323; bh=6ugOMZGSXDJjz+6Ma9hN3Meqh+YRJ4sYm4AGwbmHS1M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JnjCWpBpsWbUHM2CLpazc1MTA+GpfIrHno7cwLmht7m+mwZnxT09nr1PwnUc/G4/Q Bq9vkt+zIgpoUxwc10yHxmCGzwGYugiNTiw3SOnnJ2eoLkc2IeP5sE2Mo+EqTty2rb l8Vik/YCj3MWuiyMyS/c3yuVgUPNQUwfxgaAxFMY= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4X1QJM0vxVz11Fl; 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 4X1QJM04ctz11FW; Sat, 7 Sep 2024 20:52:02 +0000 (UTC) Date: Sat, 7 Sep 2024 22:51:48 +0200 Message-ID: <2771695a2527240c89c0ba6879aeda0d4ab840ab.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 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 allocation logic for subid ranges. Subid ranges are ranges of contiguous subids that are mapped to a user in the host system. This patch implements a flexible allocation algorithm allowing users that do not want (or need) to specify details of the subid ranges that they are requesting to avoid doing so, while upholding requests of users that need to have specific ranges. * gnu/build/accounts.scm (list-set): New variable; (%subordinate-id-min): new variable; (%subordinate-id-max): new variable; (%subordinate-id-count): new variable; (subordinate-id?): new variable; (within-interval?): 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 Signed-off-by: Giacomo Leidi --- gnu/build/accounts.scm | 187 +++++++++++++++++++++++++++++++++++++++- gnu/system/accounts.scm | 30 +++++++ tests/accounts.scm | 152 ++++++++++++++++++++++++++++++++ 3 files changed, 368 insertions(+), 1 deletion(-) diff --git a/gnu/build/accounts.scm b/gnu/build/accounts.scm index ea8c69f205..be981fca38 100644 --- a/gnu/build/accounts.scm +++ b/gnu/build/accounts.scm @@ -25,6 +25,8 @@ (define-module (gnu build accounts) #:use-module (srfi srfi-11) #:use-module (srfi srfi-19) #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) #:use-module (ice-9 match) #:use-module (ice-9 vlist) #:use-module (ice-9 rdelim) @@ -74,8 +76,19 @@ (define-module (gnu build accounts) %id-max %system-id-min %system-id-max + %subordinate-id-min + %subordinate-id-max + %subordinate-id-count - user+group-databases)) + &subordinate-id-error + subordinate-id-error? + &subordinate-id-range-error + subordinate-id-range-error? + subordinate-id-range-error-message + subordinate-id-range-error-ranges + + user+group-databases + subuid+subgid-databases)) ;;; Commentary: ;;; @@ -91,6 +104,18 @@ (define-module (gnu build accounts) ;;; ;;; Code: + +;;; +;;; General utilities. +;;; + +(define (vlist-set vlst el k) + (if (>= k (vlist-length vlst)) + (vlist-append vlst (vlist-cons el vlist-null)) + (vlist-append + (vlist-take vlst k) + (vlist-cons el (vlist-drop vlst k))))) + ;;; ;;; Machinery to define user and group databases. @@ -342,6 +367,19 @@ (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 %subordinate-id-min 100000) +(define %subordinate-id-max 600100000) +(define %subordinate-id-count 65536) + +(define-condition-type &subordinate-id-error &error + subordinate-id-error?) +(define-condition-type &subordinate-id-range-error &subordinate-id-error + subordinate-id-range-error? + (message subordinate-id-range-error-message) + (ranges subordinate-id-range-error-ranges)) + (define (system-id? id) (and (> id %system-id-min) (<= id %system-id-max))) @@ -350,6 +388,10 @@ (define (user-id? id) (and (>= id %id-min) (< id %id-max))) +(define (subordinate-id? id) + (and (>= id %subordinate-id-min) + (< id %subordinate-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 +447,90 @@ (define* (reserve-ids allocation ids #:key (skip? #t)) (allocation-ids allocation) ids)))) +(define (within-interval? start end range) + "Returns #t when RANGE is included in the interval +bounded by START and END. Both ends of the interval +are included in the comparison." + (unless (subid-range-has-start? range) + (raise + (condition + (&subordinate-id-range-error + (ranges (list range)) + (message + "Subid ranges should have a start to be tested within +an interval."))))) + (and (<= start + (subid-range-start range)) + (<= (subid-range-end range) + end))) + +(define (insert-subid-range range vlst) + "Allocates a range of subids in VLST, based on RANGE. Ranges +that do not explicitly specify a start subid are fitted based on +their size. This procedure assumes VLIST is sorted by SUBID-RANGE-LESS and +that all VLST members have a start." + (define* (actualize r #:key (start %subordinate-id-min)) + (if (subid-range-has-start? r) + r + (subid-range + (inherit r) + (start start)))) + + (define vlst-length (vlist-length vlst)) + (define range-name (subid-range-name range)) + (define range-start (subid-range-start range)) + (define range-end (subid-range-end range)) + + (when (subid-range-has-start? range) + (unless (and (subordinate-id? range-start) + (subordinate-id? range-end)) + (raise + (condition + (&subordinate-id-range-error + (ranges (list range)) + (message + (string-append "Subid range of " range-name + " from " (number->string range-start) " to " + (number->string range-end) + " spans over illegal subids. Max allowed is " + (number->string %subordinate-id-max) ", min is " + (number->string %subordinate-id-min) "."))))))) + + (let loop ((i 0) + (start %subordinate-id-min) + (end (if (< vlst-length 1) + %subordinate-id-max + (- (subid-range-start + (vlist-ref vlst 0)) + 1)))) + (define actual-range + (actualize range #:start start)) + (cond + ((> i vlst-length) + (raise + (condition + (&subordinate-id-range-error + (ranges (list range)) + (message + (string-append "Couldn't fit " range-name + ", reached end of list.")))))) + ((within-interval? start end actual-range) + (vlist-set vlst actual-range i)) + (else + (loop (+ i 1) + (+ 1 (subid-range-end + (vlist-ref vlst (if (= i vlst-length) (- i 1) i)))) + (if (>= i (- vlst-length 1)) + %subordinate-id-max + (- (subid-range-start + (vlist-ref vlst (+ i 1))) + 1))))))) + +(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." + (vlist-fold insert-subid-range allocation ranges)) + (define (allocated? allocation id) "Return true if ID is already allocated as part of ALLOCATION." (->bool (vhash-assv id (allocation-ids allocation)))) @@ -540,6 +666,39 @@ (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." + (when (any (compose not subid-range-has-start?) current-ranges) + (raise + (condition + (&subordinate-id-range-error + (ranges current-ranges) + (message "Loaded ranges are supposed to have a start, but at least one does not."))))) + (define subids + ;; Mark all the currently used IDs and the explicitly requested IDs as + ;; reserved. + (reserve-subids (reserve-subids vlist-null + (list->vlist current-ranges)) + (list->vlist + (stable-sort ranges + subid-range-less)))) + + (map range->entry (vlist->list 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 +774,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..3d038568df 100644 --- a/tests/accounts.scm +++ b/tests/accounts.scm @@ -21,6 +21,7 @@ (define-module (test-accounts) #:use-module (gnu build accounts) #:use-module (gnu system accounts) #:use-module (srfi srfi-19) + #:use-module (srfi srfi-34) #:use-module (srfi srfi-64) #:use-module (ice-9 vlist) #:use-module (ice-9 match)) @@ -193,6 +194,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 +259,112 @@ (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 %subordinate-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") + (start %subordinate-id-min) + (count 100))))) + +(test-equal "allocate-subids with requested IDs ranges" + ;; Make sure the requested sub ID for "k" and "root" are honored. + (list (subid-entry (name "x") (start %subordinate-id-min) (count 200)) + (subid-entry (name "k") (start (+ %subordinate-id-min 300)) (count 100)) + (subid-entry (name "t") (start (+ %subordinate-id-min 500)) (count 899)) + (subid-entry (name "root") (start (+ %subordinate-id-min 2500)) (count 100))) + + (allocate-subids (list + (subid-range (name "root") (start (+ %subordinate-id-min 2500)) (count 100)) + (subid-range (name "k") (start (+ %subordinate-id-min 300)) (count 100))) + (list + (subid-range (name "x") (start %subordinate-id-min) (count 200)) + (subid-range (name "t") (start (+ %subordinate-id-min 500)) (count 899))))) + +(let ((inputs+currents + (list + (list + "ranges must have start" + (list (subid-range (name "m"))) + (list (subid-range (name "x"))) + "Loaded ranges are supposed to have a start, but at least one does not.") + (list + "ranges must fall within allowed max min subids" + (list (subid-range (name "m") + (start (- %subordinate-id-min 1)) + (count + (+ %subordinate-id-max %subordinate-id-min)))) + (list + (subid-range (name "root") (start %subordinate-id-min))) + "Subid range of m from 99999 to 600299998 spans over illegal subids. Max allowed is 600100000, min is 100000.")))) + + ;; Make sure it's impossible to explicitly request impossible allocations + (for-each + (match-lambda + ((test-name ranges current-ranges message) + (test-assert (string-append "allocate-subids, impossible allocations - " + test-name) + (guard (c ((and (subordinate-id-range-error? c) + (string=? message (subordinate-id-range-error-message c))) + #t)) + (allocate-subids ranges current-ranges) + #f)))) + inputs+currents)) + +(test-equal "allocate-subids with interleaving" + ;; Make sure the requested sub ID for "m" is honored and + ;; for "l" and "i" are correctly deduced. + (list (subid-entry (name "x") (start %subordinate-id-min) (count 200)) + (subid-entry (name "l") (start (+ %subordinate-id-min 200)) (count 1)) + (subid-entry (name "m") (start (+ %subordinate-id-min 201)) (count 27)) + (subid-entry (name "i") (start (+ %subordinate-id-min 228)) (count 2)) + (subid-entry (name "root") (start (+ %subordinate-id-min 231)) (count 100))) + (allocate-subids (list + (subid-range (name "m") (start (+ %subordinate-id-min 201)) (count 27)) + (subid-range (name "l") (count 1)) + (subid-range (name "i") (count 2))) + (list + (subid-range (name "x") (start %subordinate-id-min) (count 200)) + (subid-range (name "root") (start (+ %subordinate-id-min 231)) (count 100))))) + +(let ((inputs+currents + (list + ;; Try impossible before + (list + (list (subid-range (name "m") (start %subordinate-id-min) (count 16))) + (list + (subid-range (name "x") (start (+ 15 %subordinate-id-min)) (count 150))) + "Couldn't fit m, reached end of list.") + ;; Try impossible after + (list + (list (subid-range (name "m") (start %subordinate-id-min) (count 30))) + (list + (subid-range (name "x") (start (+ 29 %subordinate-id-min)) (count 150))) + "Couldn't fit m, reached end of list.") + ;; Try impossible between + (list + (list (subid-range (name "m") (start 100200) (count 500))) + (list + (subid-range (name "root") (start %subordinate-id-min) (count 100)) + (subid-range (name "x") (start (+ %subordinate-id-min 500)) (count 100))) + "Couldn't fit m, reached end of list.")))) + + ;; Make sure it's impossible to explicitly request impossible allocations + (for-each + (match-lambda + ((ranges current-ranges message) + (test-assert "allocate-subids with interleaving, impossible interleaving" + (guard (c ((and (subordinate-id-range-error? c) + (string=? message (subordinate-id-range-error-message c))) + #t)) + (allocate-subids ranges current-ranges) + #f)))) + inputs+currents)) + (test-equal "allocate-passwd" ;; Allocate UIDs in a stateless fashion. (list (password-entry (name "alice") (uid %id-min) (gid 1000) @@ -376,4 +484,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 %subordinate-id-min) + (count 100)) + (subid-entry (name "alice") + (start (+ %subordinate-id-min 100)) + (count 200)) + (subid-entry (name "bob") + (start (+ %subordinate-id-min 100 200)) + (count 200))) + (list + (subid-entry (name "root") + (start %subordinate-id-min) + (count 200)) + (subid-entry (name "alice") + (start (+ %subordinate-id-min 200)) + (count 400)) + (subid-entry (name "charlie") + (start (+ %subordinate-id-min 200 400)) + (count 300)))) + (call-with-values + (lambda () + (subuid+subgid-databases + (list (subid-range (name "root") + (start %subordinate-id-min) + (count 100)) + (subid-range (name "alice") + (start (+ %subordinate-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 %subordinate-id-min) + (count 200))) + #:current-subuids '())) + list)) + (test-end "accounts") From patchwork Sat Sep 7 20:51:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 67743 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 6C5D027BBEC; Sat, 7 Sep 2024 21:53:27 +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 D35F327BBE2 for ; Sat, 7 Sep 2024 21:53:24 +0100 (BST) 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 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 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))))