diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index a18bfa23a3..38e8d48a54 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -159,6 +159,7 @@ Valid options for `[Container]` are listed below: | EnvironmentHost=true | --env-host | | Exec=/usr/bin/command | Command after image specification - /usr/bin/command | | ExposeHostPort=50-59 | --expose 50-59 | +| GIDMap=0:10000:10 | --gidmap=0:10000:10 | | Group=1234 | --user UID:1234 | | GlobalArgs=--log-level=debug | --log-level=debug | | HealthCmd="/usr/bin/command" | --health-cmd="/usr/bin/command" | @@ -197,9 +198,12 @@ Valid options for `[Container]` are listed below: | SecurityLabelNested=true | --security-opt label=nested | | SecurityLabelType=spc_t | --security-opt label=type:spc_t | | ShmSize=100m | --shm-size=100m | +| SubGIDMap=gtest | --subgidname=gtest | +| SubUIDMap=utest | --subuidname=utest | | Sysctl=name=value | --sysctl=name=value | | Timezone=local | --tz local | | Tmpfs=/work | --tmpfs /work | +| UIDMap=0:10000:10 | --uidmap=0:10000:10 | | Ulimit=nofile:1000:10000 | --ulimit nofile:1000:10000 | | User=bin | --user bin | | UserNS=keep-id:uid=200,gid=210 | --userns keep-id:uid=200,gid=210 | @@ -315,6 +319,13 @@ to the Podman `--expose` option. This key can be listed multiple times. +### `GIDMap=` + +Run the container in a new user namespace using the supplied GID mapping. +Equivalent to the Podman `--gidmap` option. + +This key can be listed multiple times. + ### `GlobalArgs=` This key contains a list of arguments passed directly between `podman` and `run` @@ -569,6 +580,16 @@ Size of /dev/shm. This is equivalent to the Podman `--shm-size` option and generally has the form `number[unit]` +### `SubGIDMap=` + +Run the container in a new user namespace using the map with name in the /etc/subgid file. +Equivalent to the Podman `--subgidname` option. + +### `SubUIDMap=` + +Run the container in a new user namespace using the map with name in the /etc/subuid file. +Equivalent to the Podman `--subuidname` option. + ### `Sysctl=` Configures namespaced kernel parameters for the container. The format is `Sysctl=name=value`. @@ -591,6 +612,13 @@ This key can be listed multiple times. The timezone to run the container in. +### `UIDMap=` + +Run the container in a new user namespace using the supplied UID mapping. +Equivalent to the Podman `--uidmap` option. + +This key can be listed multiple times. + ### `Ulimit=` Ulimit options. Sets the ulimits values inside of the container. diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index c2841abf5c..26e1745b1e 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -73,6 +73,7 @@ const ( KeyExec = "Exec" KeyExitCodePropagation = "ExitCodePropagation" KeyExposeHostPort = "ExposeHostPort" + KeyGIDMap = "GIDMap" KeyGlobalArgs = "GlobalArgs" KeyGroup = "Group" KeyHealthCmd = "HealthCmd" @@ -132,11 +133,14 @@ const ( KeySecurityLabelType = "SecurityLabelType" KeySetWorkingDirectory = "SetWorkingDirectory" KeyShmSize = "ShmSize" + KeySubGIDMap = "SubGIDMap" + KeySubUIDMap = "SubUIDMap" KeySysctl = "Sysctl" KeyTimezone = "Timezone" KeyTLSVerify = "TLSVerify" KeyTmpfs = "Tmpfs" KeyType = "Type" + KeyUIDMap = "UIDMap" KeyUlimit = "Ulimit" KeyUnmask = "Unmask" KeyUser = "User" @@ -169,6 +173,7 @@ var ( KeyEnvironmentHost: true, KeyExec: true, KeyExposeHostPort: true, + KeyGIDMap: true, KeyGlobalArgs: true, KeyGroup: true, KeyHealthCmd: true, @@ -213,9 +218,12 @@ var ( KeySecurityLabelNested: true, KeySecurityLabelType: true, KeyShmSize: true, + KeySubGIDMap: true, + KeySubUIDMap: true, KeySysctl: true, KeyTimezone: true, KeyTmpfs: true, + KeyUIDMap: true, KeyUlimit: true, KeyUnmask: true, KeyUser: true, @@ -625,12 +633,10 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse podman.addf("-w=%s", workdir) } - if err := handleUserRemap(container, ContainerGroup, podman, isUser, true); err != nil { + if err := handleUserMappings(container, ContainerGroup, podman, isUser, true); err != nil { return nil, err } - handleUserNS(container, ContainerGroup, podman) - tmpfsValues := container.LookupAll(ContainerGroup, KeyTmpfs) for _, tmpfs := range tmpfsValues { if strings.Count(tmpfs, ":") > 1 { @@ -1091,12 +1097,10 @@ func ConvertKube(kube *parser.UnitFile, names map[string]string, isUser bool) (* handleLogDriver(kube, KubeGroup, execStart) - if err := handleUserRemap(kube, KubeGroup, execStart, isUser, false); err != nil { + if err := handleUserMappings(kube, KubeGroup, execStart, isUser, false); err != nil { return nil, err } - handleUserNS(kube, KubeGroup, execStart) - addNetworks(kube, KubeGroup, service, names, execStart) updateMaps := kube.LookupAllStrv(KubeGroup, KeyAutoUpdate) @@ -1245,12 +1249,50 @@ func handleUser(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdli return nil } -func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline, isUser, supportManual bool) error { - // ignore Remap keys if UserNS is set +func handleUserMappings(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline, isUser, supportManual bool) error { + mappingsDefined := false + if userns, ok := unitFile.Lookup(groupName, KeyUserNS); ok && len(userns) > 0 { + podman.add("--userns", userns) + mappingsDefined = true + } + + uidMaps := unitFile.LookupAllStrv(groupName, KeyUIDMap) + mappingsDefined = mappingsDefined || len(uidMaps) > 0 + for _, uidMap := range uidMaps { + podman.addf("--uidmap=%s", uidMap) + } + + gidMaps := unitFile.LookupAllStrv(groupName, KeyGIDMap) + mappingsDefined = mappingsDefined || len(gidMaps) > 0 + for _, gidMap := range gidMaps { + podman.addf("--gidmap=%s", gidMap) + } + + if subUIDMap, ok := unitFile.Lookup(groupName, KeySubUIDMap); ok && len(subUIDMap) > 0 { + podman.add("--subuidname", subUIDMap) + mappingsDefined = true + } + + if subGIDMap, ok := unitFile.Lookup(groupName, KeySubGIDMap); ok && len(subGIDMap) > 0 { + podman.add("--subgidname", subGIDMap) + mappingsDefined = true + } + + if mappingsDefined { + _, hasRemapUID := unitFile.Lookup(groupName, KeyRemapUID) + _, hasRemapGID := unitFile.Lookup(groupName, KeyRemapGID) + _, RemapUsers := unitFile.LookupLast(groupName, KeyRemapUsers) + if hasRemapUID || hasRemapGID || RemapUsers { + return fmt.Errorf("deprecated Remap keys are set along with explicit mapping keys") + } return nil } + return handleUserRemap(unitFile, groupName, podman, isUser, supportManual) +} + +func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline, isUser, supportManual bool) error { uidMaps := unitFile.LookupAllStrv(groupName, KeyRemapUID) gidMaps := unitFile.LookupAllStrv(groupName, KeyRemapGID) remapUsers, _ := unitFile.LookupLast(groupName, KeyRemapUsers) @@ -1315,12 +1357,6 @@ func handleUserRemap(unitFile *parser.UnitFile, groupName string, podman *Podman return nil } -func handleUserNS(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline) { - if userns, ok := unitFile.Lookup(groupName, KeyUserNS); ok && len(userns) > 0 { - podman.add("--userns", userns) - } -} - func addNetworks(quadletUnitFile *parser.UnitFile, groupName string, serviceUnitFile *parser.UnitFile, names map[string]string, podman *PodmanCmdline) { networks := quadletUnitFile.LookupAll(groupName, KeyNetwork) for _, network := range networks { diff --git a/test/e2e/quadlet/idmapping-with-remap.container b/test/e2e/quadlet/idmapping-with-remap.container new file mode 100644 index 0000000000..36d435fb0d --- /dev/null +++ b/test/e2e/quadlet/idmapping-with-remap.container @@ -0,0 +1,10 @@ +## assert-failed +## assert-stderr-contains "deprecated Remap keys are set along with explicit mapping keys" + +[Container] +Image=localhost/imagename +UIDMap=0:10000:10 +UIDMap=10:20000:10 +GIDMap=0:10000:10 +GIDMap=10:20000:10 +RemapUsers=auto diff --git a/test/e2e/quadlet/idmapping.container b/test/e2e/quadlet/idmapping.container new file mode 100644 index 0000000000..5ea2e38a45 --- /dev/null +++ b/test/e2e/quadlet/idmapping.container @@ -0,0 +1,11 @@ +## 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 +UIDMap=0:10000:10 +UIDMap=10:20000:10 +GIDMap=0:10000:10 +GIDMap=10:20000:10 diff --git a/test/e2e/quadlet/subidmapping-with-remap.container b/test/e2e/quadlet/subidmapping-with-remap.container new file mode 100644 index 0000000000..143f88295e --- /dev/null +++ b/test/e2e/quadlet/subidmapping-with-remap.container @@ -0,0 +1,8 @@ +## assert-failed +## assert-stderr-contains "deprecated Remap keys are set along with explicit mapping keys" + +[Container] +Image=localhost/imagename +SubUIDMap=utest +SubGIDMap=gtest +RemapUsers=auto diff --git a/test/e2e/quadlet/subidmapping.container b/test/e2e/quadlet/subidmapping.container new file mode 100644 index 0000000000..b102850b30 --- /dev/null +++ b/test/e2e/quadlet/subidmapping.container @@ -0,0 +1,7 @@ +## assert-podman-args "--subuidname" "utest" +## assert-podman-args "--subgidname" "gtest" + +[Container] +Image=localhost/imagename +SubUIDMap=utest +SubGIDMap=gtest diff --git a/test/e2e/quadlet/userns-with-remap.container b/test/e2e/quadlet/userns-with-remap.container new file mode 100644 index 0000000000..3e1e8185aa --- /dev/null +++ b/test/e2e/quadlet/userns-with-remap.container @@ -0,0 +1,7 @@ +## assert-failed +## assert-stderr-contains "deprecated Remap keys are set along with explicit mapping keys" + +[Container] +Image=localhost/imagename +RemapUsers=auto +UserNS=keep-id diff --git a/test/e2e/quadlet/userns.container b/test/e2e/quadlet/userns.container new file mode 100644 index 0000000000..f2bc0b7bdb --- /dev/null +++ b/test/e2e/quadlet/userns.container @@ -0,0 +1,5 @@ +## assert-podman-args "--userns" "keep-id" + +[Container] +Image=localhost/imagename +UserNS=keep-id diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index f2a00b971f..3e8faa71a5 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -690,6 +690,8 @@ BOGUS=foo Entry("exec.container", "exec.container", 0, ""), Entry("health.container", "health.container", 0, ""), Entry("hostname.container", "hostname.container", 0, ""), + Entry("idmapping.container", "idmapping.container", 0, ""), + Entry("idmapping-with-remap.container", "idmapping-with-remap.container", 1, "converting \"idmapping-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"), Entry("image.container", "image.container", 0, ""), Entry("install.container", "install.container", 0, ""), Entry("ip.container", "ip.container", 0, ""), @@ -726,10 +728,14 @@ BOGUS=foo Entry("selinux.container", "selinux.container", 0, ""), Entry("shmsize.container", "shmsize.container", 0, ""), Entry("shortname.container", "shortname.container", 0, "Warning: shortname.container specifies the image \"shortname\" which not a fully qualified image name. This is not ideal for performance and security reasons. See the podman-pull manpage discussion of short-name-aliases.conf for details."), + Entry("subidmapping.container", "subidmapping.container", 0, ""), + Entry("subidmapping-with-remap.container", "subidmapping-with-remap.container", 1, "converting \"subidmapping-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"), Entry("sysctl.container", "sysctl.container", 0, ""), Entry("timezone.container", "timezone.container", 0, ""), Entry("unmask.container", "unmask.container", 0, ""), Entry("user.container", "user.container", 0, ""), + Entry("userns.container", "userns.container", 0, ""), + Entry("userns-with-remap.container", "userns-with-remap.container", 1, "converting \"userns-with-remap.container\": deprecated Remap keys are set along with explicit mapping keys"), Entry("volume.container", "volume.container", 0, ""), Entry("workingdir.container", "workingdir.container", 0, ""), Entry("Container - global args", "globalargs.container", 0, ""),