diff --git a/.circleci/config.yml b/.circleci/config.yml index cc6958d7e8..7b7b1e7ad5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,6 +99,7 @@ commands: libfuse-dev \ libglib2.0-dev \ libseccomp-dev \ + libsubid-dev \ libtool \ pkg-config \ squashfs-tools \ diff --git a/.golangci.yml b/.golangci.yml index ab22c0d646..6324e898b4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,6 +5,7 @@ run: - apparmor - e2e_test - fakeroot_engine + - libsubid - seccomp - selinux - singularity_engine diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f2031dcd7..46d2982a3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ "--fast" ], "go.buildFlags": [ - "-tags=apparmor,fakeroot_engine,seccomp,selinux,singularity_engine,sylog" + "-tags=apparmor,fakeroot_engine,libsubid,seccomp,selinux,singularity_engine,sylog" ], "go.testTags": "apparmor,fakeroot_engine,seccomp,selinux,singularity_engine,sylog,e2e_test,integration_test" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec3cec8fd..107e73a838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ - Use correct username (not user's name) when computing `singularity oci` conmon / singularity state dir. +### New Features & Functionality + +- Add support for libsubid. Sub[ug]id mappings will be retrieved from e.g. LDAP + via libsubid and sssd if Singularity is built with libsubid support (default + when libsubid headers are available). + ## 4.2.2 \[2024-12-20\] ### Bug Fixes diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e6ac2ad04a..a1be19fd09 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -22,6 +22,7 @@ The following have contributed code and/or documentation to this repository. - Alexander Grund - Amanda Duffy - Ana Guerrero Lopez +- Andrew Bruno - Ángel Bejarano - Apuã Paquola - Aron Öfjörð Jóhannesson diff --git a/INSTALL.md b/INSTALL.md index f8e38eab33..4ff9f1b4ae 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -36,6 +36,12 @@ sudo apt-get install -y \ zlib1g-dev ``` +To include support for libsubid on Ubuntu 24.04 and above: + +```sh +sudo apt-get install -y libsubid-dev +``` + ### RHEL / Alma Linux / Rocky Linux 8+ and Fedora ```sh @@ -59,6 +65,16 @@ sudo yum install -y \ zlib-devel ``` +To include support for libsubid: + +```sh +# EL +sudo dnf --enablerepo=devel install shadow-utils-subid-devel + +# Fedora +sudo dnf install shadow-utils-subid-devel +``` + ### SLES / openSUSE Leap ```sh diff --git a/LICENSE_THIRD_PARTY.md b/LICENSE_THIRD_PARTY.md index 02ab4413f4..139ceb2c81 100644 --- a/LICENSE_THIRD_PARTY.md +++ b/LICENSE_THIRD_PARTY.md @@ -292,6 +292,14 @@ The source files: Contain code from the podman project, under the Apache License, Version 2.0. +## github.com/containers/storage + +The source file: + +* `internal/pkg/fakeroot/idtools_supported.go` + +Contains code from the podman project, under the Apache License, Version 2.0. + ## github.com/containers/conmon The source files: diff --git a/dist/rpm/singularity-ce.spec.in b/dist/rpm/singularity-ce.spec.in index a54088c348..2b3ab8a6b7 100644 --- a/dist/rpm/singularity-ce.spec.in +++ b/dist/rpm/singularity-ce.spec.in @@ -88,6 +88,12 @@ Requires: fuse # FUSE 3 for squashfuse Requires: fuse3 %endif +# Libsubid on EL / OpenSuseTumbleweed +%if "%{_target_vendor}" == "suse" && 0%{?suse_version} > 1600 +BuildRequires: libsubid-devel +%else +BuildRequires: shadow-utils-subid-devel +%endif Provides: %{name}-runtime diff --git a/internal/pkg/fakeroot/fakeroot.go b/internal/pkg/fakeroot/fakeroot.go index af26e139ab..b5d7156fbd 100644 --- a/internal/pkg/fakeroot/fakeroot.go +++ b/internal/pkg/fakeroot/fakeroot.go @@ -1,4 +1,6 @@ -// Copyright (c) 2019-2021, Sylabs Inc. All rights reserved. +// Copyright (c) 2019-2025, Sylabs Inc. All rights reserved. +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. @@ -300,19 +302,24 @@ func (c *Config) GetUserEntry(username string) (*Entry, error) { if err != nil { return nil, fmt.Errorf("could not retrieve user information for %s: %s", username, err) } - for _, entry := range c.entries { + + entries, err := c.getMappingEntries(u) + if err != nil { + return nil, fmt.Errorf("failed to look up mapping entries for user %s: %w", username, err) + } + + for _, entry := range entries { if entry.invalid { continue } - if entry.UID == u.UID { - if entry.Count == validRangeCount { - return entry, nil - } else if entry.Count > validRangeCount { - largeRangeEntries = append(largeRangeEntries, entry) - continue - } - entryCount++ + + if entry.Count == validRangeCount { + return entry, nil + } else if entry.Count > validRangeCount { + largeRangeEntries = append(largeRangeEntries, entry) + continue } + entryCount++ } var largestEntry *Entry diff --git a/internal/pkg/fakeroot/idtools_supported.go b/internal/pkg/fakeroot/idtools_supported.go new file mode 100644 index 0000000000..f321cda221 --- /dev/null +++ b/internal/pkg/fakeroot/idtools_supported.go @@ -0,0 +1,123 @@ +//go:build linux && cgo && libsubid +// +build linux,cgo,libsubid + +// Portions of this code was adopted from github.com/containers/storage +// Copyright (C) The Linux Foundation and its contributors. +// Original source released under: Apache 2.0 license +// See: https://github.com/containers/storage/blob/main/pkg/idtools/idtools_supported.go +// +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// Copyright (c) 2019-2025, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE.md file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package fakeroot + +import ( + "errors" + "fmt" + "strings" + "unsafe" + + "github.com/sylabs/singularity/v4/internal/pkg/util/user" +) + +/* +#cgo LDFLAGS: -l subid + +#include +#include +#include + +struct subid_range singularity_get_range(struct subid_range *ranges, int i) +{ + return ranges[i]; +} + +#if !defined(SUBID_ABI_MAJOR) || (SUBID_ABI_MAJOR < 4) +# define subid_get_uid_ranges get_subuid_ranges +# define subid_get_gid_ranges get_subgid_ranges +#endif +*/ +import "C" + +func readSubid(user *user.User, isUser bool) ([]*Entry, error) { + ret := make([]*Entry, 0) + uidstr := fmt.Sprintf("%d", user.UID) + + if user.Name == "ALL" { + return nil, errors.New("username ALL not supported") + } + + cUsername := C.CString(user.Name) + defer C.free(unsafe.Pointer(cUsername)) + + cuidstr := C.CString(uidstr) + defer C.free(unsafe.Pointer(cuidstr)) + + var nRanges C.int + var cRanges *C.struct_subid_range + if isUser { + nRanges = C.subid_get_uid_ranges(cUsername, &cRanges) + if nRanges <= 0 { + nRanges = C.subid_get_uid_ranges(cuidstr, &cRanges) + } + } else { + nRanges = C.subid_get_gid_ranges(cUsername, &cRanges) + if nRanges <= 0 { + nRanges = C.subid_get_gid_ranges(cuidstr, &cRanges) + } + } + if nRanges < 0 { + return nil, errors.New("cannot read subids") + } + defer C.free(unsafe.Pointer(cRanges)) + + for i := 0; i < int(nRanges); i++ { + r := C.singularity_get_range(cRanges, C.int(i)) + line := fmt.Sprintf("%d:%d:%d", user.UID, r.start, r.count) + ret = append( + ret, + &Entry{ + UID: user.UID, + Start: uint32(r.start), + Count: uint32(r.count), + disabled: false, + line: line, + }) + } + return ret, nil +} + +func readSubuid(user *user.User) ([]*Entry, error) { + return readSubid(user, true) +} + +func readSubgid(user *user.User) ([]*Entry, error) { + return readSubid(user, false) +} + +func (c *Config) getMappingEntries(user *user.User) ([]*Entry, error) { + entries := make([]*Entry, 0) + for _, entry := range c.entries { + if entry.UID == user.UID { + entries = append(entries, entry) + } + } + + var subidEntries []*Entry + var err error + if strings.Contains(c.file.Name(), "gid") { + subidEntries, err = readSubgid(user) + } else { + subidEntries, err = readSubuid(user) + } + + if err != nil { + return nil, err + } + + return append(entries, subidEntries...), nil +} diff --git a/internal/pkg/fakeroot/idtools_unsupported.go b/internal/pkg/fakeroot/idtools_unsupported.go new file mode 100644 index 0000000000..afec0921d2 --- /dev/null +++ b/internal/pkg/fakeroot/idtools_unsupported.go @@ -0,0 +1,26 @@ +//go:build !linux || !libsubid || !cgo +// +build !linux !libsubid !cgo + +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// Copyright (c) 2019-2025, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE.md file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package fakeroot + +import ( + "github.com/sylabs/singularity/v4/internal/pkg/util/user" +) + +func (c *Config) getMappingEntries(user *user.User) ([]*Entry, error) { + entries := make([]*Entry, 0) + for _, entry := range c.entries { + if entry.UID == user.UID { + entries = append(entries, entry) + } + } + + return entries, nil +} diff --git a/mconfig b/mconfig index 0983ec742e..0b9ec962bd 100755 --- a/mconfig +++ b/mconfig @@ -1,5 +1,5 @@ #!/bin/sh - -# Copyright (c) 2019-2024, Sylabs Inc. All rights reserved. +# Copyright (c) 2019-2025, Sylabs Inc. All rights reserved. # Copyright (c) 2015-2018, Yannick Cote . All rights reserved. # Copyright (c) Contributors to the Apptainer project, established as # Apptainer a Series of LF Projects LLC. @@ -62,6 +62,7 @@ with_conmon=1 with_squashfuse=1 with_suid=1 with_seccomp_check=1 +with_libsubid=1 builddir= prefix= @@ -117,6 +118,7 @@ usage_args () { echo echo " Singularity options:" echo " --without-suid do not install SUID binary (linux only)" + echo " --without-libsubid do not compile libsubid support even if available (linux only)" echo " --without-network do not compile/install network plugins (linux only)" echo " --without-seccomp do not compile/install seccomp support (linux only)" echo @@ -376,6 +378,8 @@ while [ $# -ne 0 ]; do verbose=1; shift;; --without-suid) with_suid=0; shift;; + --without-libsubid) + with_libsubid=0; shift;; --without-network) with_network=0; shift;; --without-seccomp) @@ -783,6 +787,11 @@ if [ "$appsec" = "1" ]; then cat $makeit_fragsdir/go_appsec_opts.mk >> $makeit_makefile fi +if [ "$libsubid" = "1" ]; then + drawline $makeit_fragsdir/go_libsubid_opts.mk + cat $makeit_fragsdir/go_libsubid_opts.mk >> $makeit_makefile +fi + if [ "$build_runtime" = "1" ]; then drawline $makeit_fragsdir/go_runtime_opts.mk cat $makeit_fragsdir/go_runtime_opts.mk >> $makeit_makefile diff --git a/mlocal/checks/project-post.chk b/mlocal/checks/project-post.chk index 9cd0666789..d83e3ab42d 100644 --- a/mlocal/checks/project-post.chk +++ b/mlocal/checks/project-post.chk @@ -518,3 +518,40 @@ config_add_def UNSQUASHFS_PATH \"${unsquashfs_path}\" echo config_add_footer + +######################## +# libsubid +######################## +if [ "$with_libsubid" = "1" ];then + printf " checking: libsubid support... " + testprog=$makeit_testprogdir/test_libsubid + cat > ${testprog}.c << "EOF" + #include + #include + #include + + const char *Prog = "test"; + FILE *shadow_logfd = NULL; + + int main() { + struct subid_range *ranges = NULL; + #if SUBID_ABI_MAJOR >= 4 + subid_get_uid_ranges("root", &ranges); + #else + get_subuid_ranges("root", &ranges); + #endif + free(ranges); + return 0; + } +EOF + if ! $tgtcc -x c -o $testprog ${testprog}.c -l subid >/dev/null 2>&1; then + echo "no" + else + if ! $testprog; then + echo "no" + else + echo "yes" + libsubid=1 + fi + fi +fi diff --git a/mlocal/frags/go_libsubid_opts.mk b/mlocal/frags/go_libsubid_opts.mk new file mode 100644 index 0000000000..1c8c02f971 --- /dev/null +++ b/mlocal/frags/go_libsubid_opts.mk @@ -0,0 +1,2 @@ +GO_TAGS += libsubid +GO_TAGS_SUID += libsubid