Skip to content

Commit

Permalink
oci: Support --no-privs
Browse files Browse the repository at this point in the history
When `--no-privs` is set on the command line:

* The container process capability set should be empty.
* NoNewPrivileges should be enabled for the container process.

Fixes sylabs/singularity#1477

Signed-off-by: Edita Kizinevic <[email protected]>
  • Loading branch information
dtrudg authored and edytuk committed Jul 24, 2023
1 parent 2468776 commit e8754ae
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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

Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/cli/action_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,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"},
}

Expand Down
50 changes: 27 additions & 23 deletions e2e/security/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -47,41 +48,44 @@ 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,
expectBnd: ociDefaultCapString,
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,
},
}

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),
),
)
}
}
}
3 changes: 0 additions & 3 deletions internal/pkg/runtime/launcher/oci/launcher_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
26 changes: 19 additions & 7 deletions internal/pkg/runtime/launcher/oci/process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit e8754ae

Please sign in to comment.