From de6c374805355776f90cc52dad260c92587eae85 Mon Sep 17 00:00:00 2001 From: David Trudgian Date: Fri, 16 Jun 2023 13:53:01 +0100 Subject: [PATCH] oci: Support --no-mount, to extent possible Allow the `--no-mount` flag to be specified in `--oci` mode. This allows disabling the following mounts: * proc * sys * devpts * tmp * home Note that `dev` cannot be supported in `--oci` mode, as an OCI runtime *requires* that certain devices are present, and will include them in a `/dev` tmpfs. We currently run similar to native mode `--compat`, so we don't mount the current working directory. Therefore, `--no-mount cwd` is not supported. Similarly, `--compat` infers that `bind path` entries from `singularity.conf` are ignored. We may handle them in some way, in future. Fixes sylabs/singularity#1781 Signed-off-by: Edita Kizinevic --- CHANGELOG.md | 3 + e2e/actions/actions.go | 1 + e2e/actions/oci.go | 111 ++++++++++++++++++ .../runtime/launcher/oci/launcher_linux.go | 9 +- .../pkg/runtime/launcher/oci/mounts_linux.go | 24 +++- 5 files changed, 145 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a26b5d16..9b752d67f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,9 @@ For older changes see the [archived Singularity change log](https://github.com/a `/scratch` on the host, rather than to tmpfs storage. - OCI-mode now supports the `--no-home` flag, to prevent the container home directory from being mounted. +- OCI-mode now supports the `--no-mount` flag to disable the `proc`, `sys`, + `devpts`, `tmp`, and `home` mounts in the container. `dev` cannot be disabled + in OCI-mode, and `bind-path` mounts are not supported. ### Developer / API diff --git a/e2e/actions/actions.go b/e2e/actions/actions.go index edd4d04419..9583e9f030 100644 --- a/e2e/actions/actions.go +++ b/e2e/actions/actions.go @@ -2947,5 +2947,6 @@ func E2ETests(env e2e.TestEnv) testhelper.Tests { "ociCompat": np(c.actionOciCompat), // --oci equivalence to native mode --compat "ociOverlay": (c.actionOciOverlay), // --overlay in OCI mode "ociOverlayTeardown": np(c.actionOciOverlayTeardown), // proper overlay unmounting in OCI mode + "ociNo-mount": c.actionOciNoMount, // --no-mount in OCI mode } } diff --git a/e2e/actions/oci.go b/e2e/actions/oci.go index d1534cd319..871093bc18 100644 --- a/e2e/actions/oci.go +++ b/e2e/actions/oci.go @@ -1462,3 +1462,114 @@ func (c actionTests) ociSTDPipe(t *testing.T) { ) } } + +func (c actionTests) actionOciNoMount(t *testing.T) { + e2e.EnsureOCIArchive(t, c.env) + imageRef := "oci-archive:" + c.env.OCIArchivePath + + tests := []struct { + name string + noMount string + noMatch string + warnMatch string + exit int + }{ + { + name: "proc", + noMount: "proc", + noMatch: "on /proc", + exit: 1, // mount fails with exit code 1 when there is no `/proc` + }, + { + name: "sys", + noMount: "sys", + noMatch: "on /sys", + exit: 0, + }, + { + name: "dev", + noMount: "dev", + warnMatch: "--no-mount dev is not supported in OCI mode, ignoring.", + exit: 0, + }, + { + name: "devpts", + noMount: "devpts", + noMatch: "on /dev/pts", + exit: 0, + }, + { + name: "tmp", + noMount: "tmp", + noMatch: "on /tmp", + exit: 0, + }, + { + name: "home", + noMount: "home", + noMatch: "on /home", + exit: 0, + }, + { + name: "cwd", + noMount: "cwd", + warnMatch: "--no-mount cwd is not supported in OCI mode, ignoring.", + exit: 0, + }, + // + // TODO - apptainer.conf bind path handling is not implemented yet in OCI mode. + // If/when it is, consider how to handle below. + // + // /etc/hosts & /etc/localtime are default 'bind path' entries we should + // be able to disable by abs path. Although other 'bind path' entries + // are ignored under '--contain' these two are handled specially in + // addBindsMount(), so make sure that `--no-mount` applies properly + // under contain also. + { + name: "/etc/hosts", + noMount: "/etc/hosts", + // noMatch: "on /etc/hosts", + warnMatch: "--no-mount /etc/hosts is not supported in OCI mode, ignoring.", + exit: 0, + }, + { + name: "/etc/localtime", + noMount: "/etc/localtime", + // noMatch: "on /etc/localtime", + warnMatch: "--no-mount /etc/localtime is not supported in OCI mode, ignoring.", + exit: 0, + }, + // bind-paths should disable all of the bind path mounts - including both defaults + { + name: "binds-paths-hosts", + noMount: "bind-paths", + // noMatch: "on /etc/hosts", + warnMatch: "--no-mount bind-paths is not supported in OCI mode, ignoring.", + exit: 0, + }, + { + name: "bind-paths-localtime", + noMount: "bind-paths", + // noMatch: "on /etc/localtime", + warnMatch: "--no-mount bind-paths is not supported in OCI mode, ignoring.", + exit: 0, + }, + } + + for _, tt := range tests { + + expectOp := e2e.ExpectOutput(e2e.UnwantedContainMatch, tt.noMatch) + if tt.warnMatch != "" { + expectOp = e2e.ExpectError(e2e.ContainMatch, tt.warnMatch) + } + + c.env.RunApptainer( + t, + e2e.AsSubtest(tt.name), + e2e.WithProfile(e2e.OCIUserProfile), + e2e.WithCommand("exec"), + e2e.WithArgs("--no-mount", tt.noMount, imageRef, "mount"), + e2e.ExpectExit(tt.exit, expectOp), + ) + } +} diff --git a/internal/pkg/runtime/launcher/oci/launcher_linux.go b/internal/pkg/runtime/launcher/oci/launcher_linux.go index 4fcb601d48..9d0d0e871f 100644 --- a/internal/pkg/runtime/launcher/oci/launcher_linux.go +++ b/internal/pkg/runtime/launcher/oci/launcher_linux.go @@ -35,6 +35,7 @@ import ( "github.com/apptainer/apptainer/pkg/ocibundle/tools" "github.com/apptainer/apptainer/pkg/sylog" "github.com/apptainer/apptainer/pkg/util/apptainerconf" + "github.com/apptainer/apptainer/pkg/util/slice" "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" "github.com/google/uuid" lccgroups "github.com/opencontainers/runc/libcontainer/cgroups" @@ -44,6 +45,8 @@ import ( var ( ErrUnsupportedOption = errors.New("not supported by OCI launcher") ErrNotImplemented = errors.New("not implemented by OCI launcher") + + unsupportedNoMount = []string{"dev", "cwd", "bind-paths"} ) // Launcher will holds configuration for, and will launch a container using an @@ -107,8 +110,10 @@ func checkOpts(lo launcher.Options) error { badOpt = append(badOpt, "FuseMount") } - if len(lo.NoMount) > 0 { - badOpt = append(badOpt, "NoMount") + for _, nm := range lo.NoMount { + if strings.HasPrefix(nm, "/") || slice.ContainsString(unsupportedNoMount, nm) { + sylog.Warningf("--no-mount %s is not supported in OCI mode, ignoring.", nm) + } } if lo.NvCCLI { diff --git a/internal/pkg/runtime/launcher/oci/mounts_linux.go b/internal/pkg/runtime/launcher/oci/mounts_linux.go index e2a85efb38..aa9aa88dcc 100644 --- a/internal/pkg/runtime/launcher/oci/mounts_linux.go +++ b/internal/pkg/runtime/launcher/oci/mounts_linux.go @@ -25,6 +25,7 @@ import ( "github.com/apptainer/apptainer/internal/pkg/util/user" "github.com/apptainer/apptainer/pkg/sylog" "github.com/apptainer/apptainer/pkg/util/bind" + "github.com/apptainer/apptainer/pkg/util/slice" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -77,6 +78,10 @@ func (l *Launcher) addTmpMounts(mounts *[]specs.Mount) error { sylog.Debugf("Skipping mount of /tmp due to apptainer.conf") return nil } + if slice.ContainsString(l.cfg.NoMount, "tmp") { + sylog.Debugf("Skipping mount of /tmp due to --no-mount") + return nil + } if len(l.cfg.WorkDir) > 0 { sylog.Debugf("WorkDir specification provided: %s", l.cfg.WorkDir) @@ -193,7 +198,6 @@ func (l *Launcher) addDevMounts(mounts *[]specs.Mount) error { fmt.Sprintf("size=%dm", l.apptainerConf.SessiondirMaxSize), }, }, - ptsMount, specs.Mount{ Destination: "/dev/shm", Type: "tmpfs", @@ -214,6 +218,12 @@ func (l *Launcher) addDevMounts(mounts *[]specs.Mount) error { }, ) + if slice.ContainsString(l.cfg.NoMount, "devpts") { + sylog.Debugf("Skipping mount of /dev/pts due to --no-mount") + return nil + } + + *mounts = append(*mounts, ptsMount) return nil } @@ -223,6 +233,10 @@ func (l *Launcher) addProcMount(mounts *[]specs.Mount) { sylog.Debugf("Skipping mount of /proc due to apptainer.conf") return } + if slice.ContainsString(l.cfg.NoMount, "proc") { + sylog.Debugf("Skipping mount of /proc due to --no-mount") + return + } *mounts = append(*mounts, specs.Mount{ @@ -238,6 +252,10 @@ func (l *Launcher) addSysMount(mounts *[]specs.Mount) error { sylog.Debugf("Skipping mount of /sys due to apptainer.conf") return nil } + if slice.ContainsString(l.cfg.NoMount, "sys") { + sylog.Debugf("Skipping mount of /sys due to --no-mount") + return nil + } rootlessUID, err := rootless.Getuid() if err != nil { @@ -276,6 +294,10 @@ func (l *Launcher) addHomeMount(mounts *[]specs.Mount) error { sylog.Debugf("Skipping mount of $HOME due to --no-home") return nil } + if slice.ContainsString(l.cfg.NoMount, "home") { + sylog.Debugf("Skipping mount of /home due to --no-mount") + return nil + } if l.homeDest == "" { return fmt.Errorf("cannot add home mount with empty destination")