diff --git a/.github/workflows/ubuntu-tests.yaml b/.github/workflows/ubuntu-tests.yaml index 438f6fa5f..b739051ed 100644 --- a/.github/workflows/ubuntu-tests.yaml +++ b/.github/workflows/ubuntu-tests.yaml @@ -1,5 +1,5 @@ # -# Copyright © 2023 – 2024 Red Hat, Inc. +# Copyright © 2023 – 2025 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -55,6 +55,9 @@ jobs: systemd \ udisks2 + - name: Ensure that 'p11-kit server' is absent + run: sudo rm /usr/libexec/p11-kit/p11-kit-server + - name: Set up PATH for Go 1.21 run: | echo "PATH=/usr/lib/go-1.21/bin:$PATH" >> "$GITHUB_ENV" @@ -131,7 +134,7 @@ jobs: working-directory: containers/toolbox/src - name: Set up build directory - run: meson setup --fatal-meson-warnings builddir + run: meson setup builddir working-directory: containers/toolbox - name: Build diff --git a/meson.build b/meson.build index 6cb96917f..3f85363a1 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,27 @@ bats = find_program('bats', required: false) codespell = find_program('codespell', required: false) htpasswd = find_program('htpasswd', required: false) openssl = find_program('openssl', required: false) + +p11kit_server_works = false +p11kit = find_program('p11-kit', required: false) +if p11kit.found() + res = run_command(p11kit, 'server', check: false) + if res.returncode() == 0 + error('Command \'p11-kit server\' was supposed to fail') + endif + + res_stdout = res.stdout() + if res_stdout.contains('--name') and res_stdout.contains('--provider') + p11kit_server_works = true + else + warning('Command \'p11-kit server\' doesn\'t work') + endif +endif + +if not p11kit_server_works + warning('Containers won\'t have access to the CA certificates from the host') +endif + podman = find_program('podman', required: false) shellcheck = find_program('shellcheck', required: false) skopeo = find_program('skopeo', required: false) diff --git a/playbooks/dependencies-centos-9-stream.yaml b/playbooks/dependencies-centos-9-stream.yaml index 92c38e73c..2ca6ddd5c 100644 --- a/playbooks/dependencies-centos-9-stream.yaml +++ b/playbooks/dependencies-centos-9-stream.yaml @@ -38,6 +38,13 @@ - codespell - fish +- name: Ensure that 'p11-kit server' is absent + become: yes + package: + name: + - p11-kit-server + state: absent + - name: Download Go modules command: go mod download -x environment: diff --git a/playbooks/dependencies-fedora-restricted.yaml b/playbooks/dependencies-fedora-restricted.yaml index 13973b6eb..129668a74 100644 --- a/playbooks/dependencies-fedora-restricted.yaml +++ b/playbooks/dependencies-fedora-restricted.yaml @@ -1,5 +1,5 @@ # -# Copyright © 2023 – 2024 Red Hat, Inc. +# Copyright © 2023 – 2025 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,6 +47,15 @@ update_cache: "{{ true if zuul.attempts > 1 else false }}" use: "{{ 'dnf' if zuul.attempts > 1 else 'auto' }}" +- name: Ensure that 'p11-kit server' is absent + become: yes + package: + name: + - p11-kit-server + state: absent + update_cache: "{{ true if zuul.attempts > 1 else false }}" + use: "{{ 'dnf' if zuul.attempts > 1 else 'auto' }}" + - name: Ensure that podman(1) is absent become: yes package: diff --git a/playbooks/dependencies-fedora.yaml b/playbooks/dependencies-fedora.yaml index 2ef9ea201..0f10f5a4e 100644 --- a/playbooks/dependencies-fedora.yaml +++ b/playbooks/dependencies-fedora.yaml @@ -1,5 +1,5 @@ # -# Copyright © 2022 – 2024 Red Hat, Inc. +# Copyright © 2022 – 2025 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -39,6 +39,15 @@ - udisks2 use: "{{ 'dnf' if zuul.attempts > 1 else 'auto' }}" +- name: Ensure that 'p11-kit server' is absent + become: yes + package: + name: + - p11-kit-server + state: absent + update_cache: "{{ true if zuul.attempts > 1 else false }}" + use: "{{ 'dnf' if zuul.attempts > 1 else 'auto' }}" + - name: Download Go modules command: go mod download -x environment: diff --git a/playbooks/setup-env-migration-path-for-coreos-toolbox.yaml b/playbooks/setup-env-migration-path-for-coreos-toolbox.yaml index 571c4e6c0..6fe33129e 100644 --- a/playbooks/setup-env-migration-path-for-coreos-toolbox.yaml +++ b/playbooks/setup-env-migration-path-for-coreos-toolbox.yaml @@ -20,6 +20,6 @@ - include_tasks: dependencies-centos-9-stream.yaml - name: Set up build directory - command: meson -Dmigration_path_for_coreos_toolbox=true --fatal-meson-warnings builddir + command: meson -Dmigration_path_for_coreos_toolbox=true builddir args: chdir: '{{ zuul.project.src_dir }}' diff --git a/playbooks/setup-env-restricted.yaml b/playbooks/setup-env-restricted.yaml index 11e751fa4..123177b3b 100644 --- a/playbooks/setup-env-restricted.yaml +++ b/playbooks/setup-env-restricted.yaml @@ -20,6 +20,6 @@ - include_tasks: dependencies-fedora-restricted.yaml - name: Set up build directory - command: meson setup --fatal-meson-warnings builddir + command: meson setup builddir args: chdir: '{{ zuul.project.src_dir }}' diff --git a/playbooks/setup-env.yaml b/playbooks/setup-env.yaml index bebd09e04..dc08f13d7 100644 --- a/playbooks/setup-env.yaml +++ b/playbooks/setup-env.yaml @@ -20,6 +20,6 @@ - include_tasks: dependencies-fedora.yaml - name: Set up build directory - command: meson setup --fatal-meson-warnings builddir + command: meson setup builddir args: chdir: '{{ zuul.project.src_dir }}' diff --git a/src/cmd/initContainer.go b/src/cmd/initContainer.go index ac9534b52..a5c868f04 100644 --- a/src/cmd/initContainer.go +++ b/src/cmd/initContainer.go @@ -301,6 +301,10 @@ func initContainer(cmd *cobra.Command, args []string) error { return err } + if err := configurePKCS11(targetUser); err != nil { + return err + } + if err := configureRPM(); err != nil { return err } @@ -569,6 +573,57 @@ func configureKerberos() error { return nil } +func configurePKCS11(targetUser *user.User) error { + const logPrefix = "Configuring PKCS #11 to read from the host" + logrus.Debugf("%s", logPrefix) + + if path := "/etc/pkcs11/modules"; !utils.PathExists(path) { + logrus.Debugf("%s: directory %s not found", logPrefix, path) + logrus.Debugf("%s: skipping", logPrefix) + return nil + } + + if ok, err := utils.IsP11KitClientPresent(); err != nil { + logrus.Debugf("%s: %s", logPrefix, err) + + if !ok { + logrus.Debugf("%s: p11-kit-client.so not found", logPrefix) + logrus.Debugf("%s: skipping", logPrefix) + return nil + } + } else { + if !ok { + logrus.Debugf("%s: p11-kit-client.so not found", logPrefix) + logrus.Debugf("%s: skipping", logPrefix) + return nil + } + } + + if path, err := utils.GetP11KitServerSocket(targetUser); err != nil { + return err + } else if !utils.PathExists(path) { + logrus.Debugf("%s: socket %s not found", logPrefix, path) + logrus.Debugf("%s: skipping", logPrefix) + return nil + } + + var builder strings.Builder + builder.WriteString("# Written by Toolbx\n") + builder.WriteString("# https://containertoolbx.org/\n") + builder.WriteString("\n") + builder.WriteString("module: p11-kit-client.so\n") + + pkcs11ConfigString := builder.String() + pkcs11ConfigBytes := []byte(pkcs11ConfigString) + if err := renameio.WriteFile("/etc/pkcs11/modules/p11-kit-trust.module", + pkcs11ConfigBytes, + 0644); err != nil { + return fmt.Errorf("failed to configure PKCS #11 to read from the host: %w", err) + } + + return nil +} + func configureRPM() error { if !utils.PathExists("/usr/lib/rpm/macros.d") { return nil diff --git a/src/cmd/root.go b/src/cmd/root.go index 7de7f1872..cfc8f42e3 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -263,21 +263,28 @@ func migrate(cmd *cobra.Command, args []string) error { migrateLock := toolboxRuntimeDirectory + "/migrate.lock" - migrateLockFile, err := os.Create(migrateLock) + migrateLockFile, err := utils.Flock(migrateLock, syscall.LOCK_EX) if err != nil { - logrus.Debugf("Migrating to newer Podman: failed to create migration lock file %s: %s", migrateLock, err) - return errors.New("failed to create migration lock file") + logrus.Debugf("Migrating to newer Podman: %s", err) + + var errFlock *utils.FlockError + + if errors.As(err, &errFlock) { + if errors.Is(err, utils.ErrFlockAcquire) { + err = utils.ErrFlockAcquire + } else if errors.Is(err, utils.ErrFlockCreate) { + err = utils.ErrFlockCreate + } else { + panicMsg := fmt.Sprintf("unexpected %T: %s", err, err) + panic(panicMsg) + } + } + + return err } defer migrateLockFile.Close() - migrateLockFD := migrateLockFile.Fd() - migrateLockFDInt := int(migrateLockFD) - if err := syscall.Flock(migrateLockFDInt, syscall.LOCK_EX); err != nil { - logrus.Debugf("Migrating to newer Podman: failed to acquire migration lock on %s: %s", migrateLock, err) - return errors.New("failed to acquire migration lock") - } - stampBytes, err := ioutil.ReadFile(stampPath) if err != nil { if !errors.Is(err, os.ErrNotExist) { diff --git a/src/cmd/run.go b/src/cmd/run.go index 639e4605e..7094c3a4e 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" "github.com/containers/toolbox/pkg/nvidia" @@ -283,6 +284,11 @@ func runCommand(container string, cdiEnviron = append(cdiEnviron, cdiSpecForNvidia.ContainerEdits.Env...) } + p11KitServerEnviron, err := startP11KitServer() + if err != nil { + return err + } + startContainerTimestamp := time.Unix(-1, 0) if entryPointPID <= 0 { @@ -335,10 +341,11 @@ func runCommand(container string, logrus.Debugf("Container %s is initialized", container) + environ := append(cdiEnviron, p11KitServerEnviron...) if err := runCommandWithFallbacks(container, preserveFDs, command, - cdiEnviron, + environ, emitEscapeSequence, fallbackToBash); err != nil { return err @@ -1033,6 +1040,68 @@ func startContainer(container string) error { return nil } +func startP11KitServer() ([]string, error) { + serverSocket, err := utils.GetP11KitServerSocket(currentUser) + if err != nil { + return nil, err + } + + const logPrefix = "Starting 'p11-kit server'" + logrus.Debugf("%s with socket %s", logPrefix, serverSocket) + + serverSocketLock, err := utils.GetP11KitServerSocketLock(currentUser) + if err != nil { + return nil, err + } + + serverSocketLockFile, err := utils.Flock(serverSocketLock, syscall.LOCK_EX) + if err != nil { + logrus.Debugf("%s: %s", logPrefix, err) + + var errFlock *utils.FlockError + + if errors.As(err, &errFlock) { + if errors.Is(err, utils.ErrFlockAcquire) { + err = utils.ErrFlockAcquire + } else if errors.Is(err, utils.ErrFlockCreate) { + err = utils.ErrFlockCreate + } else { + panicMsg := fmt.Sprintf("unexpected %T: %s", err, err) + panic(panicMsg) + } + } + + return nil, err + } + + defer serverSocketLockFile.Close() + + serverSocketAddress := fmt.Sprintf("P11_KIT_SERVER_ADDRESS=unix:path=%s", serverSocket) + serverEnviron := []string{ + serverSocketAddress, + } + + if utils.PathExists(serverSocket) { + logrus.Debugf("%s: socket %s already exists", logPrefix, serverSocket) + logrus.Debugf("%s: skipping", logPrefix) + return serverEnviron, nil + } + + serverArgs := []string{ + "server", + "--name", serverSocket, + "--provider", "p11-kit-trust.so", + "pkcs11:model=p11-kit-trust?write-protected=yes", + } + + if err := shell.Run("p11-kit", nil, nil, nil, serverArgs...); err != nil { + logrus.Debugf("%s failed: %s", logPrefix, err) + return nil, nil + } + + return serverEnviron, nil +} + func (err *entryPointError) Error() string { return err.msg } diff --git a/src/pkg/utils/arch.go b/src/pkg/utils/arch.go index 70d4e5617..4cfe756e7 100644 --- a/src/pkg/utils/arch.go +++ b/src/pkg/utils/arch.go @@ -25,6 +25,11 @@ func getFullyQualifiedImageArch(image, release string) string { return imageFull } +func getP11KitClientPathsArch() []string { + paths := []string{"/usr/lib/pkcs11/p11-kit-client.so"} + return paths +} + func parseReleaseArch(release string) (string, error) { if release != "latest" && release != "rolling" && release != "" { return "", &ParseReleaseError{"The release must be 'latest'."} diff --git a/src/pkg/utils/errors.go b/src/pkg/utils/errors.go index a73fdf00b..ce6c81447 100644 --- a/src/pkg/utils/errors.go +++ b/src/pkg/utils/errors.go @@ -1,5 +1,5 @@ /* - * Copyright © 2022 – 2024 Red Hat Inc. + * Copyright © 2022 – 2025 Red Hat Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,12 @@ type DistroError struct { Err error } +type FlockError struct { + Path string + Errs []error + errSuffix string +} + type ImageError struct { Image string Err error @@ -58,6 +64,25 @@ func (err *DistroError) Unwrap() error { return err.Err } +func (err *FlockError) Error() string { + if err.Errs == nil || len(err.Errs) != 2 { + panicMsg := fmt.Sprintf("invalid %T", err) + panic(panicMsg) + } + + errSuffix := " " + if err.errSuffix != "" { + errSuffix = fmt.Sprintf(" %s ", err.errSuffix) + } + + errMsg := fmt.Sprintf("%s%s%s: %s", err.Errs[0], errSuffix, err.Path, err.Errs[1]) + return errMsg +} + +func (err *FlockError) Unwrap() []error { + return err.Errs +} + func (err *ImageError) Error() string { errMsg := fmt.Sprintf("%s: %s", err.Image, err.Err) return errMsg diff --git a/src/pkg/utils/fedora.go b/src/pkg/utils/fedora.go index 0847c7b75..76476ee3f 100644 --- a/src/pkg/utils/fedora.go +++ b/src/pkg/utils/fedora.go @@ -37,6 +37,11 @@ func getFullyQualifiedImageFedora(image, release string) string { return imageFull } +func getP11KitClientPathsFedora() []string { + paths := []string{"/usr/lib64/pkcs11/p11-kit-client.so"} + return paths +} + func parseReleaseFedora(release string) (string, error) { if strings.HasPrefix(release, "F") || strings.HasPrefix(release, "f") { release = release[1:] diff --git a/src/pkg/utils/rhel.go b/src/pkg/utils/rhel.go index 3c3f2271a..19ee155d1 100644 --- a/src/pkg/utils/rhel.go +++ b/src/pkg/utils/rhel.go @@ -45,6 +45,11 @@ func getFullyQualifiedImageRHEL(image, release string) string { return imageFull } +func getP11KitClientPathsRHEL() []string { + paths := []string{"/usr/lib64/pkcs11/p11-kit-client.so"} + return paths +} + func parseReleaseRHEL(release string) (string, error) { if i := strings.IndexRune(release, '.'); i == -1 { return "", &ParseReleaseError{"The release must be in the '.' format."} diff --git a/src/pkg/utils/ubuntu.go b/src/pkg/utils/ubuntu.go index f4741dbb6..b880c9c42 100644 --- a/src/pkg/utils/ubuntu.go +++ b/src/pkg/utils/ubuntu.go @@ -38,6 +38,15 @@ func getFullyQualifiedImageUbuntu(image, release string) string { return imageFull } +func getP11KitClientPathsUbuntu() []string { + paths := []string{ + "/usr/lib/aarch64-linux-gnu/pkcs11/p11-kit-client.so", + "/usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-client.so", + } + + return paths +} + func parseReleaseUbuntu(release string) (string, error) { releaseParts := strings.Split(release, ".") if len(releaseParts) != 2 { diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index d4073c000..15cfc0129 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -40,6 +40,7 @@ import ( type GetDefaultReleaseFunc func() (string, error) type GetFullyQualifiedImageFunc func(string, string) string +type GetP11KitClientPathsFunc func() []string type ParseReleaseFunc func(string) (string, error) type Distro struct { @@ -48,6 +49,7 @@ type Distro struct { ReleaseRequired bool GetDefaultRelease GetDefaultReleaseFunc GetFullyQualifiedImage GetFullyQualifiedImageFunc + GetP11KitClientPaths GetP11KitClientPathsFunc ParseRelease ParseReleaseFunc } @@ -124,6 +126,7 @@ var ( false, getDefaultReleaseArch, getFullyQualifiedImageArch, + getP11KitClientPathsArch, parseReleaseArch, }, "fedora": { @@ -132,6 +135,7 @@ var ( true, getDefaultReleaseFedora, getFullyQualifiedImageFedora, + getP11KitClientPathsFedora, parseReleaseFedora, }, "rhel": { @@ -140,6 +144,7 @@ var ( true, getDefaultReleaseRHEL, getFullyQualifiedImageRHEL, + getP11KitClientPathsRHEL, parseReleaseRHEL, }, "ubuntu": { @@ -148,6 +153,7 @@ var ( true, getDefaultReleaseUbuntu, getFullyQualifiedImageUbuntu, + getP11KitClientPathsUbuntu, parseReleaseUbuntu, }, } @@ -164,6 +170,10 @@ var ( ErrDistroWithoutRelease = errors.New("non-default distribution must specify release") + ErrFlockAcquire = errors.New("failed to acquire lock") + + ErrFlockCreate = errors.New("failed to create lock file") + ErrImageWithoutBasename = errors.New("image does not have a basename") ) @@ -227,6 +237,23 @@ func EnsureXdgRuntimeDirIsSet(uid int) { } } +func Flock(path string, how int) (*os.File, error) { + file, err := os.Create(path) + if err != nil { + errs := []error{ErrFlockCreate, err} + return nil, &FlockError{Path: path, Errs: errs} + } + + fd := file.Fd() + fdInt := int(fd) + if err := syscall.Flock(fdInt, how); err != nil { + errs := []error{ErrFlockAcquire, err} + return nil, &FlockError{Path: path, Errs: errs, errSuffix: "on"} + } + + return file, nil +} + func ForwardToHost() (int, error) { envOptions := GetEnvOptionsForPreservedVariables() toolboxPath := os.Getenv("TOOLBOX_PATH") @@ -476,6 +503,26 @@ func GetMountOptions(target string) (string, error) { return mountOptions, nil } +func GetP11KitServerSocket(targetUser *user.User) (string, error) { + toolbxRuntimeDirectory, err := GetRuntimeDirectory(targetUser) + if err != nil { + return "", err + } + + p11KitServerSocket := filepath.Join(toolbxRuntimeDirectory, "pkcs11") + return p11KitServerSocket, nil +} + +func GetP11KitServerSocketLock(targetUser *user.User) (string, error) { + toolbxRuntimeDirectory, err := GetRuntimeDirectory(targetUser) + if err != nil { + return "", err + } + + p11KitServerSocketLock := filepath.Join(toolbxRuntimeDirectory, "pkcs11.lock") + return p11KitServerSocketLock, nil +} + func GetRuntimeDirectory(targetUser *user.User) (string, error) { if runtimeDirectories == nil { runtimeDirectories = make(map[string]string) @@ -609,6 +656,39 @@ func ImageReferenceHasDomain(image string) bool { return true } +func IsP11KitClientPresent() (bool, error) { + var p11KitClientPaths []string + var supportedDistro bool + + hostID, err := getHostID() + if err == nil { + distroObj, ok := supportedDistros[hostID] + supportedDistro = ok + if supportedDistro { + p11KitClientPaths = distroObj.GetP11KitClientPaths() + } + } + + if !supportedDistro { + if err == nil { + err = fmt.Errorf("failed to find %s in the list of supported distributions", hostID) + } + + for _, distroObj := range supportedDistros { + paths := distroObj.GetP11KitClientPaths() + p11KitClientPaths = append(p11KitClientPaths, paths...) + } + } + + for _, path := range p11KitClientPaths { + if PathExists(path) { + return true, err + } + } + + return false, err +} + func SetUpConfiguration() error { logrus.Debug("Setting up configuration")