mirror of
https://github.com/containers/podman.git
synced 2025-07-04 18:27:33 +08:00
libpod: support idmap for --rootfs
add a new option idmap to --rootfs that works in the same way as it does for volumes. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
@ -21,3 +21,12 @@ finishes executing, similar to a tmpfs mount point being unmounted.
|
||||
|
||||
Note: On **SELinux** systems, the rootfs needs the correct label, which is by default
|
||||
**unconfined_u:object_r:container_file_t:s0**.
|
||||
|
||||
The `idmap` option if specified, creates an idmapped mount to the target user
|
||||
namespace in the container.
|
||||
The idmap option supports a custom mapping that can be different than the user
|
||||
namespace used by the container. The mapping can be specified after the idmap
|
||||
option like: `idmap=uids=0-1-10#10-11-10;gids=0-100-10`. For each triplet, the
|
||||
first value is the start of the backing file system IDs that are mapped to the
|
||||
second value on the host. The length of this mapping is given in the third value.
|
||||
Multiple ranges are separated with #.
|
||||
|
@ -116,6 +116,8 @@ type ContainerRootFSConfig struct {
|
||||
Rootfs string `json:"rootfs,omitempty"`
|
||||
// RootfsOverlay tells if rootfs has to be mounted as an overlay
|
||||
RootfsOverlay bool `json:"rootfs_overlay,omitempty"`
|
||||
// RootfsMapping specifies if there are mappings to apply to the rootfs.
|
||||
RootfsMapping *string `json:"rootfs_mapping,omitempty"`
|
||||
// ShmDir is the path to be mounted on /dev/shm in container.
|
||||
// If not set manually at creation time, Libpod will create a tmpfs
|
||||
// with the size specified in ShmSize and populate this with the path of
|
||||
|
@ -35,6 +35,7 @@ import (
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
"github.com/containers/storage"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/idmap"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
@ -370,9 +371,6 @@ func (c *Container) syncContainer() error {
|
||||
}
|
||||
|
||||
func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
|
||||
if c.config.Rootfs != "" {
|
||||
return
|
||||
}
|
||||
*dest = *from
|
||||
// If we are creating a container inside a pod, we always want to inherit the
|
||||
// userns settings from the infra container. So clear the auto userns settings
|
||||
@ -1525,6 +1523,31 @@ func (c *Container) mountStorage() (_ string, deferredErr error) {
|
||||
// We need to mount the container before volumes - to ensure the copyup
|
||||
// works properly.
|
||||
mountPoint := c.config.Rootfs
|
||||
|
||||
if c.config.RootfsMapping != nil {
|
||||
uidMappings, gidMappings, err := parseIDMapMountOption(c.config.IDMappings, *c.config.RootfsMapping, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pid, cleanupFunc, err := idmap.CreateUsernsProcess(util.RuntimeSpecToIDtools(uidMappings), util.RuntimeSpecToIDtools(gidMappings))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer cleanupFunc()
|
||||
|
||||
if err := idmap.CreateIDMappedMount(c.config.Rootfs, c.config.Rootfs, pid); err != nil {
|
||||
return "", fmt.Errorf("failed to create idmapped mount: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if deferredErr != nil {
|
||||
if err := unix.Unmount(c.config.Rootfs, 0); err != nil {
|
||||
logrus.Errorf("Unmounting idmapped rootfs for container %s after mount error: %v", c.ID(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Check if overlay has to be created on top of Rootfs
|
||||
if c.config.RootfsOverlay {
|
||||
overlayDest := c.runtime.GraphRoot()
|
||||
@ -1795,6 +1818,11 @@ func (c *Container) cleanupStorage() error {
|
||||
cleanupErr = err
|
||||
}
|
||||
}
|
||||
if c.config.RootfsMapping != nil {
|
||||
if err := unix.Unmount(c.config.Rootfs, 0); err != nil {
|
||||
logrus.Errorf("Unmounting idmapped rootfs for container %s after mount error: %v", c.ID(), err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, containerMount := range c.config.Mounts {
|
||||
if err := c.unmountSHM(containerMount); err != nil {
|
||||
|
@ -74,7 +74,7 @@ func parseOptionIDs(option string) ([]idtools.IDMap, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string) ([]spec.LinuxIDMapping, []spec.LinuxIDMapping, error) {
|
||||
func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string, invert bool) ([]spec.LinuxIDMapping, []spec.LinuxIDMapping, error) {
|
||||
uidMap := idMappings.UIDMap
|
||||
gidMap := idMappings.GIDMap
|
||||
if strings.HasPrefix(option, "idmap=") {
|
||||
@ -101,18 +101,34 @@ func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string) ([
|
||||
uidMappings := make([]spec.LinuxIDMapping, len(uidMap))
|
||||
gidMappings := make([]spec.LinuxIDMapping, len(gidMap))
|
||||
for i, uidmap := range uidMap {
|
||||
if invert {
|
||||
uidMappings[i] = spec.LinuxIDMapping{
|
||||
HostID: uint32(uidmap.ContainerID),
|
||||
ContainerID: uint32(uidmap.HostID),
|
||||
Size: uint32(uidmap.Size),
|
||||
}
|
||||
} else {
|
||||
uidMappings[i] = spec.LinuxIDMapping{
|
||||
HostID: uint32(uidmap.HostID),
|
||||
ContainerID: uint32(uidmap.ContainerID),
|
||||
Size: uint32(uidmap.Size),
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, gidmap := range gidMap {
|
||||
if invert {
|
||||
gidMappings[i] = spec.LinuxIDMapping{
|
||||
HostID: uint32(gidmap.ContainerID),
|
||||
ContainerID: uint32(gidmap.HostID),
|
||||
Size: uint32(gidmap.Size),
|
||||
}
|
||||
} else {
|
||||
gidMappings[i] = spec.LinuxIDMapping{
|
||||
HostID: uint32(gidmap.HostID),
|
||||
ContainerID: uint32(gidmap.ContainerID),
|
||||
Size: uint32(gidmap.Size),
|
||||
}
|
||||
}
|
||||
}
|
||||
return uidMappings, gidMappings, nil
|
||||
}
|
||||
@ -288,7 +304,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||
for _, o := range m.Options {
|
||||
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
||||
var err error
|
||||
m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o)
|
||||
m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func TestParseIDMapMountOption(t *testing.T) {
|
||||
UIDMap: uidMap,
|
||||
GIDMap: gidMap,
|
||||
}
|
||||
uids, gids, err := parseIDMapMountOption(options, "idmap")
|
||||
uids, gids, err := parseIDMapMountOption(options, "idmap", true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(uids), 1)
|
||||
assert.Equal(t, len(gids), 1)
|
||||
@ -78,7 +78,7 @@ func TestParseIDMapMountOption(t *testing.T) {
|
||||
assert.Equal(t, gids[0].HostID, uint32(0))
|
||||
assert.Equal(t, gids[0].Size, uint32(10000))
|
||||
|
||||
uids, gids, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10")
|
||||
uids, gids, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10", true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(uids), 2)
|
||||
assert.Equal(t, len(gids), 1)
|
||||
@ -95,19 +95,19 @@ func TestParseIDMapMountOption(t *testing.T) {
|
||||
assert.Equal(t, gids[0].HostID, uint32(0))
|
||||
assert.Equal(t, gids[0].Size, uint32(10))
|
||||
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10;foobar=bar")
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10;foobar=bar", true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12")
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12", true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12--12")
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12--12", true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#-1-12-12")
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#-1-12-12", true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0--12-0")
|
||||
_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0--12-0", true)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
|
@ -1334,7 +1334,7 @@ func WithCommand(command []string) CtrCreateOption {
|
||||
|
||||
// WithRootFS sets the rootfs for the container.
|
||||
// This creates a container from a directory on disk and not an image.
|
||||
func WithRootFS(rootfs string, overlay bool) CtrCreateOption {
|
||||
func WithRootFS(rootfs string, overlay bool, mapping *string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return define.ErrCtrFinalized
|
||||
@ -1344,6 +1344,7 @@ func WithRootFS(rootfs string, overlay bool) CtrCreateOption {
|
||||
}
|
||||
ctr.config.Rootfs = rootfs
|
||||
ctr.config.RootfsOverlay = overlay
|
||||
ctr.config.RootfsMapping = mapping
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
|
||||
}
|
||||
|
||||
if s.Rootfs != "" {
|
||||
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay))
|
||||
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay, s.RootfsMapping))
|
||||
}
|
||||
|
||||
newImage, resolvedImageName, imageData, err := getImageFromSpec(ctx, rt, s)
|
||||
@ -513,7 +513,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
|
||||
options = append(options, libpod.WithShmSize(*s.ShmSize))
|
||||
}
|
||||
if s.Rootfs != "" {
|
||||
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay))
|
||||
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay, s.RootfsMapping))
|
||||
}
|
||||
// Default used if not overridden on command line
|
||||
|
||||
|
@ -238,6 +238,8 @@ type ContainerStorageConfig struct {
|
||||
Rootfs string `json:"rootfs,omitempty"`
|
||||
// RootfsOverlay tells if rootfs is actually an overlay on top of base path
|
||||
RootfsOverlay bool `json:"rootfs_overlay,omitempty"`
|
||||
// RootfsMapping specifies if there are mappings to apply to the rootfs.
|
||||
RootfsMapping *string `json:"rootfs_mapping,omitempty"`
|
||||
// ImageVolumeMode indicates how image volumes will be created.
|
||||
// Supported modes are "ignore" (do not create), "tmpfs" (create as
|
||||
// tmpfs), and "anonymous" (create as anonymous volumes).
|
||||
@ -600,9 +602,15 @@ func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
|
||||
csc.Rootfs = arg
|
||||
// check if rootfs should use overlay
|
||||
lastColonIndex := strings.LastIndex(csc.Rootfs, ":")
|
||||
if lastColonIndex != -1 && lastColonIndex+1 < len(csc.Rootfs) && csc.Rootfs[lastColonIndex+1:] == "O" {
|
||||
if lastColonIndex != -1 {
|
||||
lastPart := csc.Rootfs[lastColonIndex+1:]
|
||||
if lastPart == "O" {
|
||||
csc.RootfsOverlay = true
|
||||
csc.Rootfs = csc.Rootfs[:lastColonIndex]
|
||||
} else if lastPart == "idmap" || strings.HasPrefix(lastPart, "idmap=") {
|
||||
csc.RootfsMapping = &lastPart
|
||||
csc.Rootfs = csc.Rootfs[:lastColonIndex]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
csc.Image = arg
|
||||
|
@ -7,19 +7,31 @@ import (
|
||||
)
|
||||
|
||||
func TestNewSpecGeneratorWithRootfs(t *testing.T) {
|
||||
idmap := "idmap"
|
||||
idmapMappings := "idmap=uids=1-1-2000"
|
||||
tests := []struct {
|
||||
rootfs string
|
||||
expectedRootfsOverlay bool
|
||||
expectedRootfs string
|
||||
expectedMapping *string
|
||||
}{
|
||||
{"/root/a:b:O", true, "/root/a:b"},
|
||||
{"/root/a:b/c:O", true, "/root/a:b/c"},
|
||||
{"/root/a:b/c:", false, "/root/a:b/c:"},
|
||||
{"/root/a/b", false, "/root/a/b"},
|
||||
{"/root/a:b:O", true, "/root/a:b", nil},
|
||||
{"/root/a:b/c:O", true, "/root/a:b/c", nil},
|
||||
{"/root/a:b/c:", false, "/root/a:b/c:", nil},
|
||||
{"/root/a/b", false, "/root/a/b", nil},
|
||||
{"/root/a:b/c:idmap", false, "/root/a:b/c", &idmap},
|
||||
{"/root/a:b/c:idmap=uids=1-1-2000", false, "/root/a:b/c", &idmapMappings},
|
||||
}
|
||||
for _, args := range tests {
|
||||
val := NewSpecGenerator(args.rootfs, true)
|
||||
|
||||
assert.Equal(t, val.RootfsOverlay, args.expectedRootfsOverlay)
|
||||
assert.Equal(t, val.Rootfs, args.expectedRootfs)
|
||||
if args.expectedMapping == nil {
|
||||
assert.Nil(t, val.RootfsMapping)
|
||||
} else {
|
||||
assert.NotNil(t, val.RootfsMapping)
|
||||
assert.Equal(t, *val.RootfsMapping, *args.expectedMapping)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1021,4 +1021,38 @@ EOF
|
||||
run_podman run --net=host --cgroupns=host --rm $IMAGE sh -c "grep ' / /sys/fs/cgroup ' /proc/self/mountinfo | tail -n 1 | grep '/sys/fs/cgroup ro'"
|
||||
}
|
||||
|
||||
@test "podman run - rootfs with idmapped mounts" {
|
||||
skip_if_rootless "idmapped mounts work only with root for now"
|
||||
|
||||
skip_if_remote "userns=auto is set on the server"
|
||||
|
||||
egrep -q "^containers:" /etc/subuid || skip "no IDs allocated for user 'containers'"
|
||||
|
||||
# check if the underlying file system supports idmapped mounts
|
||||
check_dir=$PODMAN_TMPDIR/idmap-check
|
||||
mkdir $check_dir
|
||||
run_podman '?' run --rm --uidmap=0:1000:10000 --rootfs $check_dir:idmap true
|
||||
if [[ "$output" == *"failed to create idmapped mount: invalid argument"* ]]; then
|
||||
skip "idmapped mounts not supported"
|
||||
fi
|
||||
|
||||
run_podman image mount $IMAGE
|
||||
src="$output"
|
||||
|
||||
# we cannot use idmap on top of overlay, so we need a copy
|
||||
romount=$PODMAN_TMPDIR/rootfs
|
||||
cp -ar "$src" "$romount"
|
||||
|
||||
run_podman image unmount $IMAGE
|
||||
|
||||
run_podman run --rm --uidmap=0:1000:10000 --rootfs $romount:idmap stat -c %u:%g /bin
|
||||
is "$output" "0:0"
|
||||
|
||||
run_podman run --uidmap=0:1000:10000 --rm --rootfs "$romount:idmap=uids=0-1001-10000;gids=0-1002-10000" stat -c %u:%g /bin
|
||||
is "$output" "1:2"
|
||||
|
||||
rm -rf $romount
|
||||
}
|
||||
|
||||
|
||||
# vim: filetype=sh
|
||||
|
Reference in New Issue
Block a user