Give access to the CA certificates from the host
This uses the same approach taken by Flatpak [1] to ensure that the certificates from certificate authorities (or CAs) that are available inside a Toolbx container are kept synchronized with the host operating system. Any program that uses PKCS #11 to access CA certificates should see the same ones both inside the container and on the host. During every 'enter' and 'run' command, toolbox(1) ensures that an instance of 'p11-kit server' is running on the host listening on a local file system socket that's accessible to both the container and the host. If an instance is already running, then a second one is not created. The location of the socket is injected into the container through the P11_KIT_SERVER_ADDRESS environment variable. Just like Flatpak, the singleton 'p11-kit server' process is not terminated when the last 'enter' or 'run' command exits. The Toolbx container's entry point configures it to use the p11-kit-client.so PKCS #11 module instead of the usual p11-kit-trust.so module. This talks to the 'p11-kit server' instance running on the host over the socket instead of reading the CA certificates that are present inside the container. However, unlike Flatpak, this doesn't use D-Bus to set up the communication between the container and the host, because when invoked as 'sudo toolbox ...' there's no user or session D-Bus instance available for the root user. This set-up is skipped if 'p11-kit server' can't be run on the host, or if the /etc/pkcs11/modules directory for configuring PKCS #11 modules or p11-kit-client.so are missing inside the container. None of these are considered hard dependencies to accommodate size-constrained OSes like Fedora CoreOS that might not have 'p11-kit server', and existing Toolbx containers and old images that might not have p11-kit-client.so. The UBI-based toolbox images haven't yet been updated to contain p11-kit-client.so. Until that happens, containers created from them won't have access to the CA certificates from the host. The CI needs to be run without 'p11-kit server' because the lingering singleton process causes Bats to hang when tearing down the suite of system tests [2]. To terminate the 'p11-kit server' instance run by the system tests, it needs to be distinguishable from the instance run by 'normal' use of Toolbx by the user. One way to do this is to isolate the host operating system's XDG_RUNTIME_DIR from the system tests. Unfortunately, this is easier said than done [3]. So, this workaround has to suffice until the problem is solved. On the Ubuntu 22.04 CI nodes, it's not possible to remove the p11-kit package that provides 'p11-kit server', because it leads to: $ sudo dpkg --purge p11-kit dpkg: dependency problems prevent removal of p11-kit: adoptium-ca-certificates depends on p11-kit. Therefore, as a workaround only the /usr/libexec/p11-kit/p11-kit-server binary that provides the 'server' command is removed. The rest of the p11-kit package is left untouched. [1] Flatpak commit 66b2ff40f7caf3a7 https://github.com/flatpak/flatpak/commit/66b2ff40f7caf3a7 https://github.com/flatpak/flatpak/pull/1757 https://github.com/p11-glue/p11-kit/issues/68 [2] https://bats-core.readthedocs.io/en/stable/writing-tests.html [3] https://github.com/containers/toolbox/pull/1652 https://github.com/containers/toolbox/issues/626
This commit is contained in:
		
							parent
							
								
									9e776b6c94
								
							
						
					
					
						commit
						5ed2442214
					
				|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										21
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										21
									
								
								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) | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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 }}' | ||||
|  |  | |||
|  | @ -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 }}' | ||||
|  |  | |||
|  | @ -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 }}' | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -503,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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue