Merge pull request #16574 from alexlarsson/quadlet-usermap

quadlet: Rework uid/gid remapping
This commit is contained in:
OpenShift Merge Robot
2022-11-22 04:13:22 -05:00
committed by GitHub
17 changed files with 116 additions and 915 deletions

View File

@ -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) \

View File

@ -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`)

View File

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

View File

@ -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)

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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

View File

@ -1,6 +0,0 @@
## !assert-podman-args --uidmap
## !assert-podman-args --gidmap
[Container]
Image=localhost/imagename
RemapUsers=no

View File

@ -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

View File

@ -0,0 +1,5 @@
## assert-podman-args --userns=auto
[Container]
Image=localhost/imagename
RemapUsers=auto

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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