vendor c/{buildah,common}: appendable containers.conf strings, Part 1

This change is the first step of integrating appendable string arrays
into containers.conf and starts with enabling the `Env`, `Mounts`, and
`Volumes` fields in the `[Containers]` table.

Both, Buildah and Podman, read (and sometimes write) the fields of the
`Config` struct at various places, so I decided to migrate the fields
step-by-step.  The ones in this change are most critical ones for
customers.  Once all string slices/arrays are migrated, the docs of
containers.conf will be updated.  The current changes are entirely
transparent to users.

Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
Valentin Rothberg 2023-10-24 09:05:41 +02:00
parent 35121f67bf
commit 989afd910e
15 changed files with 251 additions and 78 deletions

4
go.mod
View File

@ -12,8 +12,8 @@ require (
github.com/container-orchestrated-devices/container-device-interface v0.6.1 github.com/container-orchestrated-devices/container-device-interface v0.6.1
github.com/containernetworking/cni v1.1.2 github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.3.0 github.com/containernetworking/plugins v1.3.0
github.com/containers/buildah v1.32.1-0.20231016164031-ade05159a485 github.com/containers/buildah v1.32.1-0.20231024182922-ea815fea26a9
github.com/containers/common v0.56.1-0.20231023143107-8d0bd259cb7c github.com/containers/common v0.56.1-0.20231024140609-79773286b53a
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.7.1 github.com/containers/gvisor-tap-vsock v0.7.1
github.com/containers/image/v5 v5.28.0 github.com/containers/image/v5 v5.28.0

8
go.sum
View File

@ -249,10 +249,10 @@ github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHV
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q6mVDp5H1HnjM= github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q6mVDp5H1HnjM=
github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0= github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0=
github.com/containers/buildah v1.32.1-0.20231016164031-ade05159a485 h1:RqgxHW2iP5QJ3aRahT+KGI2aGXVZeZHTeulmeZQV0y0= github.com/containers/buildah v1.32.1-0.20231024182922-ea815fea26a9 h1:z8+hkCt6vFxvP7pdz69WSABkIZBNvdGBBMRLwE7UXpQ=
github.com/containers/buildah v1.32.1-0.20231016164031-ade05159a485/go.mod h1:gOMfotERP5Gz2pN+AnuM3ephId/YL9DmbOtVck6fWfE= github.com/containers/buildah v1.32.1-0.20231024182922-ea815fea26a9/go.mod h1:6SLsD6bQqppB1btFZSkdDHv2XxlXeqSyTUZsIroqssg=
github.com/containers/common v0.56.1-0.20231023143107-8d0bd259cb7c h1:+5wIm8TWi18iu/WZhF6T9x693nmfpP9yqyrKCreDOOU= github.com/containers/common v0.56.1-0.20231024140609-79773286b53a h1:1baXG5fPFMWklhYamf88+1vF2D5PbB2yenSKd9xLwm8=
github.com/containers/common v0.56.1-0.20231023143107-8d0bd259cb7c/go.mod h1:NGMoofxxOF8tno51JlwACw0HaUwaPS66h2N7CYJGFC0= github.com/containers/common v0.56.1-0.20231024140609-79773286b53a/go.mod h1:NGMoofxxOF8tno51JlwACw0HaUwaPS66h2N7CYJGFC0=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.7.1 h1:+Rc+sOPplrkQb/BUXeN0ug8TxjgyrIqo/9P/eNS2A4c= github.com/containers/gvisor-tap-vsock v0.7.1 h1:+Rc+sOPplrkQb/BUXeN0ug8TxjgyrIqo/9P/eNS2A4c=

View File

@ -111,6 +111,37 @@ See 'podman create --help'" "--module must be specified before the command"
"--module=ENOENT" "--module=ENOENT"
} }
@test "podman --module - append arrays" {
skip_if_remote "--module is not supported for remote clients"
random_data="expected_annotation_$(random_string 15)"
conf1_tmp="$PODMAN_TMPDIR/test1.conf"
conf2_tmp="$PODMAN_TMPDIR/test2.conf"
conf2_off_tmp="$PODMAN_TMPDIR/test2_off.conf"
cat > $conf1_tmp <<EOF
[containers]
env=["A=CONF1",{append=true}]
EOF
cat > $conf2_tmp <<EOF
[containers]
env=["B=CONF2"]
EOF
cat > $conf2_off_tmp <<EOF
[containers]
env=["B=CONF2_OFF",{append=false}]
EOF
# Once append is set, all subsequent loads (and the current) will be appended.
run_podman --module=$conf1_tmp --module=$conf2_tmp run --rm $IMAGE printenv A B
assert "$output" = "CONF1
CONF2"
# When explicitly turned off, values are replaced/overriden again.
run_podman 1 --module=$conf1_tmp --module=$conf2_off_tmp run --rm $IMAGE printenv A B
assert "$output" = "CONF2_OFF"
}
@test "podman --module - XDG_CONFIG_HOME" { @test "podman --module - XDG_CONFIG_HOME" {
skip_if_remote "--module is not supported for remote clients" skip_if_remote "--module is not supported for remote clients"
skip_if_not_rootless "loading a module from XDG_CONFIG_HOME requires rootless" skip_if_not_rootless "loading a module from XDG_CONFIG_HOME requires rootless"

View File

@ -32,7 +32,7 @@ env:
DEBIAN_NAME: "debian-13" DEBIAN_NAME: "debian-13"
# Image identifiers # Image identifiers
IMAGE_SUFFIX: "c20230816t191118z-f38f37d13" IMAGE_SUFFIX: "c20231004t194547z-f39f38d13"
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}"
DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}" DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}"

View File

@ -229,6 +229,29 @@ func createPlatformContainer(options runUsingChrootExecSubprocOptions) error {
return errors.New("unsupported createPlatformContainer") return errors.New("unsupported createPlatformContainer")
} }
func mountFlagsForFSFlags(fsFlags uintptr) uintptr {
var mountFlags uintptr
for _, mapping := range []struct {
fsFlag uintptr
mountFlag uintptr
}{
{unix.ST_MANDLOCK, unix.MS_MANDLOCK},
{unix.ST_NOATIME, unix.MS_NOATIME},
{unix.ST_NODEV, unix.MS_NODEV},
{unix.ST_NODIRATIME, unix.MS_NODIRATIME},
{unix.ST_NOEXEC, unix.MS_NOEXEC},
{unix.ST_NOSUID, unix.MS_NOSUID},
{unix.ST_RDONLY, unix.MS_RDONLY},
{unix.ST_RELATIME, unix.MS_RELATIME},
{unix.ST_SYNCHRONOUS, unix.MS_SYNCHRONOUS},
} {
if fsFlags&mapping.fsFlag == mapping.fsFlag {
mountFlags |= mapping.mountFlag
}
}
return mountFlags
}
func makeReadOnly(mntpoint string, flags uintptr) error { func makeReadOnly(mntpoint string, flags uintptr) error {
var fs unix.Statfs_t var fs unix.Statfs_t
// Make sure it's read-only. // Make sure it's read-only.
@ -236,7 +259,9 @@ func makeReadOnly(mntpoint string, flags uintptr) error {
return fmt.Errorf("checking if directory %q was bound read-only: %w", mntpoint, err) return fmt.Errorf("checking if directory %q was bound read-only: %w", mntpoint, err)
} }
if fs.Flags&unix.ST_RDONLY == 0 { if fs.Flags&unix.ST_RDONLY == 0 {
if err := unix.Mount(mntpoint, mntpoint, "bind", flags|unix.MS_REMOUNT, ""); err != nil { // All callers currently pass MS_RDONLY in "flags", but in case they stop doing
// that at some point in the future...
if err := unix.Mount(mntpoint, mntpoint, "bind", flags|unix.MS_RDONLY|unix.MS_REMOUNT|unix.MS_BIND, ""); err != nil {
return fmt.Errorf("remounting %s in mount namespace read-only: %w", mntpoint, err) return fmt.Errorf("remounting %s in mount namespace read-only: %w", mntpoint, err)
} }
} }
@ -268,7 +293,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
// Now bind mount all of those things to be under the rootfs's location in this // Now bind mount all of those things to be under the rootfs's location in this
// mount namespace. // mount namespace.
commonFlags := uintptr(unix.MS_BIND | unix.MS_REC | unix.MS_PRIVATE) commonFlags := uintptr(unix.MS_BIND | unix.MS_REC | unix.MS_PRIVATE)
bindFlags := commonFlags | unix.MS_NODEV bindFlags := commonFlags
devFlags := commonFlags | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY devFlags := commonFlags | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY
procFlags := devFlags | unix.MS_NODEV procFlags := devFlags | unix.MS_NODEV
sysFlags := devFlags | unix.MS_NODEV sysFlags := devFlags | unix.MS_NODEV
@ -291,7 +316,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", subDev, err) return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", subDev, err)
} }
if fs.Flags&unix.ST_RDONLY == 0 { if fs.Flags&unix.ST_RDONLY == 0 {
if err := unix.Mount(subDev, subDev, "bind", devFlags|unix.MS_REMOUNT, ""); err != nil { if err := unix.Mount(subDev, subDev, "bind", devFlags|unix.MS_REMOUNT|unix.MS_BIND, ""); err != nil {
return undoBinds, fmt.Errorf("remounting /dev in mount namespace read-only: %w", err) return undoBinds, fmt.Errorf("remounting /dev in mount namespace read-only: %w", err)
} }
} }
@ -351,7 +376,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
} }
logrus.Debugf("bind mounted %q to %q", "/sys", filepath.Join(spec.Root.Path, "/sys")) logrus.Debugf("bind mounted %q to %q", "/sys", filepath.Join(spec.Root.Path, "/sys"))
// Bind mount in everything we've been asked to mount. // Bind, overlay, or tmpfs mount everything we've been asked to mount.
for _, m := range spec.Mounts { for _, m := range spec.Mounts {
// Skip anything that we just mounted. // Skip anything that we just mounted.
switch m.Destination { switch m.Destination {
@ -369,12 +394,12 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
continue continue
} }
} }
// Skip anything that isn't a bind or tmpfs mount. // Skip anything that isn't a bind or overlay or tmpfs mount.
if m.Type != "bind" && m.Type != "tmpfs" && m.Type != "overlay" { if m.Type != "bind" && m.Type != "tmpfs" && m.Type != "overlay" {
logrus.Debugf("skipping mount of type %q on %q", m.Type, m.Destination) logrus.Debugf("skipping mount of type %q on %q", m.Type, m.Destination)
continue continue
} }
// If the target is there, we can just mount it. // If the target is already there, we can just mount over it.
var srcinfo os.FileInfo var srcinfo os.FileInfo
switch m.Type { switch m.Type {
case "bind": case "bind":
@ -382,24 +407,22 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
if err != nil { if err != nil {
return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", m.Source, err) return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", m.Source, err)
} }
case "overlay": case "overlay", "tmpfs":
fallthrough
case "tmpfs":
srcinfo, err = os.Stat("/") srcinfo, err = os.Stat("/")
if err != nil { if err != nil {
return undoBinds, fmt.Errorf("examining / to use as a template for a %s: %w", m.Type, err) return undoBinds, fmt.Errorf("examining / to use as a template for a %s mount: %w", m.Type, err)
} }
} }
target := filepath.Join(spec.Root.Path, m.Destination) target := filepath.Join(spec.Root.Path, m.Destination)
// Check if target is a symlink // Check if target is a symlink.
stat, err := os.Lstat(target) stat, err := os.Lstat(target)
// If target is a symlink, follow the link and ensure the destination exists // If target is a symlink, follow the link and ensure the destination exists.
if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) { if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) {
target, err = copier.Eval(spec.Root.Path, m.Destination, copier.EvalOptions{}) target, err = copier.Eval(spec.Root.Path, m.Destination, copier.EvalOptions{})
if err != nil { if err != nil {
return nil, fmt.Errorf("evaluating symlink %q: %w", target, err) return nil, fmt.Errorf("evaluating symlink %q: %w", target, err)
} }
// Stat the destination of the evaluated symlink // Stat the destination of the evaluated symlink.
_, err = os.Stat(target) _, err = os.Stat(target)
} }
if err != nil { if err != nil {
@ -407,7 +430,8 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", target, err) return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", target, err)
} }
// The target isn't there yet, so create it. // The target isn't there yet, so create it. If the source is a directory,
// we need a directory, otherwise we need a non-directory (i.e., a file).
if srcinfo.IsDir() { if srcinfo.IsDir() {
if err = os.MkdirAll(target, 0755); err != nil { if err = os.MkdirAll(target, 0755); err != nil {
return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err) return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err)
@ -423,79 +447,94 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
file.Close() file.Close()
} }
} }
// Sort out which flags we're asking for, and what statfs() should be telling us
// if we successfully mounted with them.
requestFlags := uintptr(0) requestFlags := uintptr(0)
expectedFlags := uintptr(0) expectedImportantFlags := uintptr(0)
importantFlags := uintptr(0)
possibleImportantFlags := uintptr(unix.ST_NODEV | unix.ST_NOEXEC | unix.ST_NOSUID | unix.ST_RDONLY)
for _, option := range m.Options { for _, option := range m.Options {
switch option { switch option {
case "nodev": case "nodev":
requestFlags |= unix.MS_NODEV requestFlags |= unix.MS_NODEV
expectedFlags |= unix.ST_NODEV importantFlags |= unix.ST_NODEV
expectedImportantFlags |= unix.ST_NODEV
case "dev": case "dev":
requestFlags &= ^uintptr(unix.MS_NODEV) requestFlags &= ^uintptr(unix.MS_NODEV)
expectedFlags &= ^uintptr(unix.ST_NODEV) importantFlags |= unix.ST_NODEV
expectedImportantFlags &= ^uintptr(unix.ST_NODEV)
case "noexec": case "noexec":
requestFlags |= unix.MS_NOEXEC requestFlags |= unix.MS_NOEXEC
expectedFlags |= unix.ST_NOEXEC importantFlags |= unix.ST_NOEXEC
expectedImportantFlags |= unix.ST_NOEXEC
case "exec": case "exec":
requestFlags &= ^uintptr(unix.MS_NOEXEC) requestFlags &= ^uintptr(unix.MS_NOEXEC)
expectedFlags &= ^uintptr(unix.ST_NOEXEC) importantFlags |= unix.ST_NOEXEC
expectedImportantFlags &= ^uintptr(unix.ST_NOEXEC)
case "nosuid": case "nosuid":
requestFlags |= unix.MS_NOSUID requestFlags |= unix.MS_NOSUID
expectedFlags |= unix.ST_NOSUID importantFlags |= unix.ST_NOSUID
expectedImportantFlags |= unix.ST_NOSUID
case "suid": case "suid":
requestFlags &= ^uintptr(unix.MS_NOSUID) requestFlags &= ^uintptr(unix.MS_NOSUID)
expectedFlags &= ^uintptr(unix.ST_NOSUID) importantFlags |= unix.ST_NOSUID
expectedImportantFlags &= ^uintptr(unix.ST_NOSUID)
case "ro": case "ro":
requestFlags |= unix.MS_RDONLY requestFlags |= unix.MS_RDONLY
expectedFlags |= unix.ST_RDONLY importantFlags |= unix.ST_RDONLY
expectedImportantFlags |= unix.ST_RDONLY
case "rw": case "rw":
requestFlags &= ^uintptr(unix.MS_RDONLY) requestFlags &= ^uintptr(unix.MS_RDONLY)
expectedFlags &= ^uintptr(unix.ST_RDONLY) importantFlags |= unix.ST_RDONLY
expectedImportantFlags &= ^uintptr(unix.ST_RDONLY)
} }
} }
switch m.Type { switch m.Type {
case "bind": case "bind":
// Do the bind mount. // Do the initial bind mount. We'll worry about the flags in a bit.
logrus.Debugf("bind mounting %q on %q", m.Destination, filepath.Join(spec.Root.Path, m.Destination)) logrus.Debugf("bind mounting %q on %q %v", m.Destination, filepath.Join(spec.Root.Path, m.Destination), m.Options)
if err := unix.Mount(m.Source, target, "", bindFlags|requestFlags, ""); err != nil { if err = unix.Mount(m.Source, target, "", bindFlags|requestFlags, ""); err != nil {
return undoBinds, fmt.Errorf("bind mounting %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err) return undoBinds, fmt.Errorf("bind mounting %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err)
} }
if (requestFlags & unix.MS_RDONLY) != 0 {
if err = unix.Statfs(target, &fs); err != nil {
return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", target, err)
}
// we need to make sure these flags are maintained in the REMOUNT operation
additionalFlags := uintptr(fs.Flags) & (unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV)
if err := unix.Mount("", target, "", unix.MS_REMOUNT|unix.MS_BIND|unix.MS_RDONLY|additionalFlags, ""); err != nil {
return undoBinds, fmt.Errorf("setting flags on the bind mount %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err)
}
}
logrus.Debugf("bind mounted %q to %q", m.Source, target) logrus.Debugf("bind mounted %q to %q", m.Source, target)
case "tmpfs": case "tmpfs":
// Mount a tmpfs. // Mount a tmpfs. We'll worry about the flags in a bit.
if err := mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil { if err = mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil {
return undoBinds, fmt.Errorf("mounting tmpfs to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(m.Options, ","), err) return undoBinds, fmt.Errorf("mounting tmpfs to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(append(m.Options, "private"), ","), err)
} }
logrus.Debugf("mounted a tmpfs to %q", target) logrus.Debugf("mounted a tmpfs to %q", target)
case "overlay": case "overlay":
// Mount a overlay. // Mount an overlay. We'll worry about the flags in a bit.
if err := mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil { if err = mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil {
return undoBinds, fmt.Errorf("mounting overlay to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(m.Options, ","), err) return undoBinds, fmt.Errorf("mounting overlay to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(append(m.Options, "private"), ","), err)
} }
logrus.Debugf("mounted a overlay to %q", target) logrus.Debugf("mounted a overlay to %q", target)
} }
// Time to worry about the flags.
if err = unix.Statfs(target, &fs); err != nil { if err = unix.Statfs(target, &fs); err != nil {
return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", target, err) return undoBinds, fmt.Errorf("checking if volume %q was mounted with requested flags: %w", target, err)
} }
if uintptr(fs.Flags)&expectedFlags != expectedFlags { effectiveImportantFlags := uintptr(fs.Flags) & importantFlags
if err := unix.Mount(target, target, "bind", requestFlags|unix.MS_REMOUNT, ""); err != nil { if effectiveImportantFlags != expectedImportantFlags {
return undoBinds, fmt.Errorf("remounting %q in mount namespace with expected flags: %w", target, err) // Do a remount to try to get the desired flags to stick.
effectiveUnimportantFlags := uintptr(fs.Flags) & ^possibleImportantFlags
if err = unix.Mount(target, target, m.Type, unix.MS_REMOUNT|bindFlags|requestFlags|mountFlagsForFSFlags(effectiveUnimportantFlags), ""); err != nil {
return undoBinds, fmt.Errorf("remounting %q in mount namespace with flags %#x instead of %#x: %w", target, requestFlags, effectiveImportantFlags, err)
}
// Check if the desired flags stuck.
if err = unix.Statfs(target, &fs); err != nil {
return undoBinds, fmt.Errorf("checking if directory %q was remounted with requested flags %#x instead of %#x: %w", target, requestFlags, effectiveImportantFlags, err)
}
newEffectiveImportantFlags := uintptr(fs.Flags) & importantFlags
if newEffectiveImportantFlags != expectedImportantFlags {
return undoBinds, fmt.Errorf("unable to remount %q with requested flags %#x instead of %#x, just got %#x back", target, requestFlags, effectiveImportantFlags, newEffectiveImportantFlags)
} }
} }
} }
// Set up any read-only paths that we need to. If we're running inside // Set up any read-only paths that we need to. If we're running inside
// of a container, some of these locations will already be read-only. // of a container, some of these locations will already be read-only, in
// which case can declare victory and move on.
for _, roPath := range spec.Linux.ReadonlyPaths { for _, roPath := range spec.Linux.ReadonlyPaths {
r := filepath.Join(spec.Root.Path, roPath) r := filepath.Join(spec.Root.Path, roPath)
target, err := filepath.EvalSymlinks(r) target, err := filepath.EvalSymlinks(r)
@ -515,12 +554,13 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
} }
return undoBinds, fmt.Errorf("checking if directory %q is already read-only: %w", target, err) return undoBinds, fmt.Errorf("checking if directory %q is already read-only: %w", target, err)
} }
if fs.Flags&unix.ST_RDONLY != 0 { if fs.Flags&unix.ST_RDONLY == unix.ST_RDONLY {
continue continue
} }
// Mount the location over itself, so that we can remount it as read-only. // Mount the location over itself, so that we can remount it as read-only, making
roFlags := uintptr(unix.MS_NODEV | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY) // sure to preserve any combination of nodev/noexec/nosuid that's already in play.
if err := unix.Mount(target, target, "", roFlags|unix.MS_BIND|unix.MS_REC, ""); err != nil { roFlags := mountFlagsForFSFlags(uintptr(fs.Flags)) | unix.MS_RDONLY
if err := unix.Mount(target, target, "", bindFlags|roFlags, ""); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// No target, no problem. // No target, no problem.
continue continue
@ -532,7 +572,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", target, err) return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", target, err)
} }
if fs.Flags&unix.ST_RDONLY == 0 { if fs.Flags&unix.ST_RDONLY == 0 {
if err := unix.Mount(target, target, "", roFlags|unix.MS_BIND|unix.MS_REMOUNT, ""); err != nil { if err := unix.Mount(target, target, "", unix.MS_REMOUNT|unix.MS_RDONLY|bindFlags|mountFlagsForFSFlags(uintptr(fs.Flags)), ""); err != nil {
return undoBinds, fmt.Errorf("remounting %q in mount namespace read-only: %w", target, err) return undoBinds, fmt.Errorf("remounting %q in mount namespace read-only: %w", target, err)
} }
} }
@ -541,6 +581,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, fmt.Errorf("checking if directory %q was remounted read-only: %w", target, err) return undoBinds, fmt.Errorf("checking if directory %q was remounted read-only: %w", target, err)
} }
if fs.Flags&unix.ST_RDONLY == 0 { if fs.Flags&unix.ST_RDONLY == 0 {
// Still not read only.
return undoBinds, fmt.Errorf("verifying that %q in mount namespace was remounted read-only: %w", target, err) return undoBinds, fmt.Errorf("verifying that %q in mount namespace was remounted read-only: %w", target, err)
} }
} }
@ -578,7 +619,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
if err = unix.Statfs(target, &statfs); err != nil { if err = unix.Statfs(target, &statfs); err != nil {
return undoBinds, fmt.Errorf("checking if directory %q is a mountpoint: %w", target, err) return undoBinds, fmt.Errorf("checking if directory %q is a mountpoint: %w", target, err)
} }
isReadOnly := statfs.Flags&unix.MS_RDONLY != 0 isReadOnly := statfs.Flags&unix.ST_RDONLY == unix.ST_RDONLY
// Check if any of the IDs we're mapping could read it. // Check if any of the IDs we're mapping could read it.
var stat unix.Stat_t var stat unix.Stat_t
if err = unix.Stat(target, &stat); err != nil { if err = unix.Stat(target, &stat); err != nil {
@ -641,11 +682,11 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, fmt.Errorf("masking directory %q in mount namespace: %w", target, err) return undoBinds, fmt.Errorf("masking directory %q in mount namespace: %w", target, err)
} }
if err = unix.Statfs(target, &fs); err != nil { if err = unix.Statfs(target, &fs); err != nil {
return undoBinds, fmt.Errorf("checking if directory %q was mounted read-only in mount namespace: %w", target, err) return undoBinds, fmt.Errorf("checking if masked directory %q was mounted read-only in mount namespace: %w", target, err)
} }
if fs.Flags&unix.ST_RDONLY == 0 { if fs.Flags&unix.ST_RDONLY == 0 {
if err = unix.Mount(target, target, "", roFlags|syscall.MS_REMOUNT, ""); err != nil { if err = unix.Mount(target, target, "", syscall.MS_REMOUNT|roFlags|mountFlagsForFSFlags(uintptr(fs.Flags)), ""); err != nil {
return undoBinds, fmt.Errorf("making sure directory %q in mount namespace is read only: %w", target, err) return undoBinds, fmt.Errorf("making sure masked directory %q in mount namespace is read only: %w", target, err)
} }
} }
} }

View File

@ -13,6 +13,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const seccompAvailable = true
// setSeccomp sets the seccomp filter for ourselves and any processes that we'll start. // setSeccomp sets the seccomp filter for ourselves and any processes that we'll start.
func setSeccomp(spec *specs.Spec) error { func setSeccomp(spec *specs.Spec) error {
logrus.Debugf("setting seccomp configuration") logrus.Debugf("setting seccomp configuration")

View File

@ -7,6 +7,8 @@ import (
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
) )
const seccompAvailable = false
func setSeccomp(spec *specs.Spec) error { func setSeccomp(spec *specs.Spec) error {
// Ignore this on FreeBSD // Ignore this on FreeBSD
return nil return nil

View File

@ -9,6 +9,8 @@ import (
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
) )
const seccompAvailable = false
func setSeccomp(spec *specs.Spec) error { func setSeccomp(spec *specs.Spec) error {
if spec.Linux.Seccomp != nil { if spec.Linux.Seccomp != nil {
return errors.New("configured a seccomp filter without seccomp support?") return errors.New("configured a seccomp filter without seccomp support?")

View File

@ -188,7 +188,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
} }
transientMounts := []Mount{} transientMounts := []Mount{}
for _, volume := range append(defaultContainerConfig.Containers.Volumes, options.TransientMounts...) { for _, volume := range append(defaultContainerConfig.Volumes(), options.TransientMounts...) {
mount, err := parse.Volume(volume) mount, err := parse.Volume(volume)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -374,7 +374,7 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults,
fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])") fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])")
fs.StringVar(&flags.ShmSize, "shm-size", defaultContainerConfig.Containers.ShmSize, "size of '/dev/shm'. The format is `<number><unit>`.") fs.StringVar(&flags.ShmSize, "shm-size", defaultContainerConfig.Containers.ShmSize, "size of '/dev/shm'. The format is `<number><unit>`.")
fs.StringSliceVar(&flags.Ulimit, "ulimit", defaultContainerConfig.Containers.DefaultUlimits, "ulimit options") fs.StringSliceVar(&flags.Ulimit, "ulimit", defaultContainerConfig.Containers.DefaultUlimits, "ulimit options")
fs.StringArrayVarP(&flags.Volumes, "volume", "v", defaultContainerConfig.Containers.Volumes, "bind mount a volume into the container") fs.StringArrayVarP(&flags.Volumes, "volume", "v", defaultContainerConfig.Volumes(), "bind mount a volume into the container")
// Add in the usernamespace and namespaceflags // Add in the usernamespace and namespaceflags
usernsFlags := GetUserNSFlags(usernsResults) usernsFlags := GetUserNSFlags(usernsResults)

View File

@ -0,0 +1,92 @@
package attributedstring
import (
"bytes"
"fmt"
"github.com/BurntSushi/toml"
)
// Slice allows for extending a TOML string array with custom
// attributes that control how the array is marshaled into a Go string.
//
// Specifically, an Slice can be configured to avoid it being
// overridden by a subsequent unmarshal sequence. When the `append` attribute
// is specified, the array will be appended instead (e.g., `array=["9",
// {append=true}]`).
type Slice struct { // A "mixed-type array" in TOML.
// Note that the fields below _must_ be exported. Otherwise the TOML
// encoder would fail during type reflection.
Values []string
Attributes struct { // Using a struct allows for adding more attributes in the future.
Append *bool // Nil if not set by the user
}
}
// Get returns the Slice values or an empty string slice.
func (a *Slice) Get() []string {
if a.Values == nil {
return []string{}
}
return a.Values
}
// UnmarshalTOML is the custom unmarshal method for Slice.
func (a *Slice) UnmarshalTOML(data interface{}) error {
iFaceSlice, ok := data.([]interface{})
if !ok {
return fmt.Errorf("unable to cast to interface array: %v", data)
}
var loadedStrings []string
for _, x := range iFaceSlice { // Iterate over each item in the slice.
switch val := x.(type) {
case string: // Strings are directly appended to the slice.
loadedStrings = append(loadedStrings, val)
case map[string]interface{}: // The attribute struct is represented as a map.
for k, v := range val { // Iterate over all _supported_ keys.
switch k {
case "append":
boolVal, ok := v.(bool)
if !ok {
return fmt.Errorf("unable to cast append to bool: %v", k)
}
a.Attributes.Append = &boolVal
default: // Unsupported map key.
return fmt.Errorf("unsupported key %q in map: %v", k, val)
}
}
default: // Unsupported item.
return fmt.Errorf("unsupported item in attributed string slice: %v", x)
}
}
if a.Attributes.Append != nil && *a.Attributes.Append { // If _explicitly_ configured, append the loaded slice.
a.Values = append(a.Values, loadedStrings...)
} else { // Default: override the existing Slice.
a.Values = loadedStrings
}
return nil
}
// MarshalTOML is the custom marshal method for Slice.
func (a *Slice) MarshalTOML() ([]byte, error) {
iFaceSlice := make([]interface{}, 0, len(a.Values))
for _, x := range a.Values {
iFaceSlice = append(iFaceSlice, x)
}
if a.Attributes.Append != nil {
Attributes := make(map[string]any)
Attributes["append"] = *a.Attributes.Append
iFaceSlice = append(iFaceSlice, Attributes)
}
buf := new(bytes.Buffer)
enc := toml.NewEncoder(buf)
if err := enc.Encode(iFaceSlice); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/containers/common/internal/attributedstring"
"github.com/containers/common/libnetwork/types" "github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/capabilities"
"github.com/containers/common/pkg/util" "github.com/containers/common/pkg/util"
@ -71,7 +72,7 @@ type ContainersConfig struct {
Devices []string `toml:"devices,omitempty"` Devices []string `toml:"devices,omitempty"`
// Volumes to add to all containers // Volumes to add to all containers
Volumes []string `toml:"volumes,omitempty"` Volumes attributedstring.Slice `toml:"volumes,omitempty"`
// ApparmorProfile is the apparmor profile name which is used as the // ApparmorProfile is the apparmor profile name which is used as the
// default for the runtime. // default for the runtime.
@ -133,7 +134,7 @@ type ContainersConfig struct {
EnableLabeledUsers bool `toml:"label_users,omitempty"` EnableLabeledUsers bool `toml:"label_users,omitempty"`
// Env is the environment variable list for container process. // Env is the environment variable list for container process.
Env []string `toml:"env,omitempty"` Env attributedstring.Slice `toml:"env,omitempty"`
// EnvHost Pass all host environment variables into the container. // EnvHost Pass all host environment variables into the container.
EnvHost bool `toml:"env_host,omitempty"` EnvHost bool `toml:"env_host,omitempty"`
@ -171,7 +172,7 @@ type ContainersConfig struct {
LogTag string `toml:"log_tag,omitempty"` LogTag string `toml:"log_tag,omitempty"`
// Mount to add to all containers // Mount to add to all containers
Mounts []string `toml:"mounts,omitempty"` Mounts attributedstring.Slice `toml:"mounts,omitempty"`
// NetNS indicates how to create a network namespace for the container // NetNS indicates how to create a network namespace for the container
NetNS string `toml:"netns,omitempty"` NetNS string `toml:"netns,omitempty"`
@ -907,7 +908,7 @@ func (c *Config) GetDefaultEnvEx(envHost, httpProxy bool) []string {
} }
} }
} }
return append(env, c.Containers.Env...) return append(env, c.Containers.Env.Get()...)
} }
// Capabilities returns the capabilities parses the Add and Drop capability // Capabilities returns the capabilities parses the Add and Drop capability

View File

@ -9,6 +9,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/containers/common/internal/attributedstring"
nettypes "github.com/containers/common/libnetwork/types" nettypes "github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/apparmor"
"github.com/containers/common/pkg/cgroupv2" "github.com/containers/common/pkg/cgroupv2"
@ -204,8 +205,8 @@ func defaultConfig() (*Config, error) {
Devices: []string{}, Devices: []string{},
EnableKeyring: true, EnableKeyring: true,
EnableLabeling: selinuxEnabled(), EnableLabeling: selinuxEnabled(),
Env: []string{ Env: attributedstring.Slice{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", Values: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
}, },
EnvHost: false, EnvHost: false,
HTTPProxy: true, HTTPProxy: true,
@ -214,7 +215,7 @@ func defaultConfig() (*Config, error) {
InitPath: "", InitPath: "",
LogDriver: defaultLogDriver(), LogDriver: defaultLogDriver(),
LogSizeMax: DefaultLogSizeMax, LogSizeMax: DefaultLogSizeMax,
Mounts: []string{}, Mounts: attributedstring.Slice{},
NetNS: "private", NetNS: "private",
NoHosts: false, NoHosts: false,
PidNS: "private", PidNS: "private",
@ -224,7 +225,7 @@ func defaultConfig() (*Config, error) {
UTSNS: "private", UTSNS: "private",
Umask: "0022", Umask: "0022",
UserNSSize: DefaultUserNSSize, // Deprecated UserNSSize: DefaultUserNSSize, // Deprecated
Volumes: []string{}, Volumes: attributedstring.Slice{},
}, },
Network: NetworkConfig{ Network: NetworkConfig{
DefaultNetwork: "podman", DefaultNetwork: "podman",
@ -509,12 +510,12 @@ func (c *Config) Sysctls() []string {
// Volumes returns the default set of volumes that should be mounted in containers. // Volumes returns the default set of volumes that should be mounted in containers.
func (c *Config) Volumes() []string { func (c *Config) Volumes() []string {
return c.Containers.Volumes return c.Containers.Volumes.Get()
} }
// Mounts returns the default set of mounts that should be mounted in containers. // Mounts returns the default set of mounts that should be mounted in containers.
func (c *Config) Mounts() []string { func (c *Config) Mounts() []string {
return c.Containers.Mounts return c.Containers.Mounts.Get()
} }
// Devices returns the default additional devices for containers. // Devices returns the default additional devices for containers.
@ -539,7 +540,7 @@ func (c *Config) DNSOptions() []string {
// Env returns the default additional environment variables to add to containers. // Env returns the default additional environment variables to add to containers.
func (c *Config) Env() []string { func (c *Config) Env() []string {
return c.Containers.Env return c.Containers.Env.Values
} }
// IPCNS returns the default IPC Namespace configuration to run containers with. // IPCNS returns the default IPC Namespace configuration to run containers with.

5
vendor/modules.txt vendored
View File

@ -139,7 +139,7 @@ github.com/containernetworking/cni/pkg/version
# github.com/containernetworking/plugins v1.3.0 # github.com/containernetworking/plugins v1.3.0
## explicit; go 1.20 ## explicit; go 1.20
github.com/containernetworking/plugins/pkg/ns github.com/containernetworking/plugins/pkg/ns
# github.com/containers/buildah v1.32.1-0.20231016164031-ade05159a485 # github.com/containers/buildah v1.32.1-0.20231024182922-ea815fea26a9
## explicit; go 1.18 ## explicit; go 1.18
github.com/containers/buildah github.com/containers/buildah
github.com/containers/buildah/bind github.com/containers/buildah/bind
@ -167,8 +167,9 @@ github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes github.com/containers/buildah/pkg/volumes
github.com/containers/buildah/util github.com/containers/buildah/util
# github.com/containers/common v0.56.1-0.20231023143107-8d0bd259cb7c # github.com/containers/common v0.56.1-0.20231024140609-79773286b53a
## explicit; go 1.18 ## explicit; go 1.18
github.com/containers/common/internal/attributedstring
github.com/containers/common/libimage github.com/containers/common/libimage
github.com/containers/common/libimage/define github.com/containers/common/libimage/define
github.com/containers/common/libimage/filter github.com/containers/common/libimage/filter