diff --git a/CHANGELOG.md b/CHANGELOG.md index dad561aae3..472aa38339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,8 @@ For older changes see the [archived Singularity change log](https://github.com/a `fuse-overlayfs` and `fusermount` for overlay mounting and unmounting. - OCI-mode now suppports the `APPTAINER_CONTAINLIBS` env var, to specify libraries to bind into `/.singularity.d/libs/` in the container. +- OCI-mode now supports the `--no-privs` flag to drop all capabilities from the + container process, and enable the NoNewPrivileges flag. ### Developer / API diff --git a/cmd/internal/cli/action_flags.go b/cmd/internal/cli/action_flags.go index ac7c3453dd..3d62f3fe8b 100644 --- a/cmd/internal/cli/action_flags.go +++ b/cmd/internal/cli/action_flags.go @@ -622,7 +622,7 @@ var actionNoPrivsFlag = cmdline.Flag{ Value: &noPrivs, DefaultValue: false, Name: "no-privs", - Usage: "drop all privileges from root user in container)", + Usage: "drop all privileges in container (root only in non-OCI mode)", EnvKeys: []string{"NO_PRIVS"}, } diff --git a/e2e/security/oci.go b/e2e/security/oci.go index d731f967b5..0a38125df0 100644 --- a/e2e/security/oci.go +++ b/e2e/security/oci.go @@ -28,7 +28,8 @@ func (c ctx) ociCapabilities(t *testing.T) { tests := []struct { name string - profile e2e.Profile + options []string + profiles []e2e.Profile expectInh string expectPrm string expectEff string @@ -38,7 +39,7 @@ func (c ctx) ociCapabilities(t *testing.T) { }{ { name: "DefaultUser", - profile: e2e.OCIUserProfile, + profiles: []e2e.Profile{e2e.OCIUserProfile}, expectInh: nullCapString, expectPrm: nullCapString, expectEff: nullCapString, @@ -47,7 +48,7 @@ func (c ctx) ociCapabilities(t *testing.T) { }, { name: "DefaultRoot", - profile: e2e.OCIRootProfile, + profiles: []e2e.Profile{e2e.OCIRootProfile, e2e.OCIFakerootProfile}, expectInh: nullCapString, expectPrm: ociDefaultCapString, expectEff: ociDefaultCapString, @@ -55,12 +56,13 @@ func (c ctx) ociCapabilities(t *testing.T) { expectAmb: nullCapString, }, { - name: "DefaultFakeroot", - profile: e2e.OCIRootProfile, + name: "NoPrivs", + options: []string{"--no-privs"}, + profiles: []e2e.Profile{e2e.OCIRootProfile, e2e.OCIFakerootProfile, e2e.OCIUserProfile}, expectInh: nullCapString, - expectPrm: ociDefaultCapString, - expectEff: ociDefaultCapString, - expectBnd: ociDefaultCapString, + expectPrm: nullCapString, + expectEff: nullCapString, + expectBnd: nullCapString, expectAmb: nullCapString, }, } @@ -68,20 +70,22 @@ func (c ctx) ociCapabilities(t *testing.T) { e2e.EnsureImage(t, c.env) for _, tt := range tests { - args := []string{imageRef, "grep", "^Cap...:", "/proc/self/status"} - c.env.RunApptainer( - t, - e2e.AsSubtest(tt.name), - e2e.WithProfile(tt.profile), - e2e.WithCommand("exec"), - e2e.WithArgs(args...), - e2e.ExpectExit(tt.expectExit, - e2e.ExpectOutput(e2e.ContainMatch, "CapInh:\t"+tt.expectInh), - e2e.ExpectOutput(e2e.ContainMatch, "CapPrm:\t"+tt.expectPrm), - e2e.ExpectOutput(e2e.ContainMatch, "CapEff:\t"+tt.expectEff), - e2e.ExpectOutput(e2e.ContainMatch, "CapBnd:\t"+tt.expectBnd), - e2e.ExpectOutput(e2e.ContainMatch, "CapAmb:\t"+tt.expectAmb), - ), - ) + for _, p := range tt.profiles { + args := append(tt.options, imageRef, "grep", "^Cap...:", "/proc/self/status") + c.env.RunApptainer( + t, + e2e.AsSubtest(tt.name+"/"+p.String()), + e2e.WithProfile(p), + e2e.WithCommand("exec"), + e2e.WithArgs(args...), + e2e.ExpectExit(tt.expectExit, + e2e.ExpectOutput(e2e.ContainMatch, "CapInh:\t"+tt.expectInh), + e2e.ExpectOutput(e2e.ContainMatch, "CapPrm:\t"+tt.expectPrm), + e2e.ExpectOutput(e2e.ContainMatch, "CapEff:\t"+tt.expectEff), + e2e.ExpectOutput(e2e.ContainMatch, "CapBnd:\t"+tt.expectBnd), + e2e.ExpectOutput(e2e.ContainMatch, "CapAmb:\t"+tt.expectAmb), + ), + ) + } } } diff --git a/internal/pkg/runtime/launcher/oci/launcher_linux.go b/internal/pkg/runtime/launcher/oci/launcher_linux.go index e1a45e66d7..6a09188446 100644 --- a/internal/pkg/runtime/launcher/oci/launcher_linux.go +++ b/internal/pkg/runtime/launcher/oci/launcher_linux.go @@ -149,9 +149,6 @@ func checkOpts(lo launcher.Options) error { if lo.KeepPrivs { badOpt = append(badOpt, "KeepPrivs") } - if lo.NoPrivs { - badOpt = append(badOpt, "NoPrivs") - } if len(lo.SecurityOpts) > 0 { badOpt = append(badOpt, "SecurityOpts") } diff --git a/internal/pkg/runtime/launcher/oci/process_linux.go b/internal/pkg/runtime/launcher/oci/process_linux.go index 7b7babe930..2fcb648cb2 100644 --- a/internal/pkg/runtime/launcher/oci/process_linux.go +++ b/internal/pkg/runtime/launcher/oci/process_linux.go @@ -62,13 +62,21 @@ func (l *Launcher) getProcess(ctx context.Context, imgSpec imgspecv1.Image, imag return nil, err } + // OCI default is NoNewPrivileges = false + noNewPrivs := false + // --no-privs sets NoNewPrivileges + if l.cfg.NoPrivs { + noNewPrivs = true + } + p := specs.Process{ - Args: getProcessArgs(imgSpec, process, args), - Capabilities: getProcessCapabilities(u.UID), - Cwd: cwd, - Env: getProcessEnv(imgSpec, rtEnv), - User: u, - Terminal: getProcessTerminal(), + Args: getProcessArgs(imgSpec, process, args), + Capabilities: l.getProcessCapabilities(u.UID), + Cwd: cwd, + Env: getProcessEnv(imgSpec, rtEnv), + NoNewPrivileges: noNewPrivs, + User: u, + Terminal: getProcessTerminal(), } return &p, nil @@ -339,7 +347,11 @@ func envFileMap(ctx context.Context, f string) (map[string]string, error) { // getProcessCapabilities returns the capabilities that are enabled for the // container. These follow OCI specified defaults. A non-root container user has // no effective/permitted capabilities. -func getProcessCapabilities(targetUID uint32) *specs.LinuxCapabilities { +func (l *Launcher) getProcessCapabilities(targetUID uint32) *specs.LinuxCapabilities { + if l.cfg.NoPrivs { + return &specs.LinuxCapabilities{} + } + if targetUID == 0 { return &specs.LinuxCapabilities{ Bounding: oci.DefaultCaps,