Support running nested SELinux container separation

Currently Podman prevents SELinux container separation,
when running within a container. This PR adds a new
--security-opt label=nested

When setting this option, Podman unmasks and mountsi
/sys/fs/selinux into the containers making /sys/fs/selinux
fully exposed. Secondly Podman sets the attribute
run.oci.mount_context_type=rootcontext

This attribute tells crun to mount volumes with rootcontext=MOUNTLABEL
as opposed to context=MOUNTLABEL.

With these two settings Podman inside the container is allowed to set
its own SELinux labels on tmpfs file systems mounted into its parents
container, while still being confined by SELinux. Thus you can have
nested SELinux labeling inside of a container.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2023-02-13 02:45:33 -05:00
parent 3920799553
commit ad8a96ab95
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
12 changed files with 130 additions and 66 deletions

View File

@ -257,7 +257,7 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra
if registry.IsRemote() { if registry.IsRemote() {
return vals, errors.New("the '--group-add keep-groups' option is not supported in remote mode") return vals, errors.New("the '--group-add keep-groups' option is not supported in remote mode")
} }
vals.Annotation = append(vals.Annotation, "run.oci.keep_original_groups=1") vals.Annotation = append(vals.Annotation, fmt.Sprintf("%s=1", define.RunOCIKeepOriginalGroups))
} else { } else {
groups = append(groups, g) groups = append(groups, g)
} }

View File

@ -18,6 +18,8 @@ Security Options
Note: Labeling can be disabled for all <<|pods/>>containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file. Note: Labeling can be disabled for all <<|pods/>>containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file.
- **label=nested**: Allows SELinux modifications within the container. Containers are allowed to modify SELinux labels on files and processes, as long as SELinux policy allows. Without **nested**, containers view SELinux as disabled, even when it is enabled on the host. Containers are prevented from setting any labels.
- **mask**=_/path/1:/path/2_: The paths to mask separated by a colon. A masked path cannot be accessed inside the container<<s within the pod|>>. - **mask**=_/path/1:/path/2_: The paths to mask separated by a colon. A masked path cannot be accessed inside the container<<s within the pod|>>.
- **no-new-privileges**: Disable container processes from gaining additional privileges. - **no-new-privileges**: Disable container processes from gaining additional privileges.

View File

@ -219,6 +219,8 @@ type ContainerSecurityConfig struct {
// Libpod - mostly used in rootless containers where the user running // Libpod - mostly used in rootless containers where the user running
// Libpod wants to retain their UID inside the container. // Libpod wants to retain their UID inside the container.
AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"`
// LabelNested, allow labeling separation from within a container
LabelNested bool `json:"label_nested"`
} }
// ContainerNameSpaceConfig is an embedded sub-config providing // ContainerNameSpaceConfig is an embedded sub-config providing

View File

@ -32,8 +32,13 @@ var (
) )
func (c *Container) mountSHM(shmOptions string) error { func (c *Container) mountSHM(shmOptions string) error {
contextType := "context"
if c.config.LabelNested {
contextType = "rootcontext"
}
if err := unix.Mount("shm", c.config.ShmDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, if err := unix.Mount("shm", c.config.ShmDir, "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV,
label.FormatMountLabel(shmOptions, c.config.MountLabel)); err != nil { label.FormatMountLabelByType(shmOptions, c.config.MountLabel, contextType)); err != nil {
return fmt.Errorf("failed to mount shm tmpfs %q: %w", c.config.ShmDir, err) return fmt.Errorf("failed to mount shm tmpfs %q: %w", c.config.ShmDir, err)
} }
return nil return nil

View File

@ -1,6 +1,12 @@
package define package define
const ( const (
// RunOCIMountContextType tells the OCI runtime which context mount
// type to use. context, rootcontext, fscontext, defcontext
RunOCIMountContextType = "run.oci.mount_context_type"
// RunOCIKeepOriginalGroups tells the OCI runtime to leak the users
// current groups into the container
RunOCIKeepOriginalGroups = "run.oci.keep_original_groups"
// InspectAnnotationCIDFile is used by Inspect to determine if a // InspectAnnotationCIDFile is used by Inspect to determine if a
// container ID file was created for the container. // container ID file was created for the container.
// If an annotation with this key is found in the OCI spec, it will be // If an annotation with this key is found in the OCI spec, it will be
@ -58,7 +64,6 @@ const (
// If an annotation with this key is found in the OCI spec, it will be // If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect(). // used in the output of Inspect().
InspectAnnotationApparmor = "io.podman.annotations.apparmor" InspectAnnotationApparmor = "io.podman.annotations.apparmor"
// InspectResponseTrue is a boolean True response for an inspect // InspectResponseTrue is a boolean True response for an inspect
// annotation. // annotation.
InspectResponseTrue = "TRUE" InspectResponseTrue = "TRUE"

View File

@ -2341,3 +2341,16 @@ func WithMountAllDevices() CtrCreateOption {
return nil return nil
} }
} }
// WithLabelNested sets the LabelNested flag allowing label separation within container
func WithLabelNested(nested bool) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
ctr.config.LabelNested = nested
return nil
}
}

View File

@ -120,15 +120,13 @@ func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, opti
volume.config.StorageImageID = image.ID() volume.config.StorageImageID = image.ID()
// Create a backing container in c/storage. // Create a backing container in c/storage.
storageConfig := storage.ContainerOptions{ storageConfig := storage.ContainerOptions{}
LabelOpts: []string{"filetype:container_file_t:s0"},
}
if len(volume.config.MountLabel) > 0 { if len(volume.config.MountLabel) > 0 {
context, err := selinux.NewContext(volume.config.MountLabel) context, err := selinux.NewContext(volume.config.MountLabel)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get SELinux context from %s: %w", volume.config.MountLabel, err) return nil, fmt.Errorf("failed to get SELinux context from %s: %w", volume.config.MountLabel, err)
} }
storageConfig.LabelOpts = []string{fmt.Sprintf("filetype:%s:s0", context["type"])} storageConfig.LabelOpts = []string{fmt.Sprintf("filetype:%s", context["type"])}
} }
if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil { if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil {
return nil, fmt.Errorf("creating backing storage for image driver: %w", err) return nil, fmt.Errorf("creating backing storage for image driver: %w", err)

View File

@ -482,6 +482,9 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver)) options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
} }
} }
if s.ContainerSecurityConfig.LabelNested {
options = append(options, libpod.WithLabelNested(s.ContainerSecurityConfig.LabelNested))
}
// Security options // Security options
if len(s.SelinuxOpts) > 0 { if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts)) options = append(options, libpod.WithSecLabels(s.SelinuxOpts))

View File

@ -396,6 +396,10 @@ type ContainerSecurityConfig struct {
// mount temporary file systems // mount temporary file systems
ReadWriteTmpfs bool `json:"read_write_tmpfs,omitempty"` ReadWriteTmpfs bool `json:"read_write_tmpfs,omitempty"`
// LabelNested indicates whether or not the container is allowed to
// run fully nested containers including labelling
LabelNested bool `json:"label_nested,omitempty"`
// Umask is the umask the init process of the container will be run with. // Umask is the umask the init process of the container will be run with.
Umask string `json:"umask,omitempty"` Umask string `json:"umask,omitempty"`
// ProcOpts are the options used for the proc mount. // ProcOpts are the options used for the proc mount.

View File

@ -620,53 +620,57 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
} }
for _, opt := range c.SecurityOpt { for _, opt := range c.SecurityOpt {
if opt == "no-new-privileges" { // Docker deprecated the ":" syntax but still supports it,
s.ContainerSecurityConfig.NoNewPrivileges = true // so we need to as well
var con []string
if strings.Contains(opt, "=") {
con = strings.SplitN(opt, "=", 2)
} else { } else {
// Docker deprecated the ":" syntax but still supports it, con = strings.SplitN(opt, ":", 2)
// so we need to as well }
var con []string if len(con) != 2 &&
if strings.Contains(opt, "=") { con[0] != "no-new-privileges" {
con = strings.SplitN(opt, "=", 2) return fmt.Errorf("invalid --security-opt 1: %q", opt)
} else { }
con = strings.SplitN(opt, ":", 2) switch con[0] {
case "apparmor":
s.ContainerSecurityConfig.ApparmorProfile = con[1]
s.Annotations[define.InspectAnnotationApparmor] = con[1]
case "label":
if con[1] == "nested" {
s.ContainerSecurityConfig.LabelNested = true
continue
} }
if len(con) != 2 { // TODO selinux opts and label opts are the same thing
return fmt.Errorf("invalid --security-opt 1: %q", opt) s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
} s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
switch con[0] { case "mask":
case "apparmor": s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
s.ContainerSecurityConfig.ApparmorProfile = con[1] case "proc-opts":
s.Annotations[define.InspectAnnotationApparmor] = con[1] s.ProcOpts = strings.Split(con[1], ",")
case "label": case "seccomp":
// TODO selinux opts and label opts are the same thing s.SeccompProfilePath = con[1]
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) s.Annotations[define.InspectAnnotationSeccomp] = con[1]
s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
case "mask":
s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
case "proc-opts":
s.ProcOpts = strings.Split(con[1], ",")
case "seccomp":
s.SeccompProfilePath = con[1]
s.Annotations[define.InspectAnnotationSeccomp] = con[1]
// this option is for docker compatibility, it is the same as unmask=ALL // this option is for docker compatibility, it is the same as unmask=ALL
case "systempaths": case "systempaths":
if con[1] == "unconfined" { if con[1] == "unconfined" {
s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...) s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...)
} else { } else {
return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1]) return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1])
} }
case "unmask": case "unmask":
s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...) s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...)
case "no-new-privileges": case "no-new-privileges":
noNewPrivileges, err := strconv.ParseBool(con[1]) noNewPrivileges := true
if len(con) == 2 {
noNewPrivileges, err = strconv.ParseBool(con[1])
if err != nil { if err != nil {
return fmt.Errorf("invalid --security-opt 2: %q", opt) return fmt.Errorf("invalid --security-opt 2: %q", opt)
} }
s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges
default:
return fmt.Errorf("invalid --security-opt 2: %q", opt)
} }
s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges
default:
return fmt.Errorf("invalid --security-opt 2: %q", opt)
} }
} }
@ -690,6 +694,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
if len(s.Volumes) == 0 || len(c.Volume) != 0 { if len(s.Volumes) == 0 || len(c.Volume) != 0 {
s.Volumes = volumes s.Volumes = volumes
} }
if s.ContainerSecurityConfig.LabelNested {
// Need to unmask the SELinux file system
s.Unmask = append(s.Unmask, "/sys/fs/selinux", "/proc")
s.Mounts = append(s.Mounts, specs.Mount{
Source: "/sys/fs/selinux",
Destination: "/sys/fs/selinux",
Type: define.TypeBind,
})
s.Annotations[define.RunOCIMountContextType] = "rootcontext"
}
// TODO make sure these work in clone // TODO make sure these work in clone
if len(s.OverlayVolumes) == 0 { if len(s.OverlayVolumes) == 0 {
s.OverlayVolumes = overlayVolumes s.OverlayVolumes = overlayVolumes

View File

@ -7,6 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/containers/podman/v4/libpod/define"
. "github.com/containers/podman/v4/test/utils" . "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -111,7 +112,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
result := podmanTest.Podman([]string{"top", "test1", "capeff"}) result := podmanTest.Podman([]string{"top", "test1", "capeff"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0)) Expect(result).Should(Exit(0))
Expect(result.Out.Contents()).To( Expect(result.OutputToString()).To(
And( And(
ContainSubstring("FOWNER"), ContainSubstring("FOWNER"),
ContainSubstring("SETFCAP"), ContainSubstring("SETFCAP"),
@ -128,7 +129,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
result := podmanTest.Podman([]string{"container", "top", "test1", "capeff"}) result := podmanTest.Podman([]string{"container", "top", "test1", "capeff"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0)) Expect(result).Should(Exit(0))
Expect(result.Out.Contents()).ToNot( Expect(result.OutputToString()).ToNot(
And( And(
ContainSubstring("SETUID"), ContainSubstring("SETUID"),
ContainSubstring("FOWNER"), ContainSubstring("FOWNER"),
@ -266,7 +267,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("search"))) Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("search")))
Expect(session.Out.Contents()).To( Expect(session.OutputToString()).To(
And( And(
ContainSubstring("foobar.com"), ContainSubstring("foobar.com"),
ContainSubstring("1.2.3.4"), ContainSubstring("1.2.3.4"),
@ -322,7 +323,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("search"))) Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("search")))
Expect(session.Out.Contents()).To( Expect(session.OutputToString()).To(
And( And(
ContainSubstring("foobar.com"), ContainSubstring("foobar.com"),
ContainSubstring("1.2.3.4"), ContainSubstring("1.2.3.4"),
@ -333,26 +334,26 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "cat", "/proc/sys/net/ipv4/ping_group_range"}) session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "cat", "/proc/sys/net/ipv4/ping_group_range"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To(ContainSubstring("1000")) Expect(session.OutputToString()).To(ContainSubstring("1000"))
// shm-size // shm-size
session = podmanTest.Podman([]string{"run", ALPINE, "grep", "shm", "/proc/self/mounts"}) session = podmanTest.Podman([]string{"run", ALPINE, "grep", "shm", "/proc/self/mounts"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To(ContainSubstring("size=200k")) Expect(session.OutputToString()).To(ContainSubstring("size=200k"))
// ulimits // ulimits
session = podmanTest.Podman([]string{"run", "--rm", fedoraMinimal, "ulimit", "-n"}) session = podmanTest.Podman([]string{"run", "--rm", fedoraMinimal, "ulimit", "-n"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To(ContainSubstring("500")) Expect(session.OutputToString()).To(ContainSubstring("500"))
// Configuration that comes from remote client // Configuration that comes from remote client
// Timezone // Timezone
session = podmanTest.Podman([]string{"run", ALPINE, "date", "+'%H %Z'"}) session = podmanTest.Podman([]string{"run", ALPINE, "date", "+'%H %Z'"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To( Expect(session.OutputToString()).To(
Or( Or(
ContainSubstring("EST"), ContainSubstring("EST"),
ContainSubstring("EDT"), ContainSubstring("EDT"),
@ -366,21 +367,21 @@ var _ = Describe("Verify podman containers.conf usage", func() {
}) })
It("add annotations", func() { It("add annotations", func() {
// containers.conf is set to "run.oci.keep_original_groups=1" // containers.conf is set to "run.oci.keep_original_groups=1"
session := podmanTest.Podman([]string{"create", "--rm", "--name", "test", fedoraMinimal}) session := podmanTest.Podman([]string{"create", "--rm", "--name", "test", fedoraMinimal})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", "--format", "{{ .Config.Annotations }}", "test"}) inspect := podmanTest.Podman([]string{"inspect", "--format", "{{ .Config.Annotations }}", "test"})
inspect.WaitWithDefaultTimeout() inspect.WaitWithDefaultTimeout()
Expect(inspect.Out.Contents()).To(ContainSubstring("run.oci.keep_original_groups:1")) Expect(inspect.OutputToString()).To(ContainSubstring(fmt.Sprintf("%s:1", define.RunOCIKeepOriginalGroups)))
}) })
It("--add-host and no-hosts=true fails", func() { It("--add-host and no-hosts=true fails", func() {
session := podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", ALPINE, "top"}) session := podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", ALPINE, "top"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError()) Expect(session).To(ExitWithError())
Expect(session.Err.Contents()).To(ContainSubstring("--no-hosts and --add-host cannot be set together")) Expect(session.ErrorToString()).To(ContainSubstring("--no-hosts and --add-host cannot be set together"))
session = podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", "--no-hosts=false", ALPINE, "top"}) session = podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", "--no-hosts=false", ALPINE, "top"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
@ -391,12 +392,12 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session := podmanTest.Podman([]string{"run", "--rm", "--name", "test", ALPINE, "cat", "/etc/hosts"}) session := podmanTest.Podman([]string{"run", "--rm", "--name", "test", ALPINE, "cat", "/etc/hosts"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).ToNot(ContainSubstring("test")) Expect(session.OutputToString()).ToNot(ContainSubstring("test"))
session = podmanTest.Podman([]string{"run", "--rm", "--name", "test", "--no-hosts=false", ALPINE, "cat", "/etc/hosts"}) session = podmanTest.Podman([]string{"run", "--rm", "--name", "test", "--no-hosts=false", ALPINE, "cat", "/etc/hosts"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To(ContainSubstring("test")) Expect(session.OutputToString()).To(ContainSubstring("test"))
}) })
It("seccomp profile path", func() { It("seccomp profile path", func() {
@ -462,7 +463,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"}) session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).To(ContainSubstring(storagePath)) Expect(session.OutputToString()).To(ContainSubstring(storagePath))
containersConf = []byte("[engine]\nimage_copy_tmp_dir=\"storage1\"") containersConf = []byte("[engine]\nimage_copy_tmp_dir=\"storage1\"")
err = os.WriteFile(configPath, containersConf, os.ModePerm) err = os.WriteFile(configPath, containersConf, os.ModePerm)
@ -472,7 +473,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"}) session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125)) Expect(session).Should(Exit(125))
Expect(session.Err.Contents()).To(ContainSubstring("invalid image_copy_tmp_dir value \"storage1\" (relative paths are not accepted)")) Expect(session.ErrorToString()).To(ContainSubstring("invalid image_copy_tmp_dir value \"storage1\" (relative paths are not accepted)"))
os.Setenv("TMPDIR", "/hoge") os.Setenv("TMPDIR", "/hoge")
session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"}) session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"})
@ -490,7 +491,7 @@ var _ = Describe("Verify podman containers.conf usage", func() {
result := podmanTest.Podman([]string{"system", "service", "--help"}) result := podmanTest.Podman([]string{"system", "service", "--help"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0)) Expect(result).Should(Exit(0))
Expect(result.Out.Contents()).To(ContainSubstring("(default 1234)")) Expect(result.OutputToString()).To(ContainSubstring("(default 1234)"))
}) })
It("bad infra_image name", func() { It("bad infra_image name", func() {
@ -512,17 +513,17 @@ var _ = Describe("Verify podman containers.conf usage", func() {
result := podmanTest.Podman([]string{"pod", "create", "--infra-image", infra2}) result := podmanTest.Podman([]string{"pod", "create", "--infra-image", infra2})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(125)) Expect(result).Should(Exit(125))
Expect(result.Err.Contents()).To(ContainSubstring(error2String)) Expect(result.ErrorToString()).To(ContainSubstring(error2String))
result = podmanTest.Podman([]string{"pod", "create"}) result = podmanTest.Podman([]string{"pod", "create"})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(125)) Expect(result).Should(Exit(125))
Expect(result.Err.Contents()).To(ContainSubstring(errorString)) Expect(result.ErrorToString()).To(ContainSubstring(errorString))
result = podmanTest.Podman([]string{"create", "--pod", "new:pod1", ALPINE}) result = podmanTest.Podman([]string{"create", "--pod", "new:pod1", ALPINE})
result.WaitWithDefaultTimeout() result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(125)) Expect(result).Should(Exit(125))
Expect(result.Err.Contents()).To(ContainSubstring(errorString)) Expect(result.ErrorToString()).To(ContainSubstring(errorString))
}) })
It("set .engine.remote=true", func() { It("set .engine.remote=true", func() {

View File

@ -277,4 +277,20 @@ function check_label() {
is "$output" "${RELABEL} $tmpdir" "Shared Relabel Correctly" is "$output" "${RELABEL} $tmpdir" "Shared Relabel Correctly"
} }
@test "podman selinux nested" {
skip_if_no_selinux
ROOTCONTEXT='rw,rootcontext="system_u:object_r:container_file_t:s0:c1,c2"'
SELINUXMNT="selinuxfs.*(rw,nosuid,noexec,relatime)"
SELINUXMNT="tmpfs.*selinux.*\(ro"
run_podman run --rm --security-opt label=level:s0:c1,c2 $IMAGE mount
assert "$output" !~ "${ROOTCONTEXT}" "Don't use rootcontext"
assert "$output" =~ "${SELINUXMNT}" "Mount SELinux file system readwrite"
run_podman run --rm --security-opt label=nested --security-opt label=level:s0:c1,c2 $IMAGE mount
assert "$output" =~ "${ROOTCONTEXT}" "Uses rootcontext"
assert "$output" =~ "${SELINUXMNT}" "Mount SELinux file system readwrite"
}
# vim: filetype=sh # vim: filetype=sh