From 285d6c9ba0cc29f92dd3ab65ce71e50434b4bad6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 21 Nov 2022 17:12:37 +0100 Subject: [PATCH] quadlet: Rework uid/gid remapping Quadlet was doing some custom handling of uid/gid remapping, originating from pre --userns=auto support, including its own user for getting subuids which kinda conflicts with the "container" user used for that. This drops all the old support for id remapping in favour of a new set of keys that more directly map to the podman run options. We have essentially 3 modes now: ``` RemapUsers=manual RemapUid=0:10000:10 RemapUid=10:20000:10 RemapGid=0:10000:10 RemapGid=10:20000:10 ``` This maps to --uidmap and --gidmap options. ``` RemapUsers=auto ``` This maps to --userns=auto. But you can additionally specify RemapUid, RemapGid and RemapUidSize which gets applied as options to the --userns podman option. ``` RemapUsers=keep-id ``` This maps to --userns=keep-id and only works for user units. Signed-off-by: Alexander Larsson --- Makefile | 2 - docs/source/markdown/podman-systemd.unit.5.md | 63 ++--- pkg/systemd/quadlet/podmancmdline.go | 7 - pkg/systemd/quadlet/quadlet.go | 245 +++++------------ pkg/systemd/quadlet/ranges.go | 249 ------------------ pkg/systemd/quadlet/ranges_test.go | 242 ----------------- pkg/systemd/quadlet/subuids.go | 69 ----- podman.spec.rpkg | 13 +- test/e2e/quadlet/noremapuser.container | 6 - test/e2e/quadlet/noremapuser2.container | 28 -- test/e2e/quadlet/remap-auto.container | 5 + test/e2e/quadlet/remap-auto2.container | 10 + test/e2e/quadlet/remap-manual.container | 12 + test/e2e/quadlet/user-host.container | 24 -- test/e2e/quadlet/user-root1.container | 26 -- test/e2e/quadlet/user-root2.container | 22 -- test/e2e/quadlet_test.go | 8 +- 17 files changed, 116 insertions(+), 915 deletions(-) delete mode 100644 pkg/systemd/quadlet/ranges.go delete mode 100644 pkg/systemd/quadlet/ranges_test.go delete mode 100644 pkg/systemd/quadlet/subuids.go delete mode 100644 test/e2e/quadlet/noremapuser.container delete mode 100644 test/e2e/quadlet/noremapuser2.container create mode 100644 test/e2e/quadlet/remap-auto.container create mode 100644 test/e2e/quadlet/remap-auto2.container create mode 100644 test/e2e/quadlet/remap-manual.container delete mode 100644 test/e2e/quadlet/user-host.container delete mode 100644 test/e2e/quadlet/user-root1.container delete mode 100644 test/e2e/quadlet/user-root2.container diff --git a/Makefile b/Makefile index 4122dd7948..4eb341da76 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ HEAD ?= HEAD PROJECT := github.com/containers/podman GIT_BASE_BRANCH ?= origin/main LIBPOD_INSTANCE := libpod_dev -QUADLET_USER ?= quadlet PREFIX ?= /usr/local BINDIR ?= ${PREFIX}/bin LIBEXECDIR ?= ${PREFIX}/libexec @@ -112,7 +111,6 @@ LDFLAGS_PODMAN ?= \ -X $(LIBPOD)/config._installPrefix=$(PREFIX) \ -X $(LIBPOD)/config._etcDir=$(ETCDIR) \ -X github.com/containers/common/pkg/config.additionalHelperBinariesDir=$(HELPER_BINARIES_DIR)\ - -X $(PROJECT)/v4/pkg/quadlet.QuadletUserName=$(QUADLET_USER) \ $(EXTRA_LDFLAGS) LDFLAGS_PODMAN_STATIC ?= \ $(LDFLAGS_PODMAN) \ diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index b8e6a408e5..4f2b1a518c 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -102,22 +102,12 @@ default entry point of the container image is used. The format is the same as fo #### `User=` The (numeric) uid to run as inside the container. This does not need to match the uid on the host, -which can be set with `HostUser`, but if that is not specified, this uid is also used on the host. - -#### `HostUser=` - -The host uid (numeric or a username) to run the container as. If this differs from the uid in `User`, -then user namespaces are used to map the ids. If unspecified, this defaults to what was specified in `User`. +which can be modified with `RemapUsers`, but if that is not specified, this uid is also used on the host. #### `Group=` The (numeric) gid to run as inside the container. This does not need to match the gid on the host, -which can be set with `HostGroup`, but if that is not specified, this gid is also used on the host. - -#### `HostGroup=` - -The host gid (numeric or group name) to run the container as. If this differs from the gid in `Group`, -then user namespaces are used to map the ids. If unspecified, this defaults to what was specified in `Group`. +which can be modified with `RemapUsers`, but if that is not specified, this gid is also used on the host. #### `NoNewPrivileges=` (defaults to `yes`) @@ -159,44 +149,33 @@ If enabled, makes image read-only, with /var/tmp, /tmp and /run a tmpfs (unless Set the seccomp profile to use in the container. If unset, the default podman profile is used. Set to either the pathname of a json file, or `unconfined` to disable the seccomp filters. -#### `RemapUsers=` (defaults to `no`) +#### `RemapUsers=` -If this is enabled, then host user and group ids are remapped in the container, such that all the uids -starting at `RemapUidStart` (and gids starting at `RemapGidStart`) in the container are chosen from the -available host uids specified by `RemapUidRanges` (and `RemapGidRanges`). +If this is set, then host user and group ids are remapped in the container. It currently +supports values: `auto`, `manual` and `keep-id`. -#### `RemapUidStart=` (defaults to `1`) +In `manual` mode, the `RemapUid` and `RemapGid` options can define an +exact mapping of uids from host to container. You must specify these. -If `RemapUsers` is enabled, this is the first uid that is remapped, and all lower uids are mapped -to the equivalent host uid. This defaults to 1 so that the host root uid is in the container, because -this means a lot less file ownership remapping in the container image. +In `auto` mode mode, the subuids and subgids allocated to the `containers` user is used to allocate +host uids/gids to use for the container. By default this will try to estimate a count of the ids +to remap, but you can set RemapUidSize to use an explicit size. You can also Use `RemapUid` and +`RemapGid` key to force a particular host uid to be mapped to the container. -#### `RemapGidStart=` (defaults to `1`) +In `keep-id` mode, the running user is mapped to the same id in the container. This is supported +only on user systemd units. -If `RemapUsers` is enabled, this is the first gid that is remapped, and all lower gids are mapped -to the equivalent host gid. This defaults to 1 so that the host root gid is in the container, because -this means a lot less file ownership remapping in the container image. +#### `RemapUid=` -#### `RemapUidRanges=` +If `RemapUsers` is enabled, this specifies a uid mapping of the form `container_uid:from_uid:amount`, +which will map `amount` number of uids on the host starting at `from_uid` into the container, starting +at `container_uid`. -This specifies a comma-separated list of ranges (like `10000-20000,40000-50000`) of available host -uids to use to remap container uids in `RemapUsers`. Alternatively, it can be a username, which means -the available subuids of that user will be used. +#### `RemapGid=` -If not specified, the default ranges are chosen as the subuids of the `quadlet` user. - -#### `RemapGidRanges=` - -This specifies a comma-separated list of ranges (like `10000-20000,40000-50000`) of available host -gids to use to remap container gids in `RemapUsers`. Alternatively, it can be a username, which means -the available subgids of that user will be used. - -If not specified, the default ranges are chosen as the subgids of the `quadlet` user. - -#### `KeepId=` (defaults to `no`, only works for user units) - -If this is enabled, then the user uid will be mapped to itself in the container, otherwise it is -mapped to root. This is ignored for system units. +If `RemapUsers` is enabled, this specifies a gid mapping of the form `container_gid:from_gid:amount`, +which will map `amount` number of gids on the host starting at `from_gid` into the container, starting +at `container_gid`. #### `Notify=` (defaults to `no`) diff --git a/pkg/systemd/quadlet/podmancmdline.go b/pkg/systemd/quadlet/podmancmdline.go index adec5b2c23..ca1fd2f5c4 100644 --- a/pkg/systemd/quadlet/podmancmdline.go +++ b/pkg/systemd/quadlet/podmancmdline.go @@ -42,13 +42,6 @@ func (c *PodmanCmdline) addAnnotations(annotations map[string]string) { c.addKeys("--annotation", annotations) } -func (c *PodmanCmdline) addIDMap(argPrefix string, containerIDStart, hostIDStart, numIDs uint32) { - if numIDs != 0 { - c.add(argPrefix) - c.addf("%d:%d:%d", containerIDStart, hostIDStart, numIDs) - } -} - func NewPodmanCmdline(args ...string) *PodmanCmdline { c := &PodmanCmdline{ Args: make([]string, 0), diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index da7d908253..6e07fd578a 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -2,20 +2,12 @@ package quadlet import ( "fmt" - "math" - "os" "regexp" "strings" - "unicode" "github.com/containers/podman/v4/pkg/systemd/parser" ) -// Overwritten at build time: -var ( - QuadletUserName = "quadlet" // Name of user used to look up subuid/subgid for remap uids -) - const ( // Directory for global Quadlet files (sysadmin owned) UnitDirAdmin = "/etc/containers/systemd" @@ -30,12 +22,6 @@ const ( XContainerGroup = "X-Container" VolumeGroup = "Volume" XVolumeGroup = "X-Volume" - - // Fallbacks uid/gid ranges if the above username doesn't exist or has no subuids - FallbackUIDStart = 1879048192 - FallbackUIDLength = 165536 - FallbackGIDStart = 1879048192 - FallbackGIDLength = 165536 ) var validPortRange = regexp.MustCompile(`\d+(-\d+)?(/udp|/tcp)?$`) @@ -51,18 +37,14 @@ const ( KeyAddCapability = "AddCapability" KeyReadOnly = "ReadOnly" KeyRemapUsers = "RemapUsers" - KeyRemapUIDStart = "RemapUidStart" - KeyRemapGIDStart = "RemapGidStart" - KeyRemapUIDRanges = "RemapUidRanges" - KeyRemapGIDRanges = "RemapGidRanges" + KeyRemapUID = "RemapUid" + KeyRemapGID = "RemapGid" + KeyRemapUIDSize = "RemapUidSize" KeyNotify = "Notify" KeyExposeHostPort = "ExposeHostPort" KeyPublishPort = "PublishPort" - KeyKeepID = "KeepId" KeyUser = "User" KeyGroup = "Group" - KeyHostUser = "HostUser" - KeyHostGroup = "HostGroup" KeyVolume = "Volume" KeyPodmanArgs = "PodmanArgs" KeyLabel = "Label" @@ -86,18 +68,14 @@ var supportedContainerKeys = map[string]bool{ KeyAddCapability: true, KeyReadOnly: true, KeyRemapUsers: true, - KeyRemapUIDStart: true, - KeyRemapGIDStart: true, - KeyRemapUIDRanges: true, - KeyRemapGIDRanges: true, + KeyRemapUID: true, + KeyRemapGID: true, + KeyRemapUIDSize: true, KeyNotify: true, KeyExposeHostPort: true, KeyPublishPort: true, - KeyKeepID: true, KeyUser: true, KeyGroup: true, - KeyHostUser: true, - KeyHostGroup: true, KeyVolume: true, KeyPodmanArgs: true, KeyLabel: true, @@ -128,30 +106,6 @@ func replaceExtension(name string, extension string, extraPrefix string, extraSu return extraPrefix + baseName + extraSuffix + extension } -var defaultRemapUIDs, defaultRemapGIDs *Ranges - -func getDefaultRemapUids() *Ranges { - if defaultRemapUIDs == nil { - defaultRemapUIDs = lookupHostSubuid(QuadletUserName) - if defaultRemapUIDs == nil { - defaultRemapUIDs = - NewRanges(FallbackUIDStart, FallbackUIDLength) - } - } - return defaultRemapUIDs -} - -func getDefaultRemapGids() *Ranges { - if defaultRemapGIDs == nil { - defaultRemapGIDs = lookupHostSubgid(QuadletUserName) - if defaultRemapGIDs == nil { - defaultRemapGIDs = - NewRanges(FallbackGIDStart, FallbackGIDLength) - } - } - return defaultRemapGIDs -} - func isPortRange(port string) bool { return validPortRange.MatchString(port) } @@ -166,33 +120,6 @@ func checkForUnknownKeys(unit *parser.UnitFile, groupName string, supportedKeys return nil } -func lookupRanges(unit *parser.UnitFile, groupName string, key string, nameLookup func(string) *Ranges, defaultValue *Ranges) *Ranges { - v, ok := unit.Lookup(groupName, key) - if !ok { - if defaultValue != nil { - return defaultValue.Copy() - } - - return NewRangesEmpty() - } - - if len(v) == 0 { - return NewRangesEmpty() - } - - if !unicode.IsDigit(rune(v[0])) { - if nameLookup != nil { - r := nameLookup(v) - if r != nil { - return r - } - } - return NewRangesEmpty() - } - - return ParseRanges(v) -} - func splitPorts(ports string) []string { parts := make([]string, 0) @@ -222,59 +149,19 @@ func splitPorts(ports string) []string { return parts } -func addIDMaps(podman *PodmanCmdline, argPrefix string, containerID, hostID, remapStartID uint32, availableHostIDs *Ranges) { - if availableHostIDs == nil { - // Map everything by default - availableHostIDs = NewRangesEmpty() +func usernsOpts(kind string, opts []string) string { + var res strings.Builder + res.WriteString(kind) + if len(opts) > 0 { + res.WriteString(":") } - - // Map the first ids up to remapStartID to the host equivalent - unmappedIds := NewRanges(0, remapStartID) - - // The rest we want to map to availableHostIDs. Note that this - // overlaps unmappedIds, because below we may remove ranges from - // unmapped ids and we want to backfill those. - mappedIds := NewRanges(0, math.MaxUint32) - - // Always map specified uid to specified host_uid - podman.addIDMap(argPrefix, containerID, hostID, 1) - - // We no longer want to map this container id as its already mapped - mappedIds.Remove(containerID, 1) - unmappedIds.Remove(containerID, 1) - - // But also, we don't want to use the *host* id again, as we can only map it once - unmappedIds.Remove(hostID, 1) - availableHostIDs.Remove(hostID, 1) - - // Map unmapped ids to equivalent host range, and remove from mappedIds to avoid double-mapping - for _, r := range unmappedIds.Ranges { - start := r.Start - length := r.Length - - podman.addIDMap(argPrefix, start, start, length) - mappedIds.Remove(start, length) - availableHostIDs.Remove(start, length) - } - - for cIdx := 0; cIdx < len(mappedIds.Ranges) && len(availableHostIDs.Ranges) > 0; cIdx++ { - cRange := &mappedIds.Ranges[cIdx] - cStart := cRange.Start - cLength := cRange.Length - - for cLength > 0 && len(availableHostIDs.Ranges) > 0 { - hRange := &availableHostIDs.Ranges[0] - hStart := hRange.Start - hLength := hRange.Length - - nextLength := minUint32(hLength, cLength) - - podman.addIDMap(argPrefix, cStart, hStart, nextLength) - availableHostIDs.Remove(hStart, nextLength) - cStart += nextLength - cLength -= nextLength + for i, opt := range opts { + if i != 0 { + res.WriteString(",") } + res.WriteString(opt) } + return res.String() } // Convert a quadlet container file (unit file with a Container group) to a systemd @@ -451,66 +338,62 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile podman.add("--read-only-tmpfs=false") } - defaultContainerUID := uint32(0) - defaultContainerGID := uint32(0) + hasUser := container.HasKey(ContainerGroup, KeyUser) + hasGroup := container.HasKey(ContainerGroup, KeyGroup) + if hasUser || hasGroup { + uid := container.LookupUint32(ContainerGroup, KeyUser, 0) + gid := container.LookupUint32(ContainerGroup, KeyGroup, 0) - keepID := container.LookupBoolean(ContainerGroup, KeyKeepID, false) - if keepID { - if isUser { - defaultContainerUID = uint32(os.Getuid()) - defaultContainerGID = uint32(os.Getgid()) - podman.add("--userns", "keep-id") - } else { - return nil, fmt.Errorf("key 'KeepId' in '%s' unsupported for system units", container.Path) - } - } - - uid := container.LookupUint32(ContainerGroup, KeyUser, defaultContainerUID) - gid := container.LookupUint32(ContainerGroup, KeyGroup, defaultContainerGID) - - hostUID, err := container.LookupUID(ContainerGroup, KeyHostUser, uid) - if err != nil { - return nil, fmt.Errorf("key 'HostUser' invalid: %s", err) - } - - hostGID, err := container.LookupGID(ContainerGroup, KeyHostGroup, gid) - if err != nil { - return nil, fmt.Errorf("key 'HostGroup' invalid: %s", err) - } - - if uid != defaultContainerUID || gid != defaultContainerGID { podman.add("--user") - if gid == defaultContainerGID { - podman.addf("%d", uid) - } else { + if hasGroup { podman.addf("%d:%d", uid, gid) + } else { + podman.addf("%d", uid) } } - var remapUsers bool - if isUser { - remapUsers = false - } else { - remapUsers = container.LookupBoolean(ContainerGroup, KeyRemapUsers, false) - } + uidMaps := container.LookupAllStrv(ContainerGroup, KeyRemapUID) + gidMaps := container.LookupAllStrv(ContainerGroup, KeyRemapGID) - if !remapUsers { - // No remapping of users, although we still need maps if the - // main user/group is remapped, even if most ids map one-to-one. - if uid != hostUID { - addIDMaps(podman, "--uidmap", uid, hostUID, math.MaxUint32, nil) - } - if gid != hostGID { - addIDMaps(podman, "--gidmap", gid, hostGID, math.MaxUint32, nil) - } - } else { - uidRemapIDs := lookupRanges(container, ContainerGroup, KeyRemapUIDRanges, lookupHostSubuid, getDefaultRemapUids()) - gidRemapIDs := lookupRanges(container, ContainerGroup, KeyRemapGIDRanges, lookupHostSubgid, getDefaultRemapGids()) - remapUIDStart := container.LookupUint32(ContainerGroup, KeyRemapUIDStart, 1) - remapGIDStart := container.LookupUint32(ContainerGroup, KeyRemapGIDStart, 1) + remapUsers, ok := container.LookupLast(ContainerGroup, KeyRemapUsers) + if ok && remapUsers != "" { + switch remapUsers { + case "": + if len(uidMaps) > 0 { + return nil, fmt.Errorf("UidMap set without RemapUsers") + } + if len(gidMaps) > 0 { + return nil, fmt.Errorf("GidMap set without RemapUsers") + } + case "manual": + for _, uidMap := range uidMaps { + podman.addf("--uidmap=%s", uidMap) + } + for _, gidMap := range gidMaps { + podman.addf("--gidmap=%s", gidMap) + } + case "auto": + autoOpts := make([]string, 0) + for _, uidMap := range uidMaps { + autoOpts = append(autoOpts, "uidmapping="+uidMap) + } + for _, gidMap := range gidMaps { + autoOpts = append(autoOpts, "gidmapping="+gidMap) + } + uidSize := container.LookupUint32(ContainerGroup, KeyRemapUIDSize, 0) + if uidSize > 0 { + autoOpts = append(autoOpts, fmt.Sprintf("size=%v", uidSize)) + } - addIDMaps(podman, "--uidmap", uid, hostUID, remapUIDStart, uidRemapIDs) - addIDMaps(podman, "--gidmap", gid, hostGID, remapGIDStart, gidRemapIDs) + podman.addf("--userns=" + usernsOpts("auto", autoOpts)) + case "keep-id": + if !isUser { + return nil, fmt.Errorf("RemapUsers=keep-id is unsupported for system units") + } + podman.addf("--userns=keep-id") + default: + return nil, fmt.Errorf("unsupported RemapUsers option '%s'", remapUsers) + } } volumes := container.LookupAll(ContainerGroup, KeyVolume) diff --git a/pkg/systemd/quadlet/ranges.go b/pkg/systemd/quadlet/ranges.go deleted file mode 100644 index 01d850056d..0000000000 --- a/pkg/systemd/quadlet/ranges.go +++ /dev/null @@ -1,249 +0,0 @@ -package quadlet - -import ( - "math" - "strconv" - "strings" -) - -// The Ranges abstraction efficiently keeps track of a list of non-intersecting -// ranges of uint32. You can merge these and modify them (add/remove a range). -// The primary use of these is to manage Uid/Gid ranges for re-mapping - -func minUint32(x, y uint32) uint32 { - if x < y { - return x - } - return y -} - -func maxUint32(x, y uint32) uint32 { - if x > y { - return x - } - return y -} - -type Range struct { - Start uint32 - Length uint32 -} - -type Ranges struct { - Ranges []Range -} - -func (r *Ranges) Add(start, length uint32) { - // The maximum value we can store is UINT32_MAX-1, because if start - // is 0 and length is UINT32_MAX, then the first non-range item is - // 0+UINT32_MAX. So, we limit the start and length here so all - // elements in the ranges are in this area. - if start == math.MaxUint32 { - return - } - length = minUint32(length, math.MaxUint32-start) - - if length == 0 { - return - } - - for i := 0; i < len(r.Ranges); i++ { - current := &r.Ranges[i] - // Check if new range starts before current - if start < current.Start { - // Check if new range is completely before current - if start+length < current.Start { - // insert new range at i - newr := make([]Range, len(r.Ranges)+1) - copy(newr[0:i], r.Ranges[0:i]) - newr[i] = Range{Start: start, Length: length} - copy(newr[i+1:], r.Ranges[i:]) - r.Ranges = newr - - return // All done - } - - // ranges overlap, extend current backward to new start - toExtendLen := current.Start - start - current.Start -= toExtendLen - current.Length += toExtendLen - - // And drop the extended part from new range - start += toExtendLen - length -= toExtendLen - - if length == 0 { - return // That was all - } - - // Move on to next case - } - - if start >= current.Start && start < current.Start+current.Length { - // New range overlaps current - if start+length <= current.Start+current.Length { - return // All overlapped, we're done - } - - // New range extends past end of current - overlapLen := (current.Start + current.Length) - start - - // And drop the overlapped part from current range - start += overlapLen - length -= overlapLen - - // Move on to next case - } - - if start == current.Start+current.Length { - // We're extending current - current.Length += length - - // Might have to merge some old remaining ranges - for i+1 < len(r.Ranges) && - r.Ranges[i+1].Start <= current.Start+current.Length { - next := &r.Ranges[i+1] - - newEnd := maxUint32(current.Start+current.Length, next.Start+next.Length) - - current.Length = newEnd - current.Start - - copy(r.Ranges[i+1:], r.Ranges[i+2:]) - r.Ranges = r.Ranges[:len(r.Ranges)-1] - current = &r.Ranges[i] - } - - return // All done - } - } - - // New range remaining after last old range, append - if length > 0 { - r.Ranges = append(r.Ranges, Range{Start: start, Length: length}) - } -} - -func (r *Ranges) Remove(start, length uint32) { - // Limit ranges, see comment in Add - if start == math.MaxUint32 { - return - } - length = minUint32(length, math.MaxUint32-start) - - if length == 0 { - return - } - - for i := 0; i < len(r.Ranges); i++ { - current := &r.Ranges[i] - - end := start + length - currentStart := current.Start - currentEnd := current.Start + current.Length - - if end > currentStart && start < currentEnd { - remainingAtStart := uint32(0) - remainingAtEnd := uint32(0) - - if start > currentStart { - remainingAtStart = start - currentStart - } - - if end < currentEnd { - remainingAtEnd = currentEnd - end - } - - switch { - case remainingAtStart == 0 && remainingAtEnd == 0: - // Remove whole range - copy(r.Ranges[i:], r.Ranges[i+1:]) - r.Ranges = r.Ranges[:len(r.Ranges)-1] - i-- // undo loop iter - case remainingAtStart != 0 && remainingAtEnd != 0: - // Range is split - - newr := make([]Range, len(r.Ranges)+1) - copy(newr[0:i], r.Ranges[0:i]) - copy(newr[i+1:], r.Ranges[i:]) - newr[i].Start = currentStart - newr[i].Length = remainingAtStart - newr[i+1].Start = currentEnd - remainingAtEnd - newr[i+1].Length = remainingAtEnd - r.Ranges = newr - i++ /* double loop iter */ - case remainingAtStart != 0: - r.Ranges[i].Start = currentStart - r.Ranges[i].Length = remainingAtStart - default: /* remainingAtEnd != 0 */ - r.Ranges[i].Start = currentEnd - remainingAtEnd - r.Ranges[i].Length = remainingAtEnd - } - } - } -} - -func (r *Ranges) Merge(other *Ranges) { - for _, o := range other.Ranges { - r.Add(o.Start, o.Length) - } -} - -func (r *Ranges) Copy() *Ranges { - rs := make([]Range, len(r.Ranges)) - copy(rs, r.Ranges) - return &Ranges{Ranges: rs} -} - -func (r *Ranges) Length() uint32 { - length := uint32(0) - for _, rr := range r.Ranges { - length += rr.Length - } - return length -} - -func NewRangesEmpty() *Ranges { - return &Ranges{Ranges: nil} -} - -func NewRanges(start, length uint32) *Ranges { - r := NewRangesEmpty() - r.Add(start, length) - - return r -} - -func parseEndpoint(str string, defaultVal uint32) uint32 { - str = strings.TrimSpace(str) - intVal, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return defaultVal - } - - if intVal < 0 { - return uint32(0) - } - if intVal > math.MaxUint32 { - return uint32(math.MaxUint32) - } - return uint32(intVal) -} - -// Ranges are specified inclusive. I.e. 1-3 is 1,2,3 -func ParseRanges(str string) *Ranges { - r := NewRangesEmpty() - - for _, part := range strings.Split(str, ",") { - start, end, isPair := strings.Cut(part, "-") - startV := parseEndpoint(start, 0) - endV := startV - if isPair { - endV = parseEndpoint(end, math.MaxUint32) - } - if endV >= startV { - r.Add(startV, endV-startV+1) - } - } - - return r -} diff --git a/pkg/systemd/quadlet/ranges_test.go b/pkg/systemd/quadlet/ranges_test.go deleted file mode 100644 index b738c7dc42..0000000000 --- a/pkg/systemd/quadlet/ranges_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package quadlet - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRanges_Creation(t *testing.T) { - empty := NewRangesEmpty() - - assert.Equal(t, empty.Length(), uint32(0)) - - one := NewRanges(17, 42) - assert.Equal(t, one.Ranges[0].Start, uint32(17)) - assert.Equal(t, one.Ranges[0].Length, uint32(42)) -} - -func TestRanges_Single(t *testing.T) { - /* Before */ - r := NewRanges(10, 10) - - r.Add(0, 9) - - assert.Equal(t, len(r.Ranges), 2) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(9)) - assert.Equal(t, r.Ranges[1].Start, uint32(10)) - assert.Equal(t, r.Ranges[1].Length, uint32(10)) - - /* just before */ - r = NewRanges(10, 10) - - r.Add(0, 10) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(20)) - - /* before + inside */ - r = NewRanges(10, 10) - - r.Add(0, 19) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(20)) - - /* before + inside, whole */ - r = NewRanges(10, 10) - - r.Add(0, 20) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(20)) - - /* before + inside + after */ - r = NewRanges(10, 10) - - r.Add(0, 30) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(30)) - - /* just inside */ - r = NewRanges(10, 10) - - r.Add(10, 5) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - - /* inside */ - r = NewRanges(10, 10) - - r.Add(12, 5) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - - /* inside at end */ - r = NewRanges(10, 10) - - r.Add(15, 5) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - - /* inside + after */ - r = NewRanges(10, 10) - - r.Add(15, 10) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(15)) - - /* just after */ - r = NewRanges(10, 10) - - r.Add(20, 10) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(20)) - - /* after */ - r = NewRanges(10, 10) - - r.Add(21, 10) - - assert.Equal(t, len(r.Ranges), 2) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(21)) - assert.Equal(t, r.Ranges[1].Length, uint32(10)) -} - -func TestRanges_Multi(t *testing.T) { - base := NewRanges(10, 10) - base.Add(50, 10) - base.Add(30, 10) - - /* Test copy */ - r := base.Copy() - - assert.Equal(t, len(r.Ranges), 3) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(30)) - assert.Equal(t, r.Ranges[1].Length, uint32(10)) - assert.Equal(t, r.Ranges[2].Start, uint32(50)) - assert.Equal(t, r.Ranges[2].Length, uint32(10)) - - /* overlap everything */ - r = base.Copy() - - r.Add(0, 100) - - assert.Equal(t, len(r.Ranges), 1) - assert.Equal(t, r.Ranges[0].Start, uint32(0)) - assert.Equal(t, r.Ranges[0].Length, uint32(100)) - - /* overlap middle */ - r = base.Copy() - - r.Add(25, 10) - - assert.Equal(t, len(r.Ranges), 3) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(25)) - assert.Equal(t, r.Ranges[1].Length, uint32(15)) - assert.Equal(t, r.Ranges[2].Start, uint32(50)) - assert.Equal(t, r.Ranges[2].Length, uint32(10)) - - /* overlap last */ - r = base.Copy() - - r.Add(45, 10) - - assert.Equal(t, len(r.Ranges), 3) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(30)) - assert.Equal(t, r.Ranges[1].Length, uint32(10)) - assert.Equal(t, r.Ranges[2].Start, uint32(45)) - assert.Equal(t, r.Ranges[2].Length, uint32(15)) -} - -func TestRanges_Remove(t *testing.T) { - base := NewRanges(10, 10) - base.Add(50, 10) - base.Add(30, 10) - - /* overlap all */ - r := base.Copy() - - r.Remove(0, 100) - - assert.Equal(t, len(r.Ranges), 0) - - /* overlap middle 1 */ - - r = base.Copy() - - r.Remove(25, 20) - - assert.Equal(t, len(r.Ranges), 2) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(50)) - assert.Equal(t, r.Ranges[1].Length, uint32(10)) - - /* overlap middle 2 */ - - r = base.Copy() - - r.Remove(25, 10) - - assert.Equal(t, len(r.Ranges), 3) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(35)) - assert.Equal(t, r.Ranges[1].Length, uint32(5)) - assert.Equal(t, r.Ranges[2].Start, uint32(50)) - assert.Equal(t, r.Ranges[2].Length, uint32(10)) - - /* overlap middle 3 */ - r = base.Copy() - - r.Remove(35, 10) - - assert.Equal(t, len(r.Ranges), 3) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(30)) - assert.Equal(t, r.Ranges[1].Length, uint32(5)) - assert.Equal(t, r.Ranges[2].Start, uint32(50)) - assert.Equal(t, r.Ranges[2].Length, uint32(10)) - - /* overlap middle 4 */ - - r = base.Copy() - - r.Remove(34, 2) - - assert.Equal(t, len(r.Ranges), 4) - assert.Equal(t, r.Ranges[0].Start, uint32(10)) - assert.Equal(t, r.Ranges[0].Length, uint32(10)) - assert.Equal(t, r.Ranges[1].Start, uint32(30)) - assert.Equal(t, r.Ranges[1].Length, uint32(4)) - assert.Equal(t, r.Ranges[2].Start, uint32(36)) - assert.Equal(t, r.Ranges[2].Length, uint32(4)) - assert.Equal(t, r.Ranges[3].Start, uint32(50)) - assert.Equal(t, r.Ranges[3].Length, uint32(10)) -} diff --git a/pkg/systemd/quadlet/subuids.go b/pkg/systemd/quadlet/subuids.go deleted file mode 100644 index 6701605472..0000000000 --- a/pkg/systemd/quadlet/subuids.go +++ /dev/null @@ -1,69 +0,0 @@ -package quadlet - -import ( - "os" - "strconv" - "strings" -) - -// Code to look up subuid/subguid allocations for a user in /etc/subuid and /etc/subgid - -func lookupHostSubid(name string, file string, cache *[]string) *Ranges { - ranges := NewRangesEmpty() - - if len(*cache) == 0 { - data, e := os.ReadFile(file) - if e != nil { - *cache = make([]string, 0) - } else { - *cache = strings.Split(string(data), "\n") - } - for i := range *cache { - (*cache)[i] = strings.TrimSpace((*cache)[i]) - } - - // If file had no lines, add an empty line so the above cache created check works - if len(*cache) == 0 { - *cache = append(*cache, "") - } - } - - for _, line := range *cache { - if strings.HasPrefix(line, name) && - len(line) > len(name)+1 && line[len(name)] == ':' { - parts := strings.SplitN(line, ":", 3) - - if len(parts) != 3 { - continue - } - - start, err := strconv.ParseUint(parts[1], 10, 32) - if err != nil { - continue - } - - len, err := strconv.ParseUint(parts[1], 10, 32) - if err != nil { - continue - } - - if len > 0 { - ranges.Add(uint32(start), uint32(len)) - } - - break - } - } - - return ranges -} - -var subuidCache, subgidCache []string - -func lookupHostSubuid(userName string) *Ranges { - return lookupHostSubid(userName, "/etc/subuid", &subuidCache) -} - -func lookupHostSubgid(userName string) *Ranges { - return lookupHostSubid(userName, "/etc/subgid", &subgidCache) -} diff --git a/podman.spec.rpkg b/podman.spec.rpkg index 449450b006..882333dda5 100644 --- a/podman.spec.rpkg +++ b/podman.spec.rpkg @@ -193,7 +193,7 @@ export BUILDTAGS="$BASEBUILDTAGS exclude_graphdriver_btrfs btrfs_noversion remot %gobuild -o bin/%{name}-remote ./cmd/%{name} # build quadlet -export BUILDTAGS="$BASEBUILDTAGS $(hack/btrfs_installed_tag.sh) $(hack/btrfs_tag.sh) -X $(PROJECT)/v4/pkg/quadlet.QuadletUserName=quadlet" +export BUILDTAGS="$BASEBUILDTAGS $(hack/btrfs_installed_tag.sh) $(hack/btrfs_tag.sh)" %gobuild -o bin/quadlet ./cmd/quadlet make docs docker-docs @@ -221,17 +221,6 @@ for file in `find %{buildroot}%{_mandir}/man[15] -type f | sed "s,%{buildroot},, echo "$file*" >> podman.file-list done -%pre quadlet -# We create a quadlet user so that we can get subuids and subgids allocated. -# It really is a system user, but Unfortunately useradd doesn't create subuids -# for system users, so we manually make it system-like and start at a higher -# min uid to avoid conflicts with common uid nrs around 1000 -getent passwd quadlet >/dev/null || \ - useradd -M -U -K SUB_UID_COUNT=65536 -K UID_MIN=50000 \ - -s /sbin/nologin -d /nonexisting \ - -c "User for quadlet" quadlet -exit 0 - # This lists all the files that are included in the rpm package and that # are going to be installed into target system where the rpm is installed. %files -f %{name}.file-list diff --git a/test/e2e/quadlet/noremapuser.container b/test/e2e/quadlet/noremapuser.container deleted file mode 100644 index f836eac11d..0000000000 --- a/test/e2e/quadlet/noremapuser.container +++ /dev/null @@ -1,6 +0,0 @@ -## !assert-podman-args --uidmap -## !assert-podman-args --gidmap - -[Container] -Image=localhost/imagename -RemapUsers=no diff --git a/test/e2e/quadlet/noremapuser2.container b/test/e2e/quadlet/noremapuser2.container deleted file mode 100644 index 13c526ab57..0000000000 --- a/test/e2e/quadlet/noremapuser2.container +++ /dev/null @@ -1,28 +0,0 @@ -# This is an non-user-remapped container, but the user is mapped (uid -# 1000 in container is uid 90 on host). This means the result should -# map those particular ids to each other, but map all other container -# ids to the same as the host. - -# There is some additional complexity, as the host uid (90) that the -# container uid is mapped to can't also be mapped to itself, as ids -# can only be mapped once, so it has to be unmapped. - -## assert-podman-args --user 1000:1001 - -## assert-podman-args --uidmap 0:0:90 -## assert-podman-args --uidmap 91:91:909 -## assert-podman-args --uidmap 1000:90:1 -## assert-podman-args --uidmap 1001:1001:4294966294 - -## assert-podman-args --gidmap 0:0:91 -## assert-podman-args --gidmap 92:92:909 -## assert-podman-args --gidmap 1001:91:1 -## assert-podman-args --gidmap 1002:1002:4294966293 - -[Container] -Image=localhost/imagename -RemapUsers=no -User=1000 -Group=1001 -HostUser=90 -HostGroup=91 diff --git a/test/e2e/quadlet/remap-auto.container b/test/e2e/quadlet/remap-auto.container new file mode 100644 index 0000000000..67ef4ee33b --- /dev/null +++ b/test/e2e/quadlet/remap-auto.container @@ -0,0 +1,5 @@ +## assert-podman-args --userns=auto + +[Container] +Image=localhost/imagename +RemapUsers=auto diff --git a/test/e2e/quadlet/remap-auto2.container b/test/e2e/quadlet/remap-auto2.container new file mode 100644 index 0000000000..8abe826d59 --- /dev/null +++ b/test/e2e/quadlet/remap-auto2.container @@ -0,0 +1,10 @@ +## assert-podman-args "--userns=auto:uidmapping=0:10000:10,uidmapping=10:20000:10,gidmapping=0:10000:10,gidmapping=10:20000:10,size=20" + +[Container] +Image=localhost/imagename +RemapUsers=auto +RemapUid=0:10000:10 +RemapUid=10:20000:10 +RemapGid=0:10000:10 +RemapGid=10:20000:10 +RemapUidSize=20 diff --git a/test/e2e/quadlet/remap-manual.container b/test/e2e/quadlet/remap-manual.container new file mode 100644 index 0000000000..e36b460083 --- /dev/null +++ b/test/e2e/quadlet/remap-manual.container @@ -0,0 +1,12 @@ +## assert-podman-args "--uidmap=0:10000:10" +## assert-podman-args "--uidmap=10:20000:10" +## assert-podman-args "--gidmap=0:10000:10" +## assert-podman-args "--gidmap=10:20000:10" + +[Container] +Image=localhost/imagename +RemapUsers=manual +RemapUid=0:10000:10 +RemapUid=10:20000:10 +RemapGid=0:10000:10 +RemapGid=10:20000:10 diff --git a/test/e2e/quadlet/user-host.container b/test/e2e/quadlet/user-host.container deleted file mode 100644 index 27a7d301bd..0000000000 --- a/test/e2e/quadlet/user-host.container +++ /dev/null @@ -1,24 +0,0 @@ -## assert-podman-args --user 1000:1001 - -## assert-podman-args --uidmap 0:0:1 -## assert-podman-args --uidmap 1:100000:999 -## assert-podman-args --uidmap 1000:900:1 -## assert-podman-args --uidmap 1001:100999:99001 - -## assert-podman-args --gidmap 0:0:1 -## assert-podman-args --gidmap 1:100000:1000 -## assert-podman-args --gidmap 1001:901:1 -## assert-podman-args --gidmap 1002:101000:99000 - -[Container] -Image=localhost/imagename -User=1000 -HostUser=900 -Group=1001 -HostGroup=901 - -RemapUsers=yes - -# Set this to get well-known valuse for the checks -RemapUidRanges=100000-199999 -RemapGidRanges=100000-199999 diff --git a/test/e2e/quadlet/user-root1.container b/test/e2e/quadlet/user-root1.container deleted file mode 100644 index ff72bd887f..0000000000 --- a/test/e2e/quadlet/user-root1.container +++ /dev/null @@ -1,26 +0,0 @@ -## assert-podman-args --user 1000:1001 - -## assert-podman-args --uidmap 0:100000:1000 -## assert-podman-args --uidmap 1000:0:1 -## assert-podman-args --uidmap 1001:101000:99000 -## !assert-podman-args --uidmap 0:0:1 - -## assert-podman-args --gidmap 0:100000:1001 -## assert-podman-args --gidmap 1001:0:1 -## assert-podman-args --gidmap 1002:101001:98999 -## !assert-podman-args --gidmap 0:0:1 - -# Map container uid 1000 to host root -# This means container root must map to something else - -[Container] -Image=localhost/imagename -User=1000 -# Also test name parsing -HostUser=root -Group=1001 -HostGroup=0 -RemapUsers=yes -# Set this to get well-known valuse for the checks -RemapUidRanges=100000-199999 -RemapGidRanges=100000-199999 diff --git a/test/e2e/quadlet/user-root2.container b/test/e2e/quadlet/user-root2.container deleted file mode 100644 index 0791f8651e..0000000000 --- a/test/e2e/quadlet/user-root2.container +++ /dev/null @@ -1,22 +0,0 @@ -# No need for --user 0:0, it is the default -## !assert-podman-args --user - -## assert-podman-args --uidmap 0:0:1 -## assert-podman-args --gidmap 0:0:1 - -## assert-podman-args --uidmap 1:100000:100000 -## assert-podman-args --gidmap 1:100000:100000 - -# Map container uid root to host root - -[Container] -Image=localhost/imagename -User=0 -# Also test name parsing -HostUser=root -Group=0 -HostGroup=0 -RemapUsers=yes -# Set this to get well-known valuse for the checks -RemapUidRanges=100000-199999 -RemapGidRanges=100000-199999 diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index 8e1f5b203a..e7f377db67 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -280,8 +280,6 @@ var _ = Describe("quadlet system generator", func() { Entry("name.container", "name.container"), Entry("network.container", "network.container"), Entry("noimage.container", "noimage.container"), - Entry("noremapuser2.container", "noremapuser2.container"), - Entry("noremapuser.container", "noremapuser.container"), Entry("notify.container", "notify.container"), Entry("other-sections.container", "other-sections.container"), Entry("podmanargs.container", "podmanargs.container"), @@ -294,9 +292,9 @@ var _ = Describe("quadlet system generator", func() { Entry("shortname.container", "shortname.container"), Entry("timezone.container", "timezone.container"), Entry("user.container", "user.container"), - Entry("user-host.container", "user-host.container"), - Entry("user-root1.container", "user-root1.container"), - Entry("user-root2.container", "user-root2.container"), + Entry("remap-manual.container", "remap-manual.container"), + Entry("remap-auto.container", "remap-auto.container"), + Entry("remap-auto2.container", "remap-auto2.container"), Entry("volume.container", "volume.container"), Entry("basic.volume", "basic.volume"),