Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

namespaces: allow configuring keep-id userns size #24882

Merged
merged 2 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/source/markdown/options/userns.container.md
giuseppe marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat

The option `--userns=keep-id` uses all the subuids and subgids of the user.
The option `--userns=nomap` uses all the subuids and subgids of the user except the user's own ID.
Using `--userns=auto` when starting new containers does not work as long as any containers exist that were started with `--userns=keep-id` or `--userns=nomap`.
Using `--userns=auto` when starting new containers does not work as long as any containers exist that were started with `--userns=nomap` or `--userns=keep-id` without limiting the user namespace size.

Valid `auto` options:

Expand All @@ -62,6 +62,7 @@ For details see **--uidmap**.

- *uid*=UID: override the UID inside the container that is used to map the current user to.
- *gid*=GID: override the GID inside the container that is used to map the current user to.
- *size*=SIZE: override the size of the configured user namespace. It is useful to not saturate all the available IDs. Not supported when running as root.

**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is not allowed for containers created by the root user.

Expand Down
11 changes: 10 additions & 1 deletion pkg/namespaces/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ const (
pastaType = "pasta"
)

// KeepIDUserNsOptions defines how to keepIDmatically create a user namespace.
// KeepIDUserNsOptions defines how to create a user namespace using keep-id.
type KeepIDUserNsOptions struct {
// UID is the target uid in the user namespace.
UID *uint32
// GID is the target uid in the user namespace.
GID *uint32
// MaxSize is the maximum size of the user namespace.
MaxSize *uint32
}

// CgroupMode represents cgroup mode in the container.
Expand Down Expand Up @@ -148,6 +150,13 @@ func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) {
}
v := uint32(s)
options.GID = &v
case "size":
s, err := strconv.ParseUint(val, 10, 32)
if err != nil {
return nil, err
}
v := uint32(s)
options.MaxSize = &v
default:
return nil, fmt.Errorf("unknown option specified: %q", opt)
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/specgen/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/namespaces"
"github.com/containers/podman/v5/pkg/rootless"
"github.com/containers/podman/v5/pkg/util"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/unshare"
Expand Down Expand Up @@ -56,7 +57,7 @@ const (
// Pasta indicates that a pasta network stack should be used.
// Only used with the network namespace, invalid otherwise.
Pasta NamespaceMode = "pasta"
// KeepId indicates a user namespace to keep the owner uid inside
// KeepID indicates a user namespace to keep the owner uid inside
// of the namespace itself.
// Only used with the user namespace, invalid otherwise.
KeepID NamespaceMode = "keep-id"
Expand Down Expand Up @@ -514,6 +515,9 @@ func SetupUserNS(idmappings *storageTypes.IDMappingOptions, userns Namespace, g
if err != nil {
return user, err
}
if opts.MaxSize != nil && !rootless.IsRootless() {
return user, fmt.Errorf("cannot set max size for user namespace when not running rootless")
}
Comment on lines +518 to +520
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? Could we not just limit the rootful size in the same way as done for rootless?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep-id with root is a no-op. It doesn't currently create a user namespace

mappings, uid, gid, err := util.GetKeepIDMapping(opts)
if err != nil {
return user, err
Expand Down
13 changes: 11 additions & 2 deletions pkg/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) {
return sig, nil
}

func getRootlessKeepIDMapping(uid, gid int, uids, gids []idtools.IDMap) (*stypes.IDMappingOptions, int, int, error) {
func getRootlessKeepIDMapping(uid, gid int, uids, gids []idtools.IDMap, maxSize int) (*stypes.IDMappingOptions, int, int, error) {
options := stypes.IDMappingOptions{
HostUIDMapping: false,
HostGIDMapping: false,
Expand All @@ -185,6 +185,11 @@ func getRootlessKeepIDMapping(uid, gid int, uids, gids []idtools.IDMap) (*stypes
for _, g := range gids {
maxGID += g.Size
}
if maxSize > 0 {
// If maxSize is set, we need to ensure that the mappings are within the available range
maxUID = min(maxUID, maxSize-1)
maxGID = min(maxGID, maxSize-1)
}

options.UIDMap, options.GIDMap = nil, nil

Expand Down Expand Up @@ -240,13 +245,17 @@ func GetKeepIDMapping(opts *namespaces.KeepIDUserNsOptions) (*stypes.IDMappingOp
if opts.GID != nil {
gid = int(*opts.GID)
}
maxSize := 0
if opts.MaxSize != nil {
maxSize = int(*opts.MaxSize)
}

uids, gids, err := rootless.GetConfiguredMappings(true)
if err != nil {
return nil, -1, -1, fmt.Errorf("cannot read mappings: %w", err)
}

return getRootlessKeepIDMapping(uid, gid, uids, gids)
return getRootlessKeepIDMapping(uid, gid, uids, gids, maxSize)
}

// GetNoMapMapping returns the mappings and the user to use when nomap is used
Expand Down
63 changes: 62 additions & 1 deletion pkg/util/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ func TestGetRootlessKeepIDMapping(t *testing.T) {
tests := []struct {
uid, gid int
uids, gids []idtools.IDMap
size int
expectedOptions *stypes.IDMappingOptions
expectedUID, expectedGID int
expectedError error
Expand Down Expand Up @@ -627,10 +628,70 @@ func TestGetRootlessKeepIDMapping(t *testing.T) {
expectedUID: 0,
expectedGID: 0,
},
{
uid: 0,
gid: 0,
uids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
gids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
expectedOptions: &stypes.IDMappingOptions{
HostUIDMapping: false,
HostGIDMapping: false,
UIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}, {ContainerID: 1, HostID: 1, Size: 1023}},
GIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}, {ContainerID: 1, HostID: 1, Size: 1023}},
},
expectedUID: 0,
expectedGID: 0,
size: 1024,
},
{
uid: 0,
gid: 0,
uids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
gids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
expectedOptions: &stypes.IDMappingOptions{
HostUIDMapping: false,
HostGIDMapping: false,
UIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}},
GIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}},
},
expectedUID: 0,
expectedGID: 0,
size: 1,
},
{
uid: 0,
gid: 0,
uids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
gids: []idtools.IDMap{{ContainerID: 0, HostID: 100000, Size: 65536}},
expectedOptions: &stypes.IDMappingOptions{
HostUIDMapping: false,
HostGIDMapping: false,
UIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}, {ContainerID: 1, HostID: 1, Size: 1}},
GIDMap: []idtools.IDMap{{ContainerID: 0, HostID: 0, Size: 1}, {ContainerID: 1, HostID: 1, Size: 1}},
},
expectedUID: 0,
expectedGID: 0,
size: 2,
},
{
uid: 1000,
gid: 1000,
uids: []idtools.IDMap{},
gids: []idtools.IDMap{},
expectedOptions: &stypes.IDMappingOptions{
HostUIDMapping: false,
HostGIDMapping: false,
UIDMap: []idtools.IDMap{{ContainerID: 1000, HostID: 0, Size: 1}},
GIDMap: []idtools.IDMap{{ContainerID: 1000, HostID: 0, Size: 1}},
},
expectedUID: 1000,
expectedGID: 1000,
size: 1000000,
},
}

for _, test := range tests {
options, uid, gid, err := getRootlessKeepIDMapping(test.uid, test.gid, test.uids, test.gids)
options, uid, gid, err := getRootlessKeepIDMapping(test.uid, test.gid, test.uids, test.gids, test.size)
assert.Nil(t, err)
assert.Equal(t, test.expectedOptions, options)
assert.Equal(t, test.expectedUID, uid)
Expand Down
12 changes: 12 additions & 0 deletions test/e2e/run_userns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ var _ = Describe("Podman UserNS support", func() {
Expect(session.OutputToString()).To(Equal("0"))
})

It("podman --userns=keep-id:size", func() {
session := podmanTest.Podman([]string{"run", "--userns=keep-id:size=10", ALPINE, "sh", "-c", "(awk 'BEGIN{SUM=0} {SUM += $3} END{print SUM}' < /proc/self/uid_map)"})
session.WaitWithDefaultTimeout()

if isRootless() {
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("10"))
} else {
Expect(session).Should(ExitWithError(125, "cannot set max size for user namespace when not running rootless"))
}
})

It("podman --userns=keep-id --user root:root", func() {
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "alpine", "id", "-u"})
session.WaitWithDefaultTimeout()
Expand Down
Loading