Skip to content

Commit

Permalink
Add container run --security-opt systempaths=unconfined
Browse files Browse the repository at this point in the history
This change adds security option to turn off confinement for system
paths (masked paths, read-only paths) for the container.

Signed-off-by: Austin Vazquez <[email protected]>
  • Loading branch information
austinvazquez committed Oct 12, 2024
1 parent 7eaaecb commit a95a8d6
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/nerdctl/container/container_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func setCreateFlags(cmd *cobra.Command) {
"seccomp=", "seccomp=" + defaults.SeccompProfileName, "seccomp=unconfined",
"apparmor=", "apparmor=" + defaults.AppArmorProfileName, "apparmor=unconfined",
"no-new-privileges",
"systempaths=unconfined",
"privileged-without-host-devices"}, cobra.ShellCompDirectiveNoFileComp
})
// cap-add and cap-drop are defined as StringSlice, not StringArray, to allow specifying "--cap-add=CAP_SYS_ADMIN,CAP_NET_ADMIN" (compatible with Podman)
Expand Down
39 changes: 39 additions & 0 deletions cmd/nerdctl/container/container_run_security_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,45 @@ func TestRunSeccompCapSysPtrace(t *testing.T) {
// Docker/Moby 's seccomp profile allows ptrace(2) by default, but containerd does not (yet): https://github.com/containerd/containerd/issues/6802
}

func TestRunSystemPathsUnconfined(t *testing.T) {
base := testutil.NewBase(t)

const findmnt = "`apk add -q findmnt && findmnt -R /proc && findmnt -R /sys`"
result := base.Cmd("run", "--rm", testutil.AlpineImage, "sh", "-euxc", findmnt).Run()
defaultContainerOutput := result.Combined()

result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmnt).Run()
unconfinedContainerOutput := result.Combined()

for _, path := range []string{
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/sys/firmware",
} {
assert.Check(t, strings.Contains(defaultContainerOutput, path), fmt.Sprintf("%s should be masked by default", path))
assert.Assert(t, !strings.Contains(unconfinedContainerOutput, path), fmt.Sprintf("%s should not be masked when unconfined", path))
}

for _, path := range []string{
"/proc/acpi",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sysrq-trigger",
"/proc/sys",
} {
findmntPath := fmt.Sprintf("`apk add -q findmnt && findmnt %s`", path)

result := base.Cmd("run", "--rm", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
assert.Check(t, strings.Contains(result.Combined(), "ro,"), fmt.Sprintf("%s should be read-only by default", path))

result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
assert.Assert(t, !strings.Contains(result.Combined(), "ro,"), fmt.Sprintf("%s should not be read-only when unconfined", path))
}
}

func TestRunPrivileged(t *testing.T) {
// docker does not support --privileged-without-host-devices
testutil.DockerIncompatible(t)
Expand Down
1 change: 1 addition & 0 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Security flags:
- :whale: `--security-opt seccomp=<PROFILE_JSON_FILE>`: specify custom seccomp profile
- :whale: `--security-opt apparmor=<PROFILE>`: specify custom AppArmor profile
- :whale: `--security-opt no-new-privileges`: disallow privilege escalation, e.g., setuid and file capabilities
- :whale: `--security-opt systempaths=unconfined`: Turn off confinement for system paths (masked paths, read-only paths) for the container
- :nerd_face: `--security-opt privileged-without-host-devices`: Don't pass host devices to privileged containers
- :whale: `--cap-add=<CAP>`: Add Linux capabilities
- :whale: `--cap-drop=<CAP>`: Drop Linux capabilities
Expand Down
13 changes: 12 additions & 1 deletion pkg/cmd/container/run_security_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ var privilegedWithoutDevicesOpts = []oci.SpecOpts{
oci.WithNewPrivileges,
}

const (
systemPathsUnconfined = "unconfined"
)

func generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([]oci.SpecOpts, error) {
for k := range securityOptsMap {
switch k {
case "seccomp", "apparmor", "no-new-privileges", "privileged-without-host-devices":
case "seccomp", "apparmor", "no-new-privileges", "systempaths", "privileged-without-host-devices":
default:
log.L.Warnf("unknown security-opt: %q", k)
}
Expand Down Expand Up @@ -99,6 +103,13 @@ func generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([
opts = append(opts, oci.WithNewPrivileges)
}

if value, ok := securityOptsMap["systempaths"]; ok && value == systemPathsUnconfined {
opts = append(opts, oci.WithMaskedPaths(nil))
opts = append(opts, oci.WithReadonlyPaths(nil))
} else if ok && value != systemPathsUnconfined {
return nil, errors.New(`invalid security-opt "systempaths=unconfined"`)
}

privilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, "privileged-without-host-devices")
if err != nil {
return nil, err
Expand Down

0 comments on commit a95a8d6

Please sign in to comment.