Skip to content

Commit

Permalink
Merge pull request containers#17352 from rhatdan/rootfs
Browse files Browse the repository at this point in the history
Add quadlet support for Rootfs and SELinux labels  containers
  • Loading branch information
openshift-merge-robot authored Feb 6, 2023
2 parents 004d611 + acaab3f commit a1f9c71
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 78 deletions.
24 changes: 24 additions & 0 deletions docs/source/markdown/podman-systemd.unit.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
193 changes: 116 additions & 77 deletions pkg/systemd/quadlet/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,86 +31,96 @@ 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 (
validPortRange = regexp.Delayed(`\d+(-\d+)?(/udp|/tcp)?$`)

// 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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/quadlet/disableselinux.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## assert-podman-args "--security-opt" "label:disable"

[Container]
Image=localhost/imagename
SecurityLabelDisable=true
2 changes: 1 addition & 1 deletion test/e2e/quadlet/noimage.container
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## assert-failed
## assert-stderr-contains "no Image key specified"
## assert-stderr-contains "no Image or Rootfs key specified"

[Container]
4 changes: 4 additions & 0 deletions test/e2e/quadlet/rootfs.container
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## assert-podman-final-args "--rootfs" "/var/lib/foobar"

[Container]
Rootfs=/var/lib/foobar
9 changes: 9 additions & 0 deletions test/e2e/quadlet/selinux.container
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions test/e2e/quadlet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down
71 changes: 71 additions & 0 deletions test/system/252-quadlet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit a1f9c71

Please sign in to comment.