Skip to content

Commit a95a8d6

Browse files
committed
Add container run --security-opt systempaths=unconfined
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 <macedonv@amazon.com>
1 parent 7eaaecb commit a95a8d6

4 files changed

Lines changed: 53 additions & 1 deletion

File tree

cmd/nerdctl/container/container_run.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func setCreateFlags(cmd *cobra.Command) {
183183
"seccomp=", "seccomp=" + defaults.SeccompProfileName, "seccomp=unconfined",
184184
"apparmor=", "apparmor=" + defaults.AppArmorProfileName, "apparmor=unconfined",
185185
"no-new-privileges",
186+
"systempaths=unconfined",
186187
"privileged-without-host-devices"}, cobra.ShellCompDirectiveNoFileComp
187188
})
188189
// 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)

cmd/nerdctl/container/container_run_security_linux_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,45 @@ func TestRunSeccompCapSysPtrace(t *testing.T) {
193193
// Docker/Moby 's seccomp profile allows ptrace(2) by default, but containerd does not (yet): https://github.com/containerd/containerd/issues/6802
194194
}
195195

196+
func TestRunSystemPathsUnconfined(t *testing.T) {
197+
base := testutil.NewBase(t)
198+
199+
const findmnt = "`apk add -q findmnt && findmnt -R /proc && findmnt -R /sys`"
200+
result := base.Cmd("run", "--rm", testutil.AlpineImage, "sh", "-euxc", findmnt).Run()
201+
defaultContainerOutput := result.Combined()
202+
203+
result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmnt).Run()
204+
unconfinedContainerOutput := result.Combined()
205+
206+
for _, path := range []string{
207+
"/proc/kcore",
208+
"/proc/keys",
209+
"/proc/latency_stats",
210+
"/proc/timer_list",
211+
"/sys/firmware",
212+
} {
213+
assert.Check(t, strings.Contains(defaultContainerOutput, path), fmt.Sprintf("%s should be masked by default", path))
214+
assert.Assert(t, !strings.Contains(unconfinedContainerOutput, path), fmt.Sprintf("%s should not be masked when unconfined", path))
215+
}
216+
217+
for _, path := range []string{
218+
"/proc/acpi",
219+
"/proc/bus",
220+
"/proc/fs",
221+
"/proc/irq",
222+
"/proc/sysrq-trigger",
223+
"/proc/sys",
224+
} {
225+
findmntPath := fmt.Sprintf("`apk add -q findmnt && findmnt %s`", path)
226+
227+
result := base.Cmd("run", "--rm", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
228+
assert.Check(t, strings.Contains(result.Combined(), "ro,"), fmt.Sprintf("%s should be read-only by default", path))
229+
230+
result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
231+
assert.Assert(t, !strings.Contains(result.Combined(), "ro,"), fmt.Sprintf("%s should not be read-only when unconfined", path))
232+
}
233+
}
234+
196235
func TestRunPrivileged(t *testing.T) {
197236
// docker does not support --privileged-without-host-devices
198237
testutil.DockerIncompatible(t)

docs/command-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ Security flags:
230230
- :whale: `--security-opt seccomp=<PROFILE_JSON_FILE>`: specify custom seccomp profile
231231
- :whale: `--security-opt apparmor=<PROFILE>`: specify custom AppArmor profile
232232
- :whale: `--security-opt no-new-privileges`: disallow privilege escalation, e.g., setuid and file capabilities
233+
- :whale: `--security-opt systempaths=unconfined`: Turn off confinement for system paths (masked paths, read-only paths) for the container
233234
- :nerd_face: `--security-opt privileged-without-host-devices`: Don't pass host devices to privileged containers
234235
- :whale: `--cap-add=<CAP>`: Add Linux capabilities
235236
- :whale: `--cap-drop=<CAP>`: Drop Linux capabilities

pkg/cmd/container/run_security_linux.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ var privilegedWithoutDevicesOpts = []oci.SpecOpts{
4545
oci.WithNewPrivileges,
4646
}
4747

48+
const (
49+
systemPathsUnconfined = "unconfined"
50+
)
51+
4852
func generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([]oci.SpecOpts, error) {
4953
for k := range securityOptsMap {
5054
switch k {
51-
case "seccomp", "apparmor", "no-new-privileges", "privileged-without-host-devices":
55+
case "seccomp", "apparmor", "no-new-privileges", "systempaths", "privileged-without-host-devices":
5256
default:
5357
log.L.Warnf("unknown security-opt: %q", k)
5458
}
@@ -99,6 +103,13 @@ func generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([
99103
opts = append(opts, oci.WithNewPrivileges)
100104
}
101105

106+
if value, ok := securityOptsMap["systempaths"]; ok && value == systemPathsUnconfined {
107+
opts = append(opts, oci.WithMaskedPaths(nil))
108+
opts = append(opts, oci.WithReadonlyPaths(nil))
109+
} else if ok && value != systemPathsUnconfined {
110+
return nil, errors.New(`invalid security-opt "systempaths=unconfined"`)
111+
}
112+
102113
privilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, "privileged-without-host-devices")
103114
if err != nil {
104115
return nil, err

0 commit comments

Comments
 (0)