Skip to content

Commit 0a79d30

Browse files
authored
Merge pull request containerd#3533 from austinvazquez/support-systempaths-unconfined
Add container run --security-opt systempaths=unconfined
2 parents a4a7282 + c3c3f91 commit 0a79d30

File tree

4 files changed

+69
-1
lines changed

4 files changed

+69
-1
lines changed

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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,61 @@ 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+
var confined []string
204+
205+
for _, path := range []string{
206+
"/proc/kcore",
207+
"/proc/keys",
208+
"/proc/latency_stats",
209+
"/proc/sched_debug",
210+
"/proc/scsi",
211+
"/proc/timer_list",
212+
"/proc/timer_stats",
213+
"/sys/firmware",
214+
"/sys/fs/selinux",
215+
} {
216+
// Not each distribution will support every masked path here.
217+
if strings.Contains(defaultContainerOutput, path) {
218+
confined = append(confined, path)
219+
}
220+
}
221+
222+
assert.Check(t, len(confined) != 0, "Default container has no confined paths to validate")
223+
224+
result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmnt).Run()
225+
unconfinedContainerOutput := result.Combined()
226+
227+
for _, path := range confined {
228+
assert.Assert(t, !strings.Contains(unconfinedContainerOutput, path), fmt.Sprintf("%s should not be masked when unconfined", path))
229+
}
230+
231+
for _, path := range []string{
232+
"/proc/acpi",
233+
"/proc/bus",
234+
"/proc/fs",
235+
"/proc/irq",
236+
"/proc/sysrq-trigger",
237+
"/proc/sys",
238+
} {
239+
findmntPath := fmt.Sprintf("`apk add -q findmnt && findmnt %s`", path)
240+
241+
result := base.Cmd("run", "--rm", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
242+
243+
// Not each distribution will support every read-only path here.
244+
if strings.Contains(result.Combined(), path) {
245+
result = base.Cmd("run", "--rm", "--security-opt", "systempaths=unconfined", testutil.AlpineImage, "sh", "-euxc", findmntPath).Run()
246+
assert.Assert(t, !strings.Contains(result.Combined(), "ro,"), fmt.Sprintf("%s should not be read-only when unconfined", path))
247+
}
248+
}
249+
}
250+
196251
func TestRunPrivileged(t *testing.T) {
197252
// docker does not support --privileged-without-host-devices
198253
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)