mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Merge pull request #20827 from kaivol/userns-auto-intermediate-id-lookup
Support lookup of intermediate ID for uidmapping and gidmapping in `--userns=auto`
This commit is contained in:
@ -48,6 +48,10 @@ Using `--userns=auto` when starting new containers does not work as long as any
|
|||||||
- *size*=_SIZE_: to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` estimates a size for the user namespace.
|
- *size*=_SIZE_: to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` estimates a size for the user namespace.
|
||||||
- *uidmapping*=_CONTAINER\_UID:HOST\_UID:SIZE_: to force a UID mapping to be present in the user namespace.
|
- *uidmapping*=_CONTAINER\_UID:HOST\_UID:SIZE_: to force a UID mapping to be present in the user namespace.
|
||||||
|
|
||||||
|
The host UID and GID in *gidmapping* and *uidmapping* can optionally be prefixed with the `@` symbol.
|
||||||
|
In this case, podman will look up the intermediate ID corresponding to host ID and it will map the found intermediate ID to the container id.
|
||||||
|
For details see **--uidmap**.
|
||||||
|
|
||||||
**container:**_id_: join the user namespace of the specified container.
|
**container:**_id_: join the user namespace of the specified container.
|
||||||
|
|
||||||
**host** or **""** (empty string): run in the user namespace of the caller. The processes running in the container have the same privileges on the host as any other process launched by the calling user.
|
**host** or **""** (empty string): run in the user namespace of the caller. The processes running in the container have the same privileges on the host as any other process launched by the calling user.
|
||||||
|
@ -282,7 +282,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
|
|||||||
options.HostUIDMapping = false
|
options.HostUIDMapping = false
|
||||||
options.HostGIDMapping = false
|
options.HostGIDMapping = false
|
||||||
options.AutoUserNs = true
|
options.AutoUserNs = true
|
||||||
opts, err := mode.GetAutoOptions()
|
opts, err := util.GetAutoOptions(mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/storage/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -122,48 +120,6 @@ func (n UsernsMode) IsDefaultValue() bool {
|
|||||||
return n == "" || n == defaultType
|
return n == "" || n == defaultType
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up
|
|
||||||
// a user namespace.
|
|
||||||
func (n UsernsMode) GetAutoOptions() (*types.AutoUserNsOptions, error) {
|
|
||||||
parts := strings.SplitN(string(n), ":", 2)
|
|
||||||
if parts[0] != "auto" {
|
|
||||||
return nil, fmt.Errorf("wrong user namespace mode")
|
|
||||||
}
|
|
||||||
options := types.AutoUserNsOptions{}
|
|
||||||
if len(parts) == 1 {
|
|
||||||
return &options, nil
|
|
||||||
}
|
|
||||||
for _, o := range strings.Split(parts[1], ",") {
|
|
||||||
v := strings.SplitN(o, "=", 2)
|
|
||||||
if len(v) != 2 {
|
|
||||||
return nil, fmt.Errorf("invalid option specified: %q", o)
|
|
||||||
}
|
|
||||||
switch v[0] {
|
|
||||||
case "size":
|
|
||||||
s, err := strconv.ParseUint(v[1], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
options.Size = uint32(s)
|
|
||||||
case "uidmapping":
|
|
||||||
mapping, err := types.ParseIDMapping([]string{v[1]}, nil, "", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping.UIDMap...)
|
|
||||||
case "gidmapping":
|
|
||||||
mapping, err := types.ParseIDMapping(nil, []string{v[1]}, "", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping.GIDMap...)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown option specified: %q", v[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &options, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up
|
// GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up
|
||||||
// a user namespace.
|
// a user namespace.
|
||||||
func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) {
|
func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) {
|
||||||
|
@ -830,6 +830,162 @@ func sortAndMergeConsecutiveMappings(idmap []idtools.IDMap) (finalIDMap []idtool
|
|||||||
return finalIDMap
|
return finalIDMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extension of idTools.parseAutoTriple that parses idmap triples.
|
||||||
|
// The triple should be a length 3 string array, containing:
|
||||||
|
// - Flags and ContainerID
|
||||||
|
// - HostID
|
||||||
|
// - Size
|
||||||
|
//
|
||||||
|
// parseAutoTriple returns the parsed mapping and any possible error.
|
||||||
|
// If the error is not-nil, the mapping is not well-defined.
|
||||||
|
//
|
||||||
|
// idTools.parseAutoTriple is extended here with the following enhancements:
|
||||||
|
//
|
||||||
|
// HostID @ syntax:
|
||||||
|
// =================
|
||||||
|
// HostID may use the "@" syntax: The "101001:@1001:1" mapping
|
||||||
|
// means "take the 1001 id from the parent namespace and map it to 101001"
|
||||||
|
func parseAutoTriple(spec []string, parentMapping []ruser.IDMap, mapSetting string) (mappings []idtools.IDMap, err error) {
|
||||||
|
if len(spec[0]) == 0 {
|
||||||
|
return mappings, fmt.Errorf("invalid empty container id at %s map: %v", mapSetting, spec)
|
||||||
|
}
|
||||||
|
var cids, hids, sizes []uint64
|
||||||
|
var cid, hid uint64
|
||||||
|
var hidIsParent bool
|
||||||
|
// Parse the container ID, which must be an integer:
|
||||||
|
cid, err = strconv.ParseUint(spec[0][0:], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return mappings, fmt.Errorf("parsing id map value %q: %w", spec[0], err)
|
||||||
|
}
|
||||||
|
// Parse the host id, which may be integer or @<integer>
|
||||||
|
if len(spec[1]) == 0 {
|
||||||
|
return mappings, fmt.Errorf("invalid empty host id at %s map: %v", mapSetting, spec)
|
||||||
|
}
|
||||||
|
if spec[1][0] != '@' {
|
||||||
|
hidIsParent = false
|
||||||
|
hid, err = strconv.ParseUint(spec[1], 10, 32)
|
||||||
|
} else {
|
||||||
|
// Parse @<id>, where <id> is an integer corresponding to the parent mapping
|
||||||
|
hidIsParent = true
|
||||||
|
hid, err = strconv.ParseUint(spec[1][1:], 10, 32)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return mappings, fmt.Errorf("parsing id map value %q: %w", spec[1], err)
|
||||||
|
}
|
||||||
|
// Parse the size of the mapping, which must be an integer
|
||||||
|
sz, err := strconv.ParseUint(spec[2], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return mappings, fmt.Errorf("parsing id map value %q: %w", spec[2], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hidIsParent {
|
||||||
|
for i := uint64(0); i < sz; i++ {
|
||||||
|
cids = append(cids, cid+i)
|
||||||
|
mappedID, err := mapIDwithMapping(hid+i, parentMapping, mapSetting)
|
||||||
|
if err != nil {
|
||||||
|
return mappings, err
|
||||||
|
}
|
||||||
|
hids = append(hids, mappedID)
|
||||||
|
sizes = append(sizes, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cids = []uint64{cid}
|
||||||
|
hids = []uint64{hid}
|
||||||
|
sizes = []uint64{sz}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid possible integer overflow on 32bit builds
|
||||||
|
if bits.UintSize == 32 {
|
||||||
|
for i := range cids {
|
||||||
|
if cids[i] > math.MaxInt32 || hids[i] > math.MaxInt32 || sizes[i] > math.MaxInt32 {
|
||||||
|
return mappings, fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range cids {
|
||||||
|
mappings = append(mappings, idtools.IDMap{
|
||||||
|
ContainerID: int(cids[i]),
|
||||||
|
HostID: int(hids[i]),
|
||||||
|
Size: int(sizes[i]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mappings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension of idTools.ParseIDMap that parses idmap triples from string.
|
||||||
|
// This extension accepts additional flags that control how the mapping is done
|
||||||
|
func parseAutoIDMap(mapSpec string, mapSetting string, parentMapping []ruser.IDMap) (idmap []idtools.IDMap, err error) {
|
||||||
|
stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"uint32:[@]uint32[:uint32]\"] : %q", mapSetting, mapSpec)
|
||||||
|
idSpec := strings.Split(mapSpec, ":")
|
||||||
|
// if it's a length-2 list assume the size is 1:
|
||||||
|
if len(idSpec) == 2 {
|
||||||
|
idSpec = append(idSpec, "1")
|
||||||
|
}
|
||||||
|
if len(idSpec) != 3 {
|
||||||
|
return nil, stdErr
|
||||||
|
}
|
||||||
|
// Parse this mapping:
|
||||||
|
mappings, err := parseAutoTriple(idSpec, parentMapping, mapSetting)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idmap = sortAndMergeConsecutiveMappings(mappings)
|
||||||
|
return idmap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up
|
||||||
|
// a user namespace.
|
||||||
|
func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) {
|
||||||
|
parts := strings.SplitN(string(n), ":", 2)
|
||||||
|
if parts[0] != "auto" {
|
||||||
|
return nil, fmt.Errorf("wrong user namespace mode")
|
||||||
|
}
|
||||||
|
options := stypes.AutoUserNsOptions{}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return &options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUIDMap, parentGIDMap, err := rootless.GetAvailableIDMaps()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// The kernel-provided files only exist if user namespaces are supported
|
||||||
|
logrus.Debugf("User or group ID mappings not available: %s", err)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range strings.Split(parts[1], ",") {
|
||||||
|
v := strings.SplitN(o, "=", 2)
|
||||||
|
if len(v) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid option specified: %q", o)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case "size":
|
||||||
|
s, err := strconv.ParseUint(v[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
options.Size = uint32(s)
|
||||||
|
case "uidmapping":
|
||||||
|
mapping, err := parseAutoIDMap(v[1], "UID", parentUIDMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...)
|
||||||
|
case "gidmapping":
|
||||||
|
mapping, err := parseAutoIDMap(v[1], "GID", parentGIDMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown option specified: %q", v[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &options, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
|
// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
|
||||||
func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) {
|
func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) {
|
||||||
options := stypes.IDMappingOptions{
|
options := stypes.IDMappingOptions{
|
||||||
@ -842,7 +998,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
|
|||||||
options.HostUIDMapping = false
|
options.HostUIDMapping = false
|
||||||
options.HostGIDMapping = false
|
options.HostGIDMapping = false
|
||||||
options.AutoUserNs = true
|
options.AutoUserNs = true
|
||||||
opts, err := mode.GetAutoOptions()
|
opts, err := GetAutoOptions(mode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -391,6 +391,42 @@ func TestParseIDMapUserGroupFlags(t *testing.T) {
|
|||||||
assert.Equal(t, expectedResultGroup, result)
|
assert.Equal(t, expectedResultGroup, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAutoIDMap(t *testing.T) {
|
||||||
|
result, err := parseAutoIDMap("3:4:5", "UID", []ruser.IDMap{})
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
assert.Equal(t, result, []idtools.IDMap{
|
||||||
|
{
|
||||||
|
ContainerID: 3,
|
||||||
|
HostID: 4,
|
||||||
|
Size: 5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAutoIDMapRelative(t *testing.T) {
|
||||||
|
parentMapping := []ruser.IDMap{
|
||||||
|
{
|
||||||
|
ID: 0,
|
||||||
|
ParentID: 1000,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
ParentID: 100000,
|
||||||
|
Count: 65536,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result, err := parseAutoIDMap("100:@100000:1", "UID", parentMapping)
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
assert.Equal(t, result, []idtools.IDMap{
|
||||||
|
{
|
||||||
|
ContainerID: 100,
|
||||||
|
HostID: 1,
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestFillIDMap(t *testing.T) {
|
func TestFillIDMap(t *testing.T) {
|
||||||
availableRanges := [][2]int{{0, 10}, {10000, 20000}}
|
availableRanges := [][2]int{{0, 10}, {10000, 20000}}
|
||||||
idmap := []idtools.IDMap{
|
idmap := []idtools.IDMap{
|
||||||
|
@ -147,3 +147,12 @@ EOF
|
|||||||
is "${output}" "$user" "Container should run as the current user"
|
is "${output}" "$user" "Container should run as the current user"
|
||||||
run_podman rmi -f $(pause_image)
|
run_podman rmi -f $(pause_image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "podman userns=auto with id mapping" {
|
||||||
|
skip_if_not_rootless
|
||||||
|
run_podman unshare awk '{if(NR == 2){print $2}}' /proc/self/uid_map
|
||||||
|
first_id=$output
|
||||||
|
mapping=1:@$first_id:1
|
||||||
|
run_podman run --rm --userns=auto:uidmapping=$mapping $IMAGE awk '{if($1 == 1){print $2}}' /proc/self/uid_map
|
||||||
|
assert "$output" == 1
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user