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"),