* [bug#72740] Add rootless-podman-service-type @ 2024-08-20 23:20 paul via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via ` (3 more replies) 0 siblings, 4 replies; 15+ messages in thread From: paul via Guix-patches via @ 2024-08-20 23:20 UTC (permalink / raw) To: 72740 [-- Attachment #1: Type: text/plain, Size: 1507 bytes --] Dear Guixers, I'm sending a patchset adding rootless Podman support to the Guix System. I'm currently using this on my systems as it's set up in my personal channel [0]. By adding the following to my own system config (use-modules (small-guix system accounts) (small-guix services containers)) (service iptables-service-type) (service rootless-podman-service-type (rootless-podman-configuration (subgids (list (subid-range (name "alice")))) (subuids (list (subid-range (name "alice")))))) I'm able to run the following rootless Podman hello world $ podman run -it --rm docker.io/alpine cat /etc/*release* NAME="Alpine Linux" ID=alpine VERSION_ID=3.20.2 PRETTY_NAME="Alpine Linux v3.20" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" and with guix shell podman compose I'm able to run this Podman compose hello world [1]: $ mkdir data $ echo hello world > data/index.html $ podman compose up -d ... exit code: 0 $ curl localhost:8080 hello world This patch depends on the subids-service-type from issue #72337 [2]. Please let me know your thoughts. Thank you for your work, giacomo [0]: https://gitlab.com/orang3/small-guix/-/blob/master/small-guix/services/containers.scm?ref_type=heads#L197 [1]: https://github.com/fishinthecalculator/rootless-podman-nginx-static-server [2]: https://issues.guix.gnu.org/72337 [-- Attachment #2: Type: text/html, Size: 2424 bytes --] ^ permalink raw reply [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates. 2024-08-20 23:20 [bug#72740] Add rootless-podman-service-type paul via Guix-patches via @ 2024-08-20 23:21 ` Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via ` (2 more replies) 2024-08-21 8:18 ` [bug#72740] " paul via Guix-patches via ` (2 subsequent siblings) 3 siblings, 3 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/system/pam.scm: Export pam-service-name?, pam-entry? and pam-limits-entry?. Change-Id: I609acfcaae85b4969dc385b72b307e470f5a246e --- gnu/system/pam.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gnu/system/pam.scm b/gnu/system/pam.scm index a035a92e25..5c7c4e8153 100644 --- a/gnu/system/pam.scm +++ b/gnu/system/pam.scm @@ -34,6 +34,7 @@ (define-module (gnu system pam) #:use-module ((guix utils) #:select (%current-system)) #:use-module (gnu packages linux) #:export (pam-service + pam-service-name? pam-service-name pam-service-account pam-service-auth @@ -41,11 +42,13 @@ (define-module (gnu system pam) pam-service-session pam-entry + pam-entry? pam-entry-control pam-entry-module pam-entry-arguments pam-limits-entry + pam-limits-entry? pam-limits-entry-domain pam-limits-entry-type pam-limits-entry-item base-commit: 00245fdcd4909d7e6b20fe88f5d089717115adc1 -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH 2/4] services: pam: Allow extension of pam limits. 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 ` Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/services/pam.scm (pam-limits-service-type): Allow extension of pam limits rules from users and services. Change-Id: I93a363d1a2887493d52ef3ae32fc9721f81ddfa8 --- gnu/services/base.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 4b5b103cc3..e4e59da433 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -1680,6 +1680,8 @@ (define pam-limits-service-type (service-type (name 'limits) + (compose concatenate) + (extend append) (extensions (list (service-extension pam-root-service-type (lambda (config) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH 3/4] services: iptables: Provide a default value. 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 ` Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi There doesn't seem to be a reason to force users to write (service iptables-service-type (iptables-configuration)) instead of simply (service iptables-service-type) This patch provides a default value for the iptables-service-type. * gnu/services/networking.scm (iptables-service-type): Set default-value. Change-Id: I93b6c544dfb064c7a0a999549dff61007a38f842 --- gnu/services/networking.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 12d8934e43..c70fea7813 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -2055,6 +2055,7 @@ (define (iptables-shepherd-service config) (define iptables-service-type (service-type (name 'iptables) + (default-value (iptables-configuration)) (description "Run @command{iptables-restore}, setting up the specified rules.") (extensions -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH 4/4] services: Add rootless-podman-service-type. 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 ` Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-20 23:21 UTC (permalink / raw) To: 72740 Cc: Giacomo Leidi, Florian Pelz, Ludovic Courtès, Matthew Trzcinski, Maxim Cournoyer * gnu/services/containers.scm: New file; (rootless-podman-configuration): new variable; (rootless-podman-service-subids): new variable; (rootless-podman-service-accounts): new variable; (rootless-podman-service-profile): new variable; (rootless-podman-shepherd-services): new variable; (rootless-podman-service-etc): new variable; (rootless-podman-service-type): new variable. * gnu/local.mk: Test it. * gnu/local.mk: Add them. * doc/guix.texi (Miscellaneous Services): Document it. Change-Id: I041496474c1027da353bd6852f2554a065914d7a --- doc/guix.texi | 104 +++++++++++ gnu/local.mk | 2 + gnu/services/containers.scm | 216 +++++++++++++++++++++ gnu/tests/containers.scm | 361 ++++++++++++++++++++++++++++++++++++ 4 files changed, 683 insertions(+) create mode 100644 gnu/services/containers.scm create mode 100644 gnu/tests/containers.scm diff --git a/doc/guix.texi b/doc/guix.texi index 0e1e253b02..eb6a1b2442 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -40852,6 +40852,110 @@ Miscellaneous Services invoke @command{singularity run} and similar commands. @end defvar +@cindex Rootless Podman +@subsubheading Rootless Podman Service + +The @code{(gnu services containers)} module provides the following service. + + +@cindex Rootless Podman, container management tool +@defvar rootless-podman-service-type + +@url{https://www.sylabs.io/singularity/, Singularity} is a container management +tool. In addition to providing a drop-in replacement for Docker, Podman offers +the ability to run containers in rootless mode. This allows regular users to +deploy containers without elevated privileges. + +The @code{rootless-podman-service-type} sets up the Guix System to allow +unprivileged users to run @command{podman} commands: + +@lisp +(use-service-modules containers networking @dots{}) + +(operating-system + ;; @dots{} + (users (cons (user-account + (name "alice") + (comment "Bob's sister") + (group "users") + + ;; Adding the account to the "cgroup" group + ;; makes it possible to run podman commands. + (supplementary-groups '("cgroup" "wheel" + "audio" "video"))) + %base-user-accounts)) + (services + (list + (service iptables-service-type) + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "alice")))) + (subuids + (list (subid-range (name "alice"))))))))) +@end lisp + +The @code{iptables-service-type} is required for Podman to be able to setup its +own networks. Due to the change in user groups and file systems it is +recommended to reboot (or at least logout), before trying to run Podman commands. + +To test your installation you can run: + +@example +$ podman run -it --rm docker.io/alpine cat /etc/*release* +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.20.2 +PRETTY_NAME="Alpine Linux v3.20" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" +@end example + +@end defvar + +@c %start of fragment + +@deftp {Data Type} rootless-podman-configuration +Available @code{rootless-podman-configuration} fields are: + +@table @asis +@item @code{podman} (default: @code{podman}) (type: package) +The Podman package that will be installed in the system profile. + +@item @code{group-name} (default: @code{"cgroup"}) (type: string) +The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group. + +@item @code{containers-registries} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file. + +@item @code{containers-storage} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file. + +@item @code{containers-policy} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file. + +@item @code{pam-limits} (type: list-of-pam-limits-entries) +The PAM limits to be set for rootless Podman. + +@item @code{subgids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subgids that will be +available for each configured user. + +@item @code{subuids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subuids that will be +available for each configured user. + +@end table + +@end deftp + + +@c %end of fragment + @cindex OCI-backed, Shepherd services @subsubheading OCI backed services diff --git a/gnu/local.mk b/gnu/local.mk index 3b0a3858f7..a543f1ddc9 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -708,6 +708,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/cgit.scm \ %D%/services/ci.scm \ %D%/services/configuration.scm \ + %D%/services/containers.scm \ %D%/services/cuirass.scm \ %D%/services/cups.scm \ %D%/services/databases.scm \ @@ -813,6 +814,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/base.scm \ %D%/tests/cachefilesd.scm \ %D%/tests/ci.scm \ + %D%/tests/containers.scm \ %D%/tests/cups.scm \ %D%/tests/databases.scm \ %D%/tests/desktop.scm \ diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm new file mode 100644 index 0000000000..2337a4a001 --- /dev/null +++ b/gnu/services/containers.scm @@ -0,0 +1,216 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu services containers) + #:use-module (gnu packages containers) + #:use-module (gnu packages file-systems) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu system accounts) + #:use-module (gnu system shadow) + #:use-module (gnu system pam) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (srfi srfi-1) + #:export (rootless-podman-configuration + rootless-podman-configuration? + rootless-podman-configuration-fields + rootless-podman-configuration-podman + rootless-podman-configuration-group-name + rootless-podman-configuration-containers-registries + rootless-podman-configuration-containers-storage + rootless-podman-configuration-containers-policy + rootless-podman-configuration-pam-limits + rootless-podman-configuration-subgids + rootless-podman-configuration-subuids + + rootless-podman-service-subids + rootless-podman-service-accounts + rootless-podman-service-profile + rootless-podman-shepherd-services + rootless-podman-service-etc + + rootless-podman-service-type)) + +(define (gexp-or-string? value) + (or (gexp? value) + (string? value))) + +(define (lowerable? value) + (or (file-like? value) + (gexp-or-string? value))) + +(define list-of-pam-limits-entries? + (list-of pam-limits-entry?)) + +(define list-of-subid-ranges? + (list-of subid-range?)) + +(define-configuration/no-serialization rootless-podman-configuration + (podman + (package podman) + "The Podman package that will be installed in the system profile.") + (group-name + (string "cgroup") + "The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group.") + (containers-registries + (lowerable + (plain-file "registries.conf" + (string-append "unqualified-search-registries = ['docker.io','" + "registry.fedora.org','registry.opensuse.org']"))) + "A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file.") + (containers-storage + (lowerable + (plain-file "storage.conf" + "[storage] +driver = \"overlay\"")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file.") + (containers-policy + (lowerable + (plain-file "policy.json" + "{\"default\": [{\"type\": \"insecureAcceptAnything\"}]}")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file.") + (pam-limits + (list-of-pam-limits-entries + (list (pam-limits-entry "*" 'both 'nofile 100000))) + "The PAM limits to be set for rootless Podman.") + (subgids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subgids that will be +available for each configured user.") + (subuids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subuids that will be +available for each configured user.")) + +(define rootless-podman-service-profile + (lambda (config) + (list + (rootless-podman-configuration-podman config)))) + +(define rootless-podman-service-etc + (lambda (config) + (list `("containers/registries.conf" + ,(rootless-podman-configuration-containers-registries config)) + `("containers/storage.conf" + ,(rootless-podman-configuration-containers-storage config)) + `("containers/policy.json" + ,(rootless-podman-configuration-containers-policy config))))) + +(define rootless-podman-service-subids + (lambda (config) + (subids-extension + (subgids (rootless-podman-configuration-subgids config)) + (subuids (rootless-podman-configuration-subuids config))))) + +(define rootless-podman-service-accounts + (lambda (config) + (list (user-group (name (rootless-podman-configuration-group-name config)) + (system? #t))))) + +(define (cgroups-fs-owner-entrypoint config) + (define group + (rootless-podman-configuration-group-name config)) + (program-file "cgroups2-fs-owner-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting /sys/fs/cgroup " + "group ownership to " #$group " && chown -v " + "root:" #$group " /sys/fs/cgroup && " + "chmod -v 775 /sys/fs/cgroup && chown -v " + "root:" #$group " /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads} && " + "chmod -v 664 /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads}")))) + +(define (rootless-podman-cgroups-fs-owner-service config) + (shepherd-service (provision '(cgroups2-fs-owner)) + (requirement + '(dbus-system + elogind + networking + udev + file-system-/sys/fs/cgroup + cgroups2-limits)) + (one-shot? #t) + (documentation + "Set ownership of /sys/fs/cgroup to the configured group.") + (start + #~(make-forkexec-constructor + (list + #$(cgroups-fs-owner-entrypoint config)))) + (stop + #~(make-kill-destructor)))) + +(define cgroups-limits-entrypoint + (program-file "cgroups2-limits-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting cgroups v2 limits && " + "echo +cpu +cpuset +memory +pids" + " >> /sys/fs/cgroup/cgroup.subtree_control")))) + +(define (rootless-podman-cgroups-limits-service config) + (shepherd-service (provision '(cgroups2-limits)) + (requirement + '(dbus-system + elogind + networking + udev + file-system-/sys/fs/cgroup)) + (one-shot? #t) + (documentation + "Allow setting cgroups limits: cpu, cpuset, memory and +pids.") + (start + #~(make-forkexec-constructor + (list + #$cgroups-limits-entrypoint))) + (stop + #~(make-kill-destructor)))) + +(define (rootless-podman-shepherd-services config) + (list + (rootless-podman-cgroups-limits-service config) + (rootless-podman-cgroups-fs-owner-service config))) + +(define rootless-podman-service-type + (service-type (name 'rootless-podman) + (extensions + (list + (service-extension subids-service-type + rootless-podman-service-subids) + (service-extension account-service-type + rootless-podman-service-accounts) + (service-extension profile-service-type + rootless-podman-service-profile) + (service-extension shepherd-root-service-type + rootless-podman-shepherd-services) + (service-extension pam-limits-service-type + rootless-podman-configuration-pam-limits) + (service-extension etc-service-type + rootless-podman-service-etc))) + (default-value (rootless-podman-configuration)) + (description + "This service configures rootless @code{podman} on the Guix System."))) diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm new file mode 100644 index 0000000000..e60b5e5b8d --- /dev/null +++ b/gnu/tests/containers.scm @@ -0,0 +1,361 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu tests containers) + #:use-module (gnu) + #:use-module (gnu tests) + #:use-module (guix build-system trivial) + #:use-module (gnu packages bash) + #:use-module (gnu packages containers) + #:use-module (gnu packages guile) + #:use-module (gnu packages guile-xyz) + #:use-module (gnu services) + #:use-module (gnu services containers) + #:use-module (gnu services desktop) + #:use-module (gnu services dbus) + #:use-module (gnu services networking) + #:use-module (gnu services shepherd) + #:use-module (gnu system) + #:use-module (gnu system accounts) + #:use-module (gnu system vm) + #:use-module (guix gexp) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix monads) + #:use-module (guix packages) + #:use-module (guix profiles) + #:use-module ((guix scripts pack) #:prefix pack:) + #:use-module (guix store) + #:export (%test-rootless-podman)) + +\f +(define %rootless-podman-os + (simple-operating-system + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "dummy")))) + (subuids + (list (subid-range (name "dummy")))))) + + (service dhcp-client-service-type) + (service dbus-root-service-type) + (service polkit-service-type) + (service elogind-service-type) + + (simple-service 'shared-root-service + shepherd-root-service-type + (list + (shepherd-service + (provision '(rootless-podman-shared-root-fs)) + (requirement + '(file-systems)) + (one-shot? #t) + (documentation + "Buildah/Podman running as rootless expects the bind mount +to be shared. This service sets it so.") + (start + #~(make-forkexec-constructor + (list + #$(program-file "rootless-podman-shared-root-fs-entrypoint" + #~(system* + "mount" "--make-shared" "/"))))) + (stop + #~(make-kill-destructor))))) + + (simple-service 'accounts + account-service-type + (list (user-account + (name "dummy") + (group "users") + (supplementary-groups '("wheel" "netdev" "cgroup" + "audio" "video"))))))) + +(define (run-rootless-podman-test oci-tarball) + + (define os + (marionette-operating-system + (operating-system-with-gc-roots + %rootless-podman-os + (list oci-tarball)) + #: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) + (gnu services herd)) + #~(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)) + (define out-dir "/tmp") + + (test-runner-current (system-test-runner #$output)) + (test-begin "rootless-podman") + + (test-assert "service started" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service 'cgroups2-fs-owner) + (#f #f) + ;; herd returns (running #f), likely because of one shot, + ;; so consider any non-error a success. + (('service response-parts ...) #t))) + marionette)) + + (test-equal "/sys/fs/cgroup/cgroup.subtree_control content is sound" + (list "cpu" "cpuset" "memory" "pids") + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (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") + "/sys/fs/cgroup/cgroup.subtree_control"))) + (sort-list (string-split (first response1) #\space) string<?))) + marionette)) + + (test-equal "/sys/fs/cgroup has correct permissions" + '("cgroup" "cgroup") + (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* ((bash + ,(string-append #$bash "/bin/bash")) + (response1 + (slurp bash "-c" + (string-append "ls -la /sys/fs/cgroup | " + "grep -E ' \\./?$' | awk '{ print $4 }'"))) + (response2 (slurp bash "-c" + (string-append "ls -l /sys/fs/cgroup/cgroup" + ".{procs,subtree_control,threads} | " + "awk '{ print $4 }' | sort -u")))) + (list (string-join response1 "\n") (string-join response2 "\n")))) + marionette)) + + (test-equal "Load oci image and run it (unprivileged)" + '("hello world" "hi!" "JSON!" #o1777) + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (ice-9 popen) + (ice-9 match) + (ice-9 rdelim)) + + (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 + (pk 'wait-for-file file) + (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 "dummy"))) + (setuid (passwd:uid (getpw "dummy"))) + + (let* ((loaded (slurp ,(string-append #$podman + "/bin/podman") + "load" "-i" + ,#$oci-tarball)) + (repository&tag "localhost/guile-guest:latest") + (response1 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" + "--entrypoint" "bin/Guile" + repository&tag + "/aa.scm")) + (response2 (slurp ;default entry point + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(display \"hi!\")'")) + + ;; Check whether (json) is in $GUILE_LOAD_PATH. + (response3 (slurp ;default entry point + environment + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(use-modules (json)) + (display (json-string->scm (scm->json-string \"JSON!\")))'")) + + ;; Check whether /tmp exists. + (response4 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag "-c" + "'(display (stat:perms (lstat \"/tmp\")))'"))) + (call-with-output-file (string-append ,out-dir "/response1") + (lambda (port) + (display (string-join response1 " ") port))) + (call-with-output-file (string-append ,out-dir "/response2") + (lambda (port) + (display (string-join response2 " ") port))) + (call-with-output-file (string-append ,out-dir "/response3") + (lambda (port) + (display (string-join response3 " ") port))) + (call-with-output-file (string-append ,out-dir "/response4") + (lambda (port) + (display (string-join response4 " ") port))))) + (lambda () + (primitive-exit 127)))) + (pid + (cdr (waitpid pid)))) + (wait-for-file (string-append ,out-dir "/response4")) + (append + (slurp "cat" (string-append ,out-dir "/response1")) + (slurp "cat" (string-append ,out-dir "/response2")) + (slurp "cat" (string-append ,out-dir "/response3")) + (map string->number (slurp "cat" (string-append ,out-dir "/response4"))))) + marionette)) + + (test-end)))) + + (gexp->derivation "rootless-podman-test" test)) + +(define (build-tarball&run-rootless-podman-test) + (mlet* %store-monad + ((_ (set-grafting #f)) + (guile (set-guile-for-build (default-guile))) + (guest-script-package -> + (package + (name "guest-script") + (version "0") + (source #f) + (build-system trivial-build-system) + (arguments `(#:guile ,guile-3.0 + #:builder + (let ((out (assoc-ref %outputs "out"))) + (mkdir out) + (call-with-output-file (string-append out "/a.scm") + (lambda (port) + (display "(display \"hello world\n\")" port))) + #t))) + (synopsis "Display hello world using Guile") + (description "This package displays the text \"hello world\" on the +standard output device and then enters a new line.") + (home-page #f) + (license license:public-domain))) + (profile (profile-derivation (packages->manifest + (list guile-3.0 guile-json-3 + guest-script-package)) + #:hooks '() + #:locales? #f)) + (tarball (pack:docker-image + "docker-pack" profile + #:symlinks '(("/bin/Guile" -> "bin/guile") + ("aa.scm" -> "a.scm")) + #:extra-options + '(#:image-tag "guile-guest") + #:entry-point "bin/guile" + #:localstatedir? #t))) + (run-rootless-podman-test tarball))) + +(define %test-rootless-podman + (system-test + (name "rootless-podman") + (description "Test rootless Podman service.") + (value (build-tarball&run-rootless-podman-test)))) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] Add rootless-podman-service-type 2024-08-20 23:20 [bug#72740] Add rootless-podman-service-type paul via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via @ 2024-08-21 8:18 ` paul via Guix-patches via 2024-08-23 11:39 ` paul via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 3 siblings, 1 reply; 15+ messages in thread From: paul via Guix-patches via @ 2024-08-21 8:18 UTC (permalink / raw) To: 72740 Dear Guixers, I'm sending a v2. This revision contains a small change: Buildah/Podman running as rootless expects the bind mount to be shared. This patchset contains a Shepherd service that sets it so. Thank you very much for your help, giacomo ^ permalink raw reply [flat|nested] 15+ messages in thread
* [bug#72740] Add rootless-podman-service-type 2024-08-21 8:18 ` [bug#72740] " paul via Guix-patches via @ 2024-08-23 11:39 ` paul via Guix-patches via 0 siblings, 0 replies; 15+ messages in thread From: paul via Guix-patches via @ 2024-08-23 11:39 UTC (permalink / raw) To: 72740 Dear Guixers, I'm sending a v3. The only fix in this revision is that instead of exporting the (non-existing) pam-service-name? procedure, the pam-service? predicate is rightly exposed with the other pam-service* procedures. Thank you for your work, giacomo ^ permalink raw reply [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates. 2024-08-20 23:20 [bug#72740] Add rootless-podman-service-type paul via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-21 8:18 ` [bug#72740] " paul via Guix-patches via @ 2024-08-21 8:19 ` Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via ` (2 more replies) 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 3 siblings, 3 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/system/pam.scm: Export pam-service-name?, pam-entry? and pam-limits-entry?. Change-Id: I609acfcaae85b4969dc385b72b307e470f5a246e --- gnu/system/pam.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gnu/system/pam.scm b/gnu/system/pam.scm index a035a92e25..5c7c4e8153 100644 --- a/gnu/system/pam.scm +++ b/gnu/system/pam.scm @@ -34,6 +34,7 @@ (define-module (gnu system pam) #:use-module ((guix utils) #:select (%current-system)) #:use-module (gnu packages linux) #:export (pam-service + pam-service-name? pam-service-name pam-service-account pam-service-auth @@ -41,11 +42,13 @@ (define-module (gnu system pam) pam-service-session pam-entry + pam-entry? pam-entry-control pam-entry-module pam-entry-arguments pam-limits-entry + pam-limits-entry? pam-limits-entry-domain pam-limits-entry-type pam-limits-entry-item base-commit: 00245fdcd4909d7e6b20fe88f5d089717115adc1 -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v2 2/4] services: pam: Allow extension of pam limits. 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 ` Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/services/pam.scm (pam-limits-service-type): Allow extension of pam limits rules from users and services. Change-Id: I93a363d1a2887493d52ef3ae32fc9721f81ddfa8 --- gnu/services/base.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 4b5b103cc3..e4e59da433 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -1680,6 +1680,8 @@ (define pam-limits-service-type (service-type (name 'limits) + (compose concatenate) + (extend append) (extensions (list (service-extension pam-root-service-type (lambda (config) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v2 3/4] services: iptables: Provide a default value. 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 ` Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi There doesn't seem to be a reason to force users to write (service iptables-service-type (iptables-configuration)) instead of simply (service iptables-service-type) This patch provides a default value for the iptables-service-type. * gnu/services/networking.scm (iptables-service-type): Set default-value. Change-Id: I93b6c544dfb064c7a0a999549dff61007a38f842 --- gnu/services/networking.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 12d8934e43..c70fea7813 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -2055,6 +2055,7 @@ (define (iptables-shepherd-service config) (define iptables-service-type (service-type (name 'iptables) + (default-value (iptables-configuration)) (description "Run @command{iptables-restore}, setting up the specified rules.") (extensions -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v2 4/4] services: Add rootless-podman-service-type. 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 ` Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-21 8:19 UTC (permalink / raw) To: 72740 Cc: Giacomo Leidi, Florian Pelz, Ludovic Courtès, Matthew Trzcinski, Maxim Cournoyer * gnu/services/containers.scm: New file; (rootless-podman-configuration): new variable; (rootless-podman-service-subids): new variable; (rootless-podman-service-accounts): new variable; (rootless-podman-service-profile): new variable; (rootless-podman-shepherd-services): new variable; (rootless-podman-service-etc): new variable; (rootless-podman-service-type): new variable. * gnu/local.mk: Test it. * gnu/local.mk: Add them. * doc/guix.texi (Miscellaneous Services): Document it. Change-Id: I041496474c1027da353bd6852f2554a065914d7a --- doc/guix.texi | 104 +++++++++++ gnu/local.mk | 2 + gnu/services/containers.scm | 238 +++++++++++++++++++++++++ gnu/tests/containers.scm | 340 ++++++++++++++++++++++++++++++++++++ 4 files changed, 684 insertions(+) create mode 100644 gnu/services/containers.scm create mode 100644 gnu/tests/containers.scm diff --git a/doc/guix.texi b/doc/guix.texi index 0e1e253b02..eb6a1b2442 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -40852,6 +40852,110 @@ Miscellaneous Services invoke @command{singularity run} and similar commands. @end defvar +@cindex Rootless Podman +@subsubheading Rootless Podman Service + +The @code{(gnu services containers)} module provides the following service. + + +@cindex Rootless Podman, container management tool +@defvar rootless-podman-service-type + +@url{https://www.sylabs.io/singularity/, Singularity} is a container management +tool. In addition to providing a drop-in replacement for Docker, Podman offers +the ability to run containers in rootless mode. This allows regular users to +deploy containers without elevated privileges. + +The @code{rootless-podman-service-type} sets up the Guix System to allow +unprivileged users to run @command{podman} commands: + +@lisp +(use-service-modules containers networking @dots{}) + +(operating-system + ;; @dots{} + (users (cons (user-account + (name "alice") + (comment "Bob's sister") + (group "users") + + ;; Adding the account to the "cgroup" group + ;; makes it possible to run podman commands. + (supplementary-groups '("cgroup" "wheel" + "audio" "video"))) + %base-user-accounts)) + (services + (list + (service iptables-service-type) + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "alice")))) + (subuids + (list (subid-range (name "alice"))))))))) +@end lisp + +The @code{iptables-service-type} is required for Podman to be able to setup its +own networks. Due to the change in user groups and file systems it is +recommended to reboot (or at least logout), before trying to run Podman commands. + +To test your installation you can run: + +@example +$ podman run -it --rm docker.io/alpine cat /etc/*release* +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.20.2 +PRETTY_NAME="Alpine Linux v3.20" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" +@end example + +@end defvar + +@c %start of fragment + +@deftp {Data Type} rootless-podman-configuration +Available @code{rootless-podman-configuration} fields are: + +@table @asis +@item @code{podman} (default: @code{podman}) (type: package) +The Podman package that will be installed in the system profile. + +@item @code{group-name} (default: @code{"cgroup"}) (type: string) +The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group. + +@item @code{containers-registries} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file. + +@item @code{containers-storage} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file. + +@item @code{containers-policy} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file. + +@item @code{pam-limits} (type: list-of-pam-limits-entries) +The PAM limits to be set for rootless Podman. + +@item @code{subgids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subgids that will be +available for each configured user. + +@item @code{subuids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subuids that will be +available for each configured user. + +@end table + +@end deftp + + +@c %end of fragment + @cindex OCI-backed, Shepherd services @subsubheading OCI backed services diff --git a/gnu/local.mk b/gnu/local.mk index 3b0a3858f7..a543f1ddc9 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -708,6 +708,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/cgit.scm \ %D%/services/ci.scm \ %D%/services/configuration.scm \ + %D%/services/containers.scm \ %D%/services/cuirass.scm \ %D%/services/cups.scm \ %D%/services/databases.scm \ @@ -813,6 +814,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/base.scm \ %D%/tests/cachefilesd.scm \ %D%/tests/ci.scm \ + %D%/tests/containers.scm \ %D%/tests/cups.scm \ %D%/tests/databases.scm \ %D%/tests/desktop.scm \ diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm new file mode 100644 index 0000000000..03f0649c0d --- /dev/null +++ b/gnu/services/containers.scm @@ -0,0 +1,238 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu services containers) + #:use-module (gnu packages containers) + #:use-module (gnu packages file-systems) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu system accounts) + #:use-module (gnu system shadow) + #:use-module (gnu system pam) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (srfi srfi-1) + #:export (rootless-podman-configuration + rootless-podman-configuration? + rootless-podman-configuration-fields + rootless-podman-configuration-podman + rootless-podman-configuration-group-name + rootless-podman-configuration-containers-registries + rootless-podman-configuration-containers-storage + rootless-podman-configuration-containers-policy + rootless-podman-configuration-pam-limits + rootless-podman-configuration-subgids + rootless-podman-configuration-subuids + + rootless-podman-service-subids + rootless-podman-service-accounts + rootless-podman-service-profile + rootless-podman-shepherd-services + rootless-podman-service-etc + + rootless-podman-service-type)) + +(define (gexp-or-string? value) + (or (gexp? value) + (string? value))) + +(define (lowerable? value) + (or (file-like? value) + (gexp-or-string? value))) + +(define list-of-pam-limits-entries? + (list-of pam-limits-entry?)) + +(define list-of-subid-ranges? + (list-of subid-range?)) + +(define-configuration/no-serialization rootless-podman-configuration + (podman + (package podman) + "The Podman package that will be installed in the system profile.") + (group-name + (string "cgroup") + "The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group.") + (containers-registries + (lowerable + (plain-file "registries.conf" + (string-append "unqualified-search-registries = ['docker.io','" + "registry.fedora.org','registry.opensuse.org']"))) + "A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file.") + (containers-storage + (lowerable + (plain-file "storage.conf" + "[storage] +driver = \"overlay\"")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file.") + (containers-policy + (lowerable + (plain-file "policy.json" + "{\"default\": [{\"type\": \"insecureAcceptAnything\"}]}")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file.") + (pam-limits + (list-of-pam-limits-entries + (list (pam-limits-entry "*" 'both 'nofile 100000))) + "The PAM limits to be set for rootless Podman.") + (subgids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subgids that will be +available for each configured user.") + (subuids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subuids that will be +available for each configured user.")) + +(define rootless-podman-service-profile + (lambda (config) + (list + (rootless-podman-configuration-podman config)))) + +(define rootless-podman-service-etc + (lambda (config) + (list `("containers/registries.conf" + ,(rootless-podman-configuration-containers-registries config)) + `("containers/storage.conf" + ,(rootless-podman-configuration-containers-storage config)) + `("containers/policy.json" + ,(rootless-podman-configuration-containers-policy config))))) + +(define rootless-podman-service-subids + (lambda (config) + (subids-extension + (subgids (rootless-podman-configuration-subgids config)) + (subuids (rootless-podman-configuration-subuids config))))) + +(define rootless-podman-service-accounts + (lambda (config) + (list (user-group (name (rootless-podman-configuration-group-name config)) + (system? #t))))) + +(define (cgroups-fs-owner-entrypoint config) + (define group + (rootless-podman-configuration-group-name config)) + (program-file "cgroups2-fs-owner-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting /sys/fs/cgroup " + "group ownership to " #$group " && chown -v " + "root:" #$group " /sys/fs/cgroup && " + "chmod -v 775 /sys/fs/cgroup && chown -v " + "root:" #$group " /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads} && " + "chmod -v 664 /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads}")))) + +(define (rootless-podman-cgroups-fs-owner-service config) + (shepherd-service (provision '(cgroups2-fs-owner)) + (requirement + '(dbus-system + elogind + file-system-/sys/fs/cgroup + networking + udev + cgroups2-limits)) + (one-shot? #t) + (documentation + "Set ownership of /sys/fs/cgroup to the configured group.") + (start + #~(make-forkexec-constructor + (list + #$(cgroups-fs-owner-entrypoint config)))) + (stop + #~(make-kill-destructor)))) + +(define cgroups-limits-entrypoint + (program-file "cgroups2-limits-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting cgroups v2 limits && " + "echo +cpu +cpuset +memory +pids" + " >> /sys/fs/cgroup/cgroup.subtree_control")))) + +(define (rootless-podman-cgroups-limits-service config) + (shepherd-service (provision '(cgroups2-limits)) + (requirement + '(dbus-system + elogind + networking + udev + file-system-/sys/fs/cgroup + rootless-podman-shared-root-fs)) + (one-shot? #t) + (documentation + "Allow setting cgroups limits: cpu, cpuset, memory and +pids.") + (start + #~(make-forkexec-constructor + (list + #$cgroups-limits-entrypoint))) + (stop + #~(make-kill-destructor)))) + +(define rootless-podman-shared-root-fs-entrypoint + (program-file "rootless-podman-shared-root-fs-entrypoint" + #~(system* + "mount" "--make-shared" "/"))) + +(define (rootless-podman-shared-root-fs-service config) + (shepherd-service (provision '(rootless-podman-shared-root-fs)) + (requirement + '(user-processes)) + (one-shot? #t) + (documentation + "Buildah/Podman running as rootless expects the bind mount +to be shared. This service sets it so.") + (start + #~(make-forkexec-constructor + (list + #$rootless-podman-shared-root-fs-entrypoint))) + (stop + #~(make-kill-destructor)))) + +(define (rootless-podman-shepherd-services config) + (list + (rootless-podman-shared-root-fs-service config) + (rootless-podman-cgroups-limits-service config) + (rootless-podman-cgroups-fs-owner-service config))) + +(define rootless-podman-service-type + (service-type (name 'rootless-podman) + (extensions + (list + (service-extension subids-service-type + rootless-podman-service-subids) + (service-extension account-service-type + rootless-podman-service-accounts) + (service-extension profile-service-type + rootless-podman-service-profile) + (service-extension shepherd-root-service-type + rootless-podman-shepherd-services) + (service-extension pam-limits-service-type + rootless-podman-configuration-pam-limits) + (service-extension etc-service-type + rootless-podman-service-etc))) + (default-value (rootless-podman-configuration)) + (description + "This service configures rootless @code{podman} on the Guix System."))) diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm new file mode 100644 index 0000000000..ba2fb22df6 --- /dev/null +++ b/gnu/tests/containers.scm @@ -0,0 +1,340 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu tests containers) + #:use-module (gnu) + #:use-module (gnu tests) + #:use-module (guix build-system trivial) + #:use-module (gnu packages bash) + #:use-module (gnu packages containers) + #:use-module (gnu packages guile) + #:use-module (gnu packages guile-xyz) + #:use-module (gnu services) + #:use-module (gnu services containers) + #:use-module (gnu services desktop) + #:use-module (gnu services dbus) + #:use-module (gnu services networking) + #:use-module (gnu system) + #:use-module (gnu system accounts) + #:use-module (gnu system vm) + #:use-module (guix gexp) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix monads) + #:use-module (guix packages) + #:use-module (guix profiles) + #:use-module ((guix scripts pack) #:prefix pack:) + #:use-module (guix store) + #:export (%test-rootless-podman)) + +\f +(define %rootless-podman-os + (simple-operating-system + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "dummy")))) + (subuids + (list (subid-range (name "dummy")))))) + + (service dhcp-client-service-type) + (service dbus-root-service-type) + (service polkit-service-type) + (service elogind-service-type) + + (simple-service 'accounts + account-service-type + (list (user-account + (name "dummy") + (group "users") + (supplementary-groups '("wheel" "netdev" "cgroup" + "audio" "video"))))))) + +(define (run-rootless-podman-test oci-tarball) + + (define os + (marionette-operating-system + (operating-system-with-gc-roots + %rootless-podman-os + (list oci-tarball)) + #: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) + (gnu services herd)) + #~(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)) + (define out-dir "/tmp") + + (test-runner-current (system-test-runner #$output)) + (test-begin "rootless-podman") + + (test-assert "service started" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service 'cgroups2-fs-owner) + (#f #f) + ;; herd returns (running #f), likely because of one shot, + ;; so consider any non-error a success. + (('service response-parts ...) #t))) + marionette)) + + (test-equal "/sys/fs/cgroup/cgroup.subtree_control content is sound" + (list "cpu" "cpuset" "memory" "pids") + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (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") + "/sys/fs/cgroup/cgroup.subtree_control"))) + (sort-list (string-split (first response1) #\space) string<?))) + marionette)) + + (test-equal "/sys/fs/cgroup has correct permissions" + '("cgroup" "cgroup") + (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* ((bash + ,(string-append #$bash "/bin/bash")) + (response1 + (slurp bash "-c" + (string-append "ls -la /sys/fs/cgroup | " + "grep -E ' \\./?$' | awk '{ print $4 }'"))) + (response2 (slurp bash "-c" + (string-append "ls -l /sys/fs/cgroup/cgroup" + ".{procs,subtree_control,threads} | " + "awk '{ print $4 }' | sort -u")))) + (list (string-join response1 "\n") (string-join response2 "\n")))) + marionette)) + + (test-equal "Load oci image and run it (unprivileged)" + '("hello world" "hi!" "JSON!" #o1777) + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (ice-9 popen) + (ice-9 match) + (ice-9 rdelim)) + + (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 + (pk 'wait-for-file file) + (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 "dummy"))) + (setuid (passwd:uid (getpw "dummy"))) + + (let* ((loaded (slurp ,(string-append #$podman + "/bin/podman") + "load" "-i" + ,#$oci-tarball)) + (repository&tag "localhost/guile-guest:latest") + (response1 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" + "--entrypoint" "bin/Guile" + repository&tag + "/aa.scm")) + (response2 (slurp ;default entry point + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(display \"hi!\")'")) + + ;; Check whether (json) is in $GUILE_LOAD_PATH. + (response3 (slurp ;default entry point + environment + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(use-modules (json)) + (display (json-string->scm (scm->json-string \"JSON!\")))'")) + + ;; Check whether /tmp exists. + (response4 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag "-c" + "'(display (stat:perms (lstat \"/tmp\")))'"))) + (call-with-output-file (string-append ,out-dir "/response1") + (lambda (port) + (display (string-join response1 " ") port))) + (call-with-output-file (string-append ,out-dir "/response2") + (lambda (port) + (display (string-join response2 " ") port))) + (call-with-output-file (string-append ,out-dir "/response3") + (lambda (port) + (display (string-join response3 " ") port))) + (call-with-output-file (string-append ,out-dir "/response4") + (lambda (port) + (display (string-join response4 " ") port))))) + (lambda () + (primitive-exit 127)))) + (pid + (cdr (waitpid pid)))) + (wait-for-file (string-append ,out-dir "/response4")) + (append + (slurp "cat" (string-append ,out-dir "/response1")) + (slurp "cat" (string-append ,out-dir "/response2")) + (slurp "cat" (string-append ,out-dir "/response3")) + (map string->number (slurp "cat" (string-append ,out-dir "/response4"))))) + marionette)) + + (test-end)))) + + (gexp->derivation "rootless-podman-test" test)) + +(define (build-tarball&run-rootless-podman-test) + (mlet* %store-monad + ((_ (set-grafting #f)) + (guile (set-guile-for-build (default-guile))) + (guest-script-package -> + (package + (name "guest-script") + (version "0") + (source #f) + (build-system trivial-build-system) + (arguments `(#:guile ,guile-3.0 + #:builder + (let ((out (assoc-ref %outputs "out"))) + (mkdir out) + (call-with-output-file (string-append out "/a.scm") + (lambda (port) + (display "(display \"hello world\n\")" port))) + #t))) + (synopsis "Display hello world using Guile") + (description "This package displays the text \"hello world\" on the +standard output device and then enters a new line.") + (home-page #f) + (license license:public-domain))) + (profile (profile-derivation (packages->manifest + (list guile-3.0 guile-json-3 + guest-script-package)) + #:hooks '() + #:locales? #f)) + (tarball (pack:docker-image + "docker-pack" profile + #:symlinks '(("/bin/Guile" -> "bin/guile") + ("aa.scm" -> "a.scm")) + #:extra-options + '(#:image-tag "guile-guest") + #:entry-point "bin/guile" + #:localstatedir? #t))) + (run-rootless-podman-test tarball))) + +(define %test-rootless-podman + (system-test + (name "rootless-podman") + (description "Test rootless Podman service.") + (value (build-tarball&run-rootless-podman-test)))) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates. 2024-08-20 23:20 [bug#72740] Add rootless-podman-service-type paul via Guix-patches via ` (2 preceding siblings ...) 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 ` Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via ` (2 more replies) 3 siblings, 3 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/system/pam.scm: Export pam-service-name?, pam-entry? and pam-limits-entry?. Change-Id: I609acfcaae85b4969dc385b72b307e470f5a246e --- gnu/system/pam.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gnu/system/pam.scm b/gnu/system/pam.scm index a035a92e25..07b84b04ef 100644 --- a/gnu/system/pam.scm +++ b/gnu/system/pam.scm @@ -34,6 +34,7 @@ (define-module (gnu system pam) #:use-module ((guix utils) #:select (%current-system)) #:use-module (gnu packages linux) #:export (pam-service + pam-service? pam-service-name pam-service-account pam-service-auth @@ -41,11 +42,13 @@ (define-module (gnu system pam) pam-service-session pam-entry + pam-entry? pam-entry-control pam-entry-module pam-entry-arguments pam-limits-entry + pam-limits-entry? pam-limits-entry-domain pam-limits-entry-type pam-limits-entry-item base-commit: 00245fdcd4909d7e6b20fe88f5d089717115adc1 -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v3 2/4] services: pam: Allow extension of pam limits. 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 ` Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi * gnu/services/pam.scm (pam-limits-service-type): Allow extension of pam limits rules from users and services. Change-Id: I93a363d1a2887493d52ef3ae32fc9721f81ddfa8 --- gnu/services/base.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 4b5b103cc3..e4e59da433 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -1680,6 +1680,8 @@ (define pam-limits-service-type (service-type (name 'limits) + (compose concatenate) + (extend append) (extensions (list (service-extension pam-root-service-type (lambda (config) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v3 3/4] services: iptables: Provide a default value. 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 ` Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 UTC (permalink / raw) To: 72740; +Cc: Giacomo Leidi There doesn't seem to be a reason to force users to write (service iptables-service-type (iptables-configuration)) instead of simply (service iptables-service-type) This patch provides a default value for the iptables-service-type. * gnu/services/networking.scm (iptables-service-type): Set default-value. Change-Id: I93b6c544dfb064c7a0a999549dff61007a38f842 --- gnu/services/networking.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/gnu/services/networking.scm b/gnu/services/networking.scm index 12d8934e43..c70fea7813 100644 --- a/gnu/services/networking.scm +++ b/gnu/services/networking.scm @@ -2055,6 +2055,7 @@ (define (iptables-shepherd-service config) (define iptables-service-type (service-type (name 'iptables) + (default-value (iptables-configuration)) (description "Run @command{iptables-restore}, setting up the specified rules.") (extensions -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [bug#72740] [PATCH v3 4/4] services: Add rootless-podman-service-type. 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 ` Giacomo Leidi via Guix-patches via 2 siblings, 0 replies; 15+ messages in thread From: Giacomo Leidi via Guix-patches via @ 2024-08-23 11:40 UTC (permalink / raw) To: 72740 Cc: Giacomo Leidi, Florian Pelz, Ludovic Courtès, Matthew Trzcinski, Maxim Cournoyer * gnu/services/containers.scm: New file; (rootless-podman-configuration): new variable; (rootless-podman-service-subids): new variable; (rootless-podman-service-accounts): new variable; (rootless-podman-service-profile): new variable; (rootless-podman-shepherd-services): new variable; (rootless-podman-service-etc): new variable; (rootless-podman-service-type): new variable. * gnu/local.mk: Test it. * gnu/local.mk: Add them. * doc/guix.texi (Miscellaneous Services): Document it. Change-Id: I041496474c1027da353bd6852f2554a065914d7a --- doc/guix.texi | 104 +++++++++++ gnu/local.mk | 2 + gnu/services/containers.scm | 238 +++++++++++++++++++++++++ gnu/tests/containers.scm | 340 ++++++++++++++++++++++++++++++++++++ 4 files changed, 684 insertions(+) create mode 100644 gnu/services/containers.scm create mode 100644 gnu/tests/containers.scm diff --git a/doc/guix.texi b/doc/guix.texi index 0e1e253b02..eb6a1b2442 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -40852,6 +40852,110 @@ Miscellaneous Services invoke @command{singularity run} and similar commands. @end defvar +@cindex Rootless Podman +@subsubheading Rootless Podman Service + +The @code{(gnu services containers)} module provides the following service. + + +@cindex Rootless Podman, container management tool +@defvar rootless-podman-service-type + +@url{https://www.sylabs.io/singularity/, Singularity} is a container management +tool. In addition to providing a drop-in replacement for Docker, Podman offers +the ability to run containers in rootless mode. This allows regular users to +deploy containers without elevated privileges. + +The @code{rootless-podman-service-type} sets up the Guix System to allow +unprivileged users to run @command{podman} commands: + +@lisp +(use-service-modules containers networking @dots{}) + +(operating-system + ;; @dots{} + (users (cons (user-account + (name "alice") + (comment "Bob's sister") + (group "users") + + ;; Adding the account to the "cgroup" group + ;; makes it possible to run podman commands. + (supplementary-groups '("cgroup" "wheel" + "audio" "video"))) + %base-user-accounts)) + (services + (list + (service iptables-service-type) + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "alice")))) + (subuids + (list (subid-range (name "alice"))))))))) +@end lisp + +The @code{iptables-service-type} is required for Podman to be able to setup its +own networks. Due to the change in user groups and file systems it is +recommended to reboot (or at least logout), before trying to run Podman commands. + +To test your installation you can run: + +@example +$ podman run -it --rm docker.io/alpine cat /etc/*release* +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.20.2 +PRETTY_NAME="Alpine Linux v3.20" +HOME_URL="https://alpinelinux.org/" +BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" +@end example + +@end defvar + +@c %start of fragment + +@deftp {Data Type} rootless-podman-configuration +Available @code{rootless-podman-configuration} fields are: + +@table @asis +@item @code{podman} (default: @code{podman}) (type: package) +The Podman package that will be installed in the system profile. + +@item @code{group-name} (default: @code{"cgroup"}) (type: string) +The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group. + +@item @code{containers-registries} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file. + +@item @code{containers-storage} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file. + +@item @code{containers-policy} (type: lowerable) +A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file. + +@item @code{pam-limits} (type: list-of-pam-limits-entries) +The PAM limits to be set for rootless Podman. + +@item @code{subgids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subgids that will be +available for each configured user. + +@item @code{subuids} (default: @code{()}) (type: list-of-subid-ranges) +A list of subid ranges representing the subuids that will be +available for each configured user. + +@end table + +@end deftp + + +@c %end of fragment + @cindex OCI-backed, Shepherd services @subsubheading OCI backed services diff --git a/gnu/local.mk b/gnu/local.mk index 3b0a3858f7..a543f1ddc9 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -708,6 +708,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/cgit.scm \ %D%/services/ci.scm \ %D%/services/configuration.scm \ + %D%/services/containers.scm \ %D%/services/cuirass.scm \ %D%/services/cups.scm \ %D%/services/databases.scm \ @@ -813,6 +814,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/base.scm \ %D%/tests/cachefilesd.scm \ %D%/tests/ci.scm \ + %D%/tests/containers.scm \ %D%/tests/cups.scm \ %D%/tests/databases.scm \ %D%/tests/desktop.scm \ diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm new file mode 100644 index 0000000000..03f0649c0d --- /dev/null +++ b/gnu/services/containers.scm @@ -0,0 +1,238 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu services containers) + #:use-module (gnu packages containers) + #:use-module (gnu packages file-systems) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu system accounts) + #:use-module (gnu system shadow) + #:use-module (gnu system pam) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (srfi srfi-1) + #:export (rootless-podman-configuration + rootless-podman-configuration? + rootless-podman-configuration-fields + rootless-podman-configuration-podman + rootless-podman-configuration-group-name + rootless-podman-configuration-containers-registries + rootless-podman-configuration-containers-storage + rootless-podman-configuration-containers-policy + rootless-podman-configuration-pam-limits + rootless-podman-configuration-subgids + rootless-podman-configuration-subuids + + rootless-podman-service-subids + rootless-podman-service-accounts + rootless-podman-service-profile + rootless-podman-shepherd-services + rootless-podman-service-etc + + rootless-podman-service-type)) + +(define (gexp-or-string? value) + (or (gexp? value) + (string? value))) + +(define (lowerable? value) + (or (file-like? value) + (gexp-or-string? value))) + +(define list-of-pam-limits-entries? + (list-of pam-limits-entry?)) + +(define list-of-subid-ranges? + (list-of subid-range?)) + +(define-configuration/no-serialization rootless-podman-configuration + (podman + (package podman) + "The Podman package that will be installed in the system profile.") + (group-name + (string "cgroup") + "The name of the group that will own /sys/fs/cgroup resources. Users that +want to use rootless Podman have to be in this group.") + (containers-registries + (lowerable + (plain-file "registries.conf" + (string-append "unqualified-search-registries = ['docker.io','" + "registry.fedora.org','registry.opensuse.org']"))) + "A string or a gexp evaluating to the path of Podman's +@code{containers/registries.conf} configuration file.") + (containers-storage + (lowerable + (plain-file "storage.conf" + "[storage] +driver = \"overlay\"")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/storage.conf} configuration file.") + (containers-policy + (lowerable + (plain-file "policy.json" + "{\"default\": [{\"type\": \"insecureAcceptAnything\"}]}")) + "A string or a gexp evaluating to the path of Podman's +@code{containers/policy.json} configuration file.") + (pam-limits + (list-of-pam-limits-entries + (list (pam-limits-entry "*" 'both 'nofile 100000))) + "The PAM limits to be set for rootless Podman.") + (subgids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subgids that will be +available for each configured user.") + (subuids + (list-of-subid-ranges '()) + "A list of subid ranges representing the subuids that will be +available for each configured user.")) + +(define rootless-podman-service-profile + (lambda (config) + (list + (rootless-podman-configuration-podman config)))) + +(define rootless-podman-service-etc + (lambda (config) + (list `("containers/registries.conf" + ,(rootless-podman-configuration-containers-registries config)) + `("containers/storage.conf" + ,(rootless-podman-configuration-containers-storage config)) + `("containers/policy.json" + ,(rootless-podman-configuration-containers-policy config))))) + +(define rootless-podman-service-subids + (lambda (config) + (subids-extension + (subgids (rootless-podman-configuration-subgids config)) + (subuids (rootless-podman-configuration-subuids config))))) + +(define rootless-podman-service-accounts + (lambda (config) + (list (user-group (name (rootless-podman-configuration-group-name config)) + (system? #t))))) + +(define (cgroups-fs-owner-entrypoint config) + (define group + (rootless-podman-configuration-group-name config)) + (program-file "cgroups2-fs-owner-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting /sys/fs/cgroup " + "group ownership to " #$group " && chown -v " + "root:" #$group " /sys/fs/cgroup && " + "chmod -v 775 /sys/fs/cgroup && chown -v " + "root:" #$group " /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads} && " + "chmod -v 664 /sys/fs/cgroup/cgroup." + "{procs,subtree_control,threads}")))) + +(define (rootless-podman-cgroups-fs-owner-service config) + (shepherd-service (provision '(cgroups2-fs-owner)) + (requirement + '(dbus-system + elogind + file-system-/sys/fs/cgroup + networking + udev + cgroups2-limits)) + (one-shot? #t) + (documentation + "Set ownership of /sys/fs/cgroup to the configured group.") + (start + #~(make-forkexec-constructor + (list + #$(cgroups-fs-owner-entrypoint config)))) + (stop + #~(make-kill-destructor)))) + +(define cgroups-limits-entrypoint + (program-file "cgroups2-limits-entrypoint" + #~(system* + "bash" "-c" + (string-append "echo Setting cgroups v2 limits && " + "echo +cpu +cpuset +memory +pids" + " >> /sys/fs/cgroup/cgroup.subtree_control")))) + +(define (rootless-podman-cgroups-limits-service config) + (shepherd-service (provision '(cgroups2-limits)) + (requirement + '(dbus-system + elogind + networking + udev + file-system-/sys/fs/cgroup + rootless-podman-shared-root-fs)) + (one-shot? #t) + (documentation + "Allow setting cgroups limits: cpu, cpuset, memory and +pids.") + (start + #~(make-forkexec-constructor + (list + #$cgroups-limits-entrypoint))) + (stop + #~(make-kill-destructor)))) + +(define rootless-podman-shared-root-fs-entrypoint + (program-file "rootless-podman-shared-root-fs-entrypoint" + #~(system* + "mount" "--make-shared" "/"))) + +(define (rootless-podman-shared-root-fs-service config) + (shepherd-service (provision '(rootless-podman-shared-root-fs)) + (requirement + '(user-processes)) + (one-shot? #t) + (documentation + "Buildah/Podman running as rootless expects the bind mount +to be shared. This service sets it so.") + (start + #~(make-forkexec-constructor + (list + #$rootless-podman-shared-root-fs-entrypoint))) + (stop + #~(make-kill-destructor)))) + +(define (rootless-podman-shepherd-services config) + (list + (rootless-podman-shared-root-fs-service config) + (rootless-podman-cgroups-limits-service config) + (rootless-podman-cgroups-fs-owner-service config))) + +(define rootless-podman-service-type + (service-type (name 'rootless-podman) + (extensions + (list + (service-extension subids-service-type + rootless-podman-service-subids) + (service-extension account-service-type + rootless-podman-service-accounts) + (service-extension profile-service-type + rootless-podman-service-profile) + (service-extension shepherd-root-service-type + rootless-podman-shepherd-services) + (service-extension pam-limits-service-type + rootless-podman-configuration-pam-limits) + (service-extension etc-service-type + rootless-podman-service-etc))) + (default-value (rootless-podman-configuration)) + (description + "This service configures rootless @code{podman} on the Guix System."))) diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm new file mode 100644 index 0000000000..ba2fb22df6 --- /dev/null +++ b/gnu/tests/containers.scm @@ -0,0 +1,340 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu tests containers) + #:use-module (gnu) + #:use-module (gnu tests) + #:use-module (guix build-system trivial) + #:use-module (gnu packages bash) + #:use-module (gnu packages containers) + #:use-module (gnu packages guile) + #:use-module (gnu packages guile-xyz) + #:use-module (gnu services) + #:use-module (gnu services containers) + #:use-module (gnu services desktop) + #:use-module (gnu services dbus) + #:use-module (gnu services networking) + #:use-module (gnu system) + #:use-module (gnu system accounts) + #:use-module (gnu system vm) + #:use-module (guix gexp) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix monads) + #:use-module (guix packages) + #:use-module (guix profiles) + #:use-module ((guix scripts pack) #:prefix pack:) + #:use-module (guix store) + #:export (%test-rootless-podman)) + +\f +(define %rootless-podman-os + (simple-operating-system + (service rootless-podman-service-type + (rootless-podman-configuration + (subgids + (list (subid-range (name "dummy")))) + (subuids + (list (subid-range (name "dummy")))))) + + (service dhcp-client-service-type) + (service dbus-root-service-type) + (service polkit-service-type) + (service elogind-service-type) + + (simple-service 'accounts + account-service-type + (list (user-account + (name "dummy") + (group "users") + (supplementary-groups '("wheel" "netdev" "cgroup" + "audio" "video"))))))) + +(define (run-rootless-podman-test oci-tarball) + + (define os + (marionette-operating-system + (operating-system-with-gc-roots + %rootless-podman-os + (list oci-tarball)) + #: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) + (gnu services herd)) + #~(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)) + (define out-dir "/tmp") + + (test-runner-current (system-test-runner #$output)) + (test-begin "rootless-podman") + + (test-assert "service started" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service 'cgroups2-fs-owner) + (#f #f) + ;; herd returns (running #f), likely because of one shot, + ;; so consider any non-error a success. + (('service response-parts ...) #t))) + marionette)) + + (test-equal "/sys/fs/cgroup/cgroup.subtree_control content is sound" + (list "cpu" "cpuset" "memory" "pids") + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (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") + "/sys/fs/cgroup/cgroup.subtree_control"))) + (sort-list (string-split (first response1) #\space) string<?))) + marionette)) + + (test-equal "/sys/fs/cgroup has correct permissions" + '("cgroup" "cgroup") + (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* ((bash + ,(string-append #$bash "/bin/bash")) + (response1 + (slurp bash "-c" + (string-append "ls -la /sys/fs/cgroup | " + "grep -E ' \\./?$' | awk '{ print $4 }'"))) + (response2 (slurp bash "-c" + (string-append "ls -l /sys/fs/cgroup/cgroup" + ".{procs,subtree_control,threads} | " + "awk '{ print $4 }' | sort -u")))) + (list (string-join response1 "\n") (string-join response2 "\n")))) + marionette)) + + (test-equal "Load oci image and run it (unprivileged)" + '("hello world" "hi!" "JSON!" #o1777) + (marionette-eval + `(begin + (use-modules (srfi srfi-1) + (ice-9 popen) + (ice-9 match) + (ice-9 rdelim)) + + (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 + (pk 'wait-for-file file) + (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 "dummy"))) + (setuid (passwd:uid (getpw "dummy"))) + + (let* ((loaded (slurp ,(string-append #$podman + "/bin/podman") + "load" "-i" + ,#$oci-tarball)) + (repository&tag "localhost/guile-guest:latest") + (response1 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" + "--entrypoint" "bin/Guile" + repository&tag + "/aa.scm")) + (response2 (slurp ;default entry point + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(display \"hi!\")'")) + + ;; Check whether (json) is in $GUILE_LOAD_PATH. + (response3 (slurp ;default entry point + environment + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag + "-c" "'(use-modules (json)) + (display (json-string->scm (scm->json-string \"JSON!\")))'")) + + ;; Check whether /tmp exists. + (response4 (slurp + ,(string-append #$podman "/bin/podman") + "run" "--pull" "never" repository&tag "-c" + "'(display (stat:perms (lstat \"/tmp\")))'"))) + (call-with-output-file (string-append ,out-dir "/response1") + (lambda (port) + (display (string-join response1 " ") port))) + (call-with-output-file (string-append ,out-dir "/response2") + (lambda (port) + (display (string-join response2 " ") port))) + (call-with-output-file (string-append ,out-dir "/response3") + (lambda (port) + (display (string-join response3 " ") port))) + (call-with-output-file (string-append ,out-dir "/response4") + (lambda (port) + (display (string-join response4 " ") port))))) + (lambda () + (primitive-exit 127)))) + (pid + (cdr (waitpid pid)))) + (wait-for-file (string-append ,out-dir "/response4")) + (append + (slurp "cat" (string-append ,out-dir "/response1")) + (slurp "cat" (string-append ,out-dir "/response2")) + (slurp "cat" (string-append ,out-dir "/response3")) + (map string->number (slurp "cat" (string-append ,out-dir "/response4"))))) + marionette)) + + (test-end)))) + + (gexp->derivation "rootless-podman-test" test)) + +(define (build-tarball&run-rootless-podman-test) + (mlet* %store-monad + ((_ (set-grafting #f)) + (guile (set-guile-for-build (default-guile))) + (guest-script-package -> + (package + (name "guest-script") + (version "0") + (source #f) + (build-system trivial-build-system) + (arguments `(#:guile ,guile-3.0 + #:builder + (let ((out (assoc-ref %outputs "out"))) + (mkdir out) + (call-with-output-file (string-append out "/a.scm") + (lambda (port) + (display "(display \"hello world\n\")" port))) + #t))) + (synopsis "Display hello world using Guile") + (description "This package displays the text \"hello world\" on the +standard output device and then enters a new line.") + (home-page #f) + (license license:public-domain))) + (profile (profile-derivation (packages->manifest + (list guile-3.0 guile-json-3 + guest-script-package)) + #:hooks '() + #:locales? #f)) + (tarball (pack:docker-image + "docker-pack" profile + #:symlinks '(("/bin/Guile" -> "bin/guile") + ("aa.scm" -> "a.scm")) + #:extra-options + '(#:image-tag "guile-guest") + #:entry-point "bin/guile" + #:localstatedir? #t))) + (run-rootless-podman-test tarball))) + +(define %test-rootless-podman + (system-test + (name "rootless-podman") + (description "Test rootless Podman service.") + (value (build-tarball&run-rootless-podman-test)))) -- 2.45.2 ^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2024-08-23 11:42 UTC | newest] Thread overview: 15+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-08-20 23:20 [bug#72740] Add rootless-podman-service-type paul via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-20 23:21 ` [bug#72740] [PATCH 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2024-08-21 8:18 ` [bug#72740] " paul via Guix-patches via 2024-08-23 11:39 ` paul via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-21 8:19 ` [bug#72740] [PATCH v2 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 1/4] system: pam: Export pam records predicates Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 2/4] services: pam: Allow extension of pam limits Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 3/4] services: iptables: Provide a default value Giacomo Leidi via Guix-patches via 2024-08-23 11:40 ` [bug#72740] [PATCH v3 4/4] services: Add rootless-podman-service-type Giacomo Leidi via Guix-patches via
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/guix.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).