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:
parent
cbb45a6d42
commit
2bb4c7cdde
|
@ -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
|
Note: On **SELinux** systems, the rootfs needs the correct label, which is by default
|
||||||
**unconfined_u:object_r:container_file_t:s0**.
|
**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"`
|
Rootfs string `json:"rootfs,omitempty"`
|
||||||
// RootfsOverlay tells if rootfs has to be mounted as an overlay
|
// RootfsOverlay tells if rootfs has to be mounted as an overlay
|
||||||
RootfsOverlay bool `json:"rootfs_overlay,omitempty"`
|
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.
|
// ShmDir is the path to be mounted on /dev/shm in container.
|
||||||
// If not set manually at creation time, Libpod will create a tmpfs
|
// 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
|
// 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/podman/v4/pkg/util"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
|
"github.com/containers/storage/pkg/idmap"
|
||||||
"github.com/containers/storage/pkg/idtools"
|
"github.com/containers/storage/pkg/idtools"
|
||||||
"github.com/containers/storage/pkg/lockfile"
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
"github.com/containers/storage/pkg/mount"
|
"github.com/containers/storage/pkg/mount"
|
||||||
|
@ -370,9 +371,6 @@ func (c *Container) syncContainer() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
|
func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
|
||||||
if c.config.Rootfs != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*dest = *from
|
*dest = *from
|
||||||
// If we are creating a container inside a pod, we always want to inherit the
|
// 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
|
// 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
|
// We need to mount the container before volumes - to ensure the copyup
|
||||||
// works properly.
|
// works properly.
|
||||||
mountPoint := c.config.Rootfs
|
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
|
// Check if overlay has to be created on top of Rootfs
|
||||||
if c.config.RootfsOverlay {
|
if c.config.RootfsOverlay {
|
||||||
overlayDest := c.runtime.GraphRoot()
|
overlayDest := c.runtime.GraphRoot()
|
||||||
|
@ -1795,6 +1818,11 @@ func (c *Container) cleanupStorage() error {
|
||||||
cleanupErr = err
|
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 {
|
for _, containerMount := range c.config.Mounts {
|
||||||
if err := c.unmountSHM(containerMount); err != nil {
|
if err := c.unmountSHM(containerMount); err != nil {
|
||||||
|
|
|
@ -74,7 +74,7 @@ func parseOptionIDs(option string) ([]idtools.IDMap, error) {
|
||||||
return ret, nil
|
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
|
uidMap := idMappings.UIDMap
|
||||||
gidMap := idMappings.GIDMap
|
gidMap := idMappings.GIDMap
|
||||||
if strings.HasPrefix(option, "idmap=") {
|
if strings.HasPrefix(option, "idmap=") {
|
||||||
|
@ -101,17 +101,33 @@ func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string) ([
|
||||||
uidMappings := make([]spec.LinuxIDMapping, len(uidMap))
|
uidMappings := make([]spec.LinuxIDMapping, len(uidMap))
|
||||||
gidMappings := make([]spec.LinuxIDMapping, len(gidMap))
|
gidMappings := make([]spec.LinuxIDMapping, len(gidMap))
|
||||||
for i, uidmap := range uidMap {
|
for i, uidmap := range uidMap {
|
||||||
uidMappings[i] = spec.LinuxIDMapping{
|
if invert {
|
||||||
HostID: uint32(uidmap.ContainerID),
|
uidMappings[i] = spec.LinuxIDMapping{
|
||||||
ContainerID: uint32(uidmap.HostID),
|
HostID: uint32(uidmap.ContainerID),
|
||||||
Size: uint32(uidmap.Size),
|
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 {
|
for i, gidmap := range gidMap {
|
||||||
gidMappings[i] = spec.LinuxIDMapping{
|
if invert {
|
||||||
HostID: uint32(gidmap.ContainerID),
|
gidMappings[i] = spec.LinuxIDMapping{
|
||||||
ContainerID: uint32(gidmap.HostID),
|
HostID: uint32(gidmap.ContainerID),
|
||||||
Size: uint32(gidmap.Size),
|
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
|
return uidMappings, gidMappings, nil
|
||||||
|
@ -288,7 +304,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
for _, o := range m.Options {
|
for _, o := range m.Options {
|
||||||
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestParseIDMapMountOption(t *testing.T) {
|
||||||
UIDMap: uidMap,
|
UIDMap: uidMap,
|
||||||
GIDMap: gidMap,
|
GIDMap: gidMap,
|
||||||
}
|
}
|
||||||
uids, gids, err := parseIDMapMountOption(options, "idmap")
|
uids, gids, err := parseIDMapMountOption(options, "idmap", true)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, len(uids), 1)
|
assert.Equal(t, len(uids), 1)
|
||||||
assert.Equal(t, len(gids), 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].HostID, uint32(0))
|
||||||
assert.Equal(t, gids[0].Size, uint32(10000))
|
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.Nil(t, err)
|
||||||
assert.Equal(t, len(uids), 2)
|
assert.Equal(t, len(uids), 2)
|
||||||
assert.Equal(t, len(gids), 1)
|
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].HostID, uint32(0))
|
||||||
assert.Equal(t, gids[0].Size, uint32(10))
|
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)
|
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)
|
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)
|
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)
|
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)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1334,7 +1334,7 @@ func WithCommand(command []string) CtrCreateOption {
|
||||||
|
|
||||||
// WithRootFS sets the rootfs for the container.
|
// WithRootFS sets the rootfs for the container.
|
||||||
// This creates a container from a directory on disk and not an image.
|
// 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 {
|
return func(ctr *Container) error {
|
||||||
if ctr.valid {
|
if ctr.valid {
|
||||||
return define.ErrCtrFinalized
|
return define.ErrCtrFinalized
|
||||||
|
@ -1344,6 +1344,7 @@ func WithRootFS(rootfs string, overlay bool) CtrCreateOption {
|
||||||
}
|
}
|
||||||
ctr.config.Rootfs = rootfs
|
ctr.config.Rootfs = rootfs
|
||||||
ctr.config.RootfsOverlay = overlay
|
ctr.config.RootfsOverlay = overlay
|
||||||
|
ctr.config.RootfsMapping = mapping
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Rootfs != "" {
|
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)
|
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))
|
options = append(options, libpod.WithShmSize(*s.ShmSize))
|
||||||
}
|
}
|
||||||
if s.Rootfs != "" {
|
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
|
// Default used if not overridden on command line
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,8 @@ type ContainerStorageConfig struct {
|
||||||
Rootfs string `json:"rootfs,omitempty"`
|
Rootfs string `json:"rootfs,omitempty"`
|
||||||
// RootfsOverlay tells if rootfs is actually an overlay on top of base path
|
// RootfsOverlay tells if rootfs is actually an overlay on top of base path
|
||||||
RootfsOverlay bool `json:"rootfs_overlay,omitempty"`
|
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.
|
// ImageVolumeMode indicates how image volumes will be created.
|
||||||
// Supported modes are "ignore" (do not create), "tmpfs" (create as
|
// Supported modes are "ignore" (do not create), "tmpfs" (create as
|
||||||
// tmpfs), and "anonymous" (create as anonymous volumes).
|
// tmpfs), and "anonymous" (create as anonymous volumes).
|
||||||
|
@ -600,9 +602,15 @@ func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
|
||||||
csc.Rootfs = arg
|
csc.Rootfs = arg
|
||||||
// check if rootfs should use overlay
|
// check if rootfs should use overlay
|
||||||
lastColonIndex := strings.LastIndex(csc.Rootfs, ":")
|
lastColonIndex := strings.LastIndex(csc.Rootfs, ":")
|
||||||
if lastColonIndex != -1 && lastColonIndex+1 < len(csc.Rootfs) && csc.Rootfs[lastColonIndex+1:] == "O" {
|
if lastColonIndex != -1 {
|
||||||
csc.RootfsOverlay = true
|
lastPart := csc.Rootfs[lastColonIndex+1:]
|
||||||
csc.Rootfs = csc.Rootfs[:lastColonIndex]
|
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 {
|
} else {
|
||||||
csc.Image = arg
|
csc.Image = arg
|
||||||
|
|
|
@ -7,19 +7,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewSpecGeneratorWithRootfs(t *testing.T) {
|
func TestNewSpecGeneratorWithRootfs(t *testing.T) {
|
||||||
|
idmap := "idmap"
|
||||||
|
idmapMappings := "idmap=uids=1-1-2000"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
rootfs string
|
rootfs string
|
||||||
expectedRootfsOverlay bool
|
expectedRootfsOverlay bool
|
||||||
expectedRootfs string
|
expectedRootfs string
|
||||||
|
expectedMapping *string
|
||||||
}{
|
}{
|
||||||
{"/root/a:b:O", true, "/root/a:b"},
|
{"/root/a:b:O", true, "/root/a:b", nil},
|
||||||
{"/root/a:b/c:O", true, "/root/a:b/c"},
|
{"/root/a:b/c:O", true, "/root/a:b/c", nil},
|
||||||
{"/root/a:b/c:", false, "/root/a:b/c:"},
|
{"/root/a:b/c:", false, "/root/a:b/c:", nil},
|
||||||
{"/root/a/b", false, "/root/a/b"},
|
{"/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 {
|
for _, args := range tests {
|
||||||
val := NewSpecGenerator(args.rootfs, true)
|
val := NewSpecGenerator(args.rootfs, true)
|
||||||
|
|
||||||
assert.Equal(t, val.RootfsOverlay, args.expectedRootfsOverlay)
|
assert.Equal(t, val.RootfsOverlay, args.expectedRootfsOverlay)
|
||||||
assert.Equal(t, val.Rootfs, args.expectedRootfs)
|
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'"
|
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
|
# vim: filetype=sh
|
||||||
|
|
Loading…
Reference in New Issue