mirror of
https://github.com/containers/podman.git
synced 2025-08-01 07:40:22 +08:00
Merge pull request #17352 from rhatdan/rootfs
Add quadlet support for Rootfs and SELinux labels containers
This commit is contained in:
docs/source/markdown
pkg/systemd/quadlet
test
@ -188,6 +188,14 @@ This key can be listed multiple times.
|
||||
If enabled (which is the default), this disables the container processes from gaining additional privileges via things like
|
||||
setuid and file capabilities.
|
||||
|
||||
#### `Rootfs=`
|
||||
|
||||
The rootfs to use for the container. Rootfs points to a directory on the system that contains the content to be run within the container. This option conflicts with the `Image` option.
|
||||
|
||||
The format of the rootfs is the same as when passed to `podman run --rootfs`, so it supports ovelay mounts as well.
|
||||
|
||||
Note: On SELinux systems, the rootfs needs the correct label, which is by default unconfined_u:object_r:container_file_t:s0.
|
||||
|
||||
#### `Notify=` (defaults to `no`)
|
||||
|
||||
By default, Podman is run in such a way that the systemd startup notify command is handled by
|
||||
@ -275,6 +283,22 @@ container that forwards signals and reaps processes.
|
||||
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.
|
||||
|
||||
#### `SecurityLabelDisable=`
|
||||
|
||||
Turn off label separation for the container.
|
||||
|
||||
#### `SecurityLabelFileType=`
|
||||
|
||||
Set the label file type for the container files.
|
||||
|
||||
#### `SecurityLabelLevel=`
|
||||
|
||||
Set the label process level for the container processes.
|
||||
|
||||
#### `SecurityLabelType=`
|
||||
|
||||
Set the label process type for the container processes.
|
||||
|
||||
#### `Timezone=` (if unset uses system-configured default)
|
||||
|
||||
The timezone to run the container in.
|
||||
|
@ -31,50 +31,55 @@ const (
|
||||
|
||||
// All the supported quadlet keys
|
||||
const (
|
||||
KeyContainerName = "ContainerName"
|
||||
KeyImage = "Image"
|
||||
KeyEnvironment = "Environment"
|
||||
KeyEnvironmentFile = "EnvironmentFile"
|
||||
KeyEnvironmentHost = "EnvironmentHost"
|
||||
KeyExec = "Exec"
|
||||
KeyNoNewPrivileges = "NoNewPrivileges"
|
||||
KeyDropCapability = "DropCapability"
|
||||
KeyAddCapability = "AddCapability"
|
||||
KeyReadOnly = "ReadOnly"
|
||||
KeyRemapUsers = "RemapUsers"
|
||||
KeyRemapUID = "RemapUid"
|
||||
KeyRemapGID = "RemapGid"
|
||||
KeyRemapUIDSize = "RemapUidSize"
|
||||
KeyNotify = "Notify"
|
||||
KeyExposeHostPort = "ExposeHostPort"
|
||||
KeyPublishPort = "PublishPort"
|
||||
KeyUser = "User"
|
||||
KeyGroup = "Group"
|
||||
KeyDevice = "Device"
|
||||
KeyType = "Type"
|
||||
KeyOptions = "Options"
|
||||
KeyCopy = "Copy"
|
||||
KeyVolume = "Volume"
|
||||
KeyPodmanArgs = "PodmanArgs"
|
||||
KeyLabel = "Label"
|
||||
KeyAnnotation = "Annotation"
|
||||
KeyRunInit = "RunInit"
|
||||
KeyVolatileTmp = "VolatileTmp"
|
||||
KeyTimezone = "Timezone"
|
||||
KeySeccompProfile = "SeccompProfile"
|
||||
KeyAddDevice = "AddDevice"
|
||||
KeyNetwork = "Network"
|
||||
KeyYaml = "Yaml"
|
||||
KeyNetworkDisableDNS = "DisableDNS"
|
||||
KeyNetworkDriver = "Driver"
|
||||
KeyNetworkGateway = "Gateway"
|
||||
KeyNetworkInternal = "Internal"
|
||||
KeyNetworkIPRange = "IPRange"
|
||||
KeyNetworkIPAMDriver = "IPAMDriver"
|
||||
KeyNetworkIPv6 = "IPv6"
|
||||
KeyNetworkOptions = "Options"
|
||||
KeyNetworkSubnet = "Subnet"
|
||||
KeyConfigMap = "ConfigMap"
|
||||
KeyContainerName = "ContainerName"
|
||||
KeyImage = "Image"
|
||||
KeyEnvironment = "Environment"
|
||||
KeyEnvironmentFile = "EnvironmentFile"
|
||||
KeyEnvironmentHost = "EnvironmentHost"
|
||||
KeyExec = "Exec"
|
||||
KeyNoNewPrivileges = "NoNewPrivileges"
|
||||
KeyDropCapability = "DropCapability"
|
||||
KeyAddCapability = "AddCapability"
|
||||
KeyReadOnly = "ReadOnly"
|
||||
KeyRemapUsers = "RemapUsers"
|
||||
KeyRemapUID = "RemapUid"
|
||||
KeyRemapGID = "RemapGid"
|
||||
KeyRemapUIDSize = "RemapUidSize"
|
||||
KeyRootfs = "Rootfs"
|
||||
KeyNotify = "Notify"
|
||||
KeyExposeHostPort = "ExposeHostPort"
|
||||
KeyPublishPort = "PublishPort"
|
||||
KeyUser = "User"
|
||||
KeyGroup = "Group"
|
||||
KeyDevice = "Device"
|
||||
KeyType = "Type"
|
||||
KeyOptions = "Options"
|
||||
KeyCopy = "Copy"
|
||||
KeyVolume = "Volume"
|
||||
KeyPodmanArgs = "PodmanArgs"
|
||||
KeyLabel = "Label"
|
||||
KeyAnnotation = "Annotation"
|
||||
KeyRunInit = "RunInit"
|
||||
KeyVolatileTmp = "VolatileTmp"
|
||||
KeyTimezone = "Timezone"
|
||||
KeySeccompProfile = "SeccompProfile"
|
||||
KeySecurityLabelDisable = "SecurityLabelDisable"
|
||||
KeySecurityLabelFileType = "SecurityLabelFileType"
|
||||
KeySecurityLabelType = "SecurityLabelType"
|
||||
KeySecurityLabelLevel = "SecurityLabelLevel"
|
||||
KeyAddDevice = "AddDevice"
|
||||
KeyNetwork = "Network"
|
||||
KeyYaml = "Yaml"
|
||||
KeyNetworkDisableDNS = "DisableDNS"
|
||||
KeyNetworkDriver = "Driver"
|
||||
KeyNetworkGateway = "Gateway"
|
||||
KeyNetworkInternal = "Internal"
|
||||
KeyNetworkIPRange = "IPRange"
|
||||
KeyNetworkIPAMDriver = "IPAMDriver"
|
||||
KeyNetworkIPv6 = "IPv6"
|
||||
KeyNetworkOptions = "Options"
|
||||
KeyNetworkSubnet = "Subnet"
|
||||
KeyConfigMap = "ConfigMap"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -82,35 +87,40 @@ var (
|
||||
|
||||
// Supported keys in "Container" group
|
||||
supportedContainerKeys = map[string]bool{
|
||||
KeyContainerName: true,
|
||||
KeyImage: true,
|
||||
KeyEnvironment: true,
|
||||
KeyEnvironmentFile: true,
|
||||
KeyEnvironmentHost: true,
|
||||
KeyExec: true,
|
||||
KeyNoNewPrivileges: true,
|
||||
KeyDropCapability: true,
|
||||
KeyAddCapability: true,
|
||||
KeyReadOnly: true,
|
||||
KeyRemapUsers: true,
|
||||
KeyRemapUID: true,
|
||||
KeyRemapGID: true,
|
||||
KeyRemapUIDSize: true,
|
||||
KeyNotify: true,
|
||||
KeyExposeHostPort: true,
|
||||
KeyPublishPort: true,
|
||||
KeyUser: true,
|
||||
KeyGroup: true,
|
||||
KeyVolume: true,
|
||||
KeyPodmanArgs: true,
|
||||
KeyLabel: true,
|
||||
KeyAnnotation: true,
|
||||
KeyRunInit: true,
|
||||
KeyVolatileTmp: true,
|
||||
KeyTimezone: true,
|
||||
KeySeccompProfile: true,
|
||||
KeyAddDevice: true,
|
||||
KeyNetwork: true,
|
||||
KeyContainerName: true,
|
||||
KeyImage: true,
|
||||
KeyEnvironment: true,
|
||||
KeyEnvironmentFile: true,
|
||||
KeyEnvironmentHost: true,
|
||||
KeyExec: true,
|
||||
KeyNoNewPrivileges: true,
|
||||
KeyDropCapability: true,
|
||||
KeyAddCapability: true,
|
||||
KeyReadOnly: true,
|
||||
KeyRemapUsers: true,
|
||||
KeyRemapUID: true,
|
||||
KeyRemapGID: true,
|
||||
KeyRemapUIDSize: true,
|
||||
KeyRootfs: true,
|
||||
KeyNotify: true,
|
||||
KeyExposeHostPort: true,
|
||||
KeyPublishPort: true,
|
||||
KeyUser: true,
|
||||
KeyGroup: true,
|
||||
KeyVolume: true,
|
||||
KeyPodmanArgs: true,
|
||||
KeyLabel: true,
|
||||
KeyAnnotation: true,
|
||||
KeyRunInit: true,
|
||||
KeyVolatileTmp: true,
|
||||
KeyTimezone: true,
|
||||
KeySeccompProfile: true,
|
||||
KeySecurityLabelDisable: true,
|
||||
KeySecurityLabelFileType: true,
|
||||
KeySecurityLabelType: true,
|
||||
KeySecurityLabelLevel: true,
|
||||
KeyAddDevice: true,
|
||||
KeyNetwork: true,
|
||||
}
|
||||
|
||||
// Supported keys in "Volume" group
|
||||
@ -239,9 +249,14 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
|
||||
// Rename old Container group to x-Container so that systemd ignores it
|
||||
service.RenameGroup(ContainerGroup, XContainerGroup)
|
||||
|
||||
image, ok := container.Lookup(ContainerGroup, KeyImage)
|
||||
if !ok || len(image) == 0 {
|
||||
return nil, fmt.Errorf("no Image key specified")
|
||||
// One image or rootfs must be specified for the container
|
||||
image, _ := container.Lookup(ContainerGroup, KeyImage)
|
||||
rootfs, _ := container.Lookup(ContainerGroup, KeyRootfs)
|
||||
if len(image) == 0 && len(rootfs) == 0 {
|
||||
return nil, fmt.Errorf("no Image or Rootfs key specified")
|
||||
}
|
||||
if len(image) > 0 && len(rootfs) > 0 {
|
||||
return nil, fmt.Errorf("the Image And Rootfs keys conflict can not be specified together")
|
||||
}
|
||||
|
||||
containerName, ok := container.Lookup(ContainerGroup, KeyContainerName)
|
||||
@ -346,6 +361,26 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
|
||||
podman.add("--security-opt=no-new-privileges")
|
||||
}
|
||||
|
||||
securityLabelDisable := container.LookupBooleanWithDefault(ContainerGroup, KeySecurityLabelDisable, false)
|
||||
if securityLabelDisable {
|
||||
podman.add("--security-opt", "label:disable")
|
||||
}
|
||||
|
||||
securityLabelType, _ := container.Lookup(ContainerGroup, KeySecurityLabelType)
|
||||
if len(securityLabelType) > 0 {
|
||||
podman.add("--security-opt", fmt.Sprintf("label=type:%s", securityLabelType))
|
||||
}
|
||||
|
||||
securityLabelFileType, _ := container.Lookup(ContainerGroup, KeySecurityLabelFileType)
|
||||
if len(securityLabelFileType) > 0 {
|
||||
podman.add("--security-opt", fmt.Sprintf("label=filetype:%s", securityLabelFileType))
|
||||
}
|
||||
|
||||
securityLabelLevel, _ := container.Lookup(ContainerGroup, KeySecurityLabelLevel)
|
||||
if len(securityLabelLevel) > 0 {
|
||||
podman.add("--security-opt", fmt.Sprintf("label=level:%s", securityLabelLevel))
|
||||
}
|
||||
|
||||
// But allow overrides with AddCapability
|
||||
devices := container.LookupAllStrv(ContainerGroup, KeyAddDevice)
|
||||
for _, device := range devices {
|
||||
@ -486,7 +521,11 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile
|
||||
podmanArgs := container.LookupAllArgs(ContainerGroup, KeyPodmanArgs)
|
||||
podman.add(podmanArgs...)
|
||||
|
||||
podman.add(image)
|
||||
if len(image) > 0 {
|
||||
podman.add(image)
|
||||
} else {
|
||||
podman.add("--rootfs", rootfs)
|
||||
}
|
||||
|
||||
execArgs, ok := container.LookupLastArgs(ContainerGroup, KeyExec)
|
||||
if ok {
|
||||
|
5
test/e2e/quadlet/disableselinux.container
Normal file
5
test/e2e/quadlet/disableselinux.container
Normal file
@ -0,0 +1,5 @@
|
||||
## assert-podman-args "--security-opt" "label:disable"
|
||||
|
||||
[Container]
|
||||
Image=localhost/imagename
|
||||
SecurityLabelDisable=true
|
@ -1,4 +1,4 @@
|
||||
## assert-failed
|
||||
## assert-stderr-contains "no Image key specified"
|
||||
## assert-stderr-contains "no Image or Rootfs key specified"
|
||||
|
||||
[Container]
|
||||
|
4
test/e2e/quadlet/rootfs.container
Normal file
4
test/e2e/quadlet/rootfs.container
Normal file
@ -0,0 +1,4 @@
|
||||
## assert-podman-final-args "--rootfs" "/var/lib/foobar"
|
||||
|
||||
[Container]
|
||||
Rootfs=/var/lib/foobar
|
9
test/e2e/quadlet/selinux.container
Normal file
9
test/e2e/quadlet/selinux.container
Normal file
@ -0,0 +1,9 @@
|
||||
## assert-podman-args "--security-opt" "label=type:foobar_t"
|
||||
## assert-podman-args "--security-opt" "label=level:s0:c1000,c10001"
|
||||
## assert-podman-args "--security-opt" "label=filetype:foobar_file_t"
|
||||
|
||||
[Container]
|
||||
Image=localhost/imagename
|
||||
SecurityLabelType=foobar_t
|
||||
SecurityLabelFileType=foobar_file_t
|
||||
SecurityLabelLevel=s0:c1000,c10001
|
@ -447,6 +447,7 @@ var _ = Describe("quadlet system generator", func() {
|
||||
Entry("basepodman.container", "basepodman.container"),
|
||||
Entry("capabilities.container", "capabilities.container"),
|
||||
Entry("capabilities2.container", "capabilities2.container"),
|
||||
Entry("disableselinux.container", "disableselinux.container"),
|
||||
Entry("devices.container", "devices.container"),
|
||||
Entry("env.container", "env.container"),
|
||||
Entry("escapes.container", "escapes.container"),
|
||||
@ -460,6 +461,8 @@ var _ = Describe("quadlet system generator", func() {
|
||||
Entry("noimage.container", "noimage.container"),
|
||||
Entry("notify.container", "notify.container"),
|
||||
Entry("oneshot.container", "oneshot.container"),
|
||||
Entry("rootfs.container", "rootfs.container"),
|
||||
Entry("selinux.container", "selinux.container"),
|
||||
Entry("other-sections.container", "other-sections.container"),
|
||||
Entry("podmanargs.container", "podmanargs.container"),
|
||||
Entry("ports.container", "ports.container"),
|
||||
|
@ -406,4 +406,75 @@ EOF
|
||||
run_podman rmi $(pause_image)
|
||||
}
|
||||
|
||||
@test "quadlet - rootfs" {
|
||||
skip_if_no_selinux
|
||||
skip_if_rootless
|
||||
local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).container
|
||||
cat > $quadlet_file <<EOF
|
||||
[Container]
|
||||
Rootfs=/:O
|
||||
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; top"
|
||||
EOF
|
||||
|
||||
run_quadlet "$quadlet_file"
|
||||
service_setup $QUADLET_SERVICE_NAME
|
||||
|
||||
# Ensure we have output. Output is synced via sd-notify (socat in Exec)
|
||||
run journalctl "--since=$STARTED_TIME" --unit="$QUADLET_SERVICE_NAME"
|
||||
is "$output" '.*STARTED CONTAINER.*'
|
||||
}
|
||||
|
||||
@test "quadlet - selinux disable" {
|
||||
skip_if_no_selinux
|
||||
local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).container
|
||||
cat > $quadlet_file <<EOF
|
||||
[Container]
|
||||
Image=$IMAGE
|
||||
SecurityLabelDisable=true
|
||||
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; top"
|
||||
EOF
|
||||
|
||||
run_quadlet "$quadlet_file"
|
||||
service_setup $QUADLET_SERVICE_NAME
|
||||
|
||||
# Ensure we have output. Output is synced via sd-notify (socat in Exec)
|
||||
run journalctl "--since=$STARTED_TIME" --unit="$QUADLET_SERVICE_NAME"
|
||||
is "$output" '.*STARTED CONTAINER.*'
|
||||
|
||||
run_podman container inspect --format "{{.ProcessLabel}}" $QUADLET_CONTAINER_NAME
|
||||
is "$output" "" "container should be started without specifying a Process Label"
|
||||
|
||||
service_cleanup $QUADLET_SERVICE_NAME failed
|
||||
}
|
||||
|
||||
@test "quadlet - selinux labels" {
|
||||
skip_if_no_selinux
|
||||
NAME=$(random_string)
|
||||
local quadlet_file=$PODMAN_TMPDIR/basic_$(random_string).container
|
||||
cat > $quadlet_file <<EOF
|
||||
[Container]
|
||||
ContainerName=$NAME
|
||||
Image=$IMAGE
|
||||
SecurityLabelType=spc_t
|
||||
SecurityLabelLevel=s0:c100,c200
|
||||
SecurityLabelFileType=container_ro_file_t
|
||||
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; top"
|
||||
EOF
|
||||
|
||||
run_quadlet "$quadlet_file"
|
||||
service_setup $QUADLET_SERVICE_NAME
|
||||
|
||||
# Ensure we have output. Output is synced via sd-notify (socat in Exec)
|
||||
run journalctl "--since=$STARTED_TIME" --unit="$QUADLET_SERVICE_NAME"
|
||||
is "$output" '.*STARTED CONTAINER.*'
|
||||
|
||||
run_podman container ps
|
||||
run_podman container inspect --format "{{.ProcessLabel}}" $NAME
|
||||
is "$output" "system_u:system_r:spc_t:s0:c100,c200" "container should be started with correct Process Label"
|
||||
run_podman container inspect --format "{{.MountLabel}}" $NAME
|
||||
is "$output" "system_u:object_r:container_ro_file_t:s0:c100,c200" "container should be started with correct Mount Label"
|
||||
|
||||
service_cleanup $QUADLET_SERVICE_NAME failed
|
||||
}
|
||||
|
||||
# vim: filetype=sh
|
||||
|
Reference in New Issue
Block a user