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:
Debarshi Ray 2025-04-29 00:14:18 +02:00
parent 9e776b6c94
commit 5ed2442214
11 changed files with 201 additions and 8 deletions

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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 }}'

View File

@ -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 }}'

View File

@ -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 }}'

View File

@ -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

View File

@ -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
}

View File

@ -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)