diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index fa17a8859f..e95c855393 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -485,6 +485,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con return nil, err } + securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts) + storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll()) if err != nil { return nil, err @@ -635,6 +637,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con Sysctls: copts.sysctls.GetAll(), Runtime: copts.runtime, Mounts: mounts, + MaskedPaths: maskedPaths, + ReadonlyPaths: readonlyPaths, } if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() { @@ -825,6 +829,25 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { return securityOpts, nil } +// parseSystemPaths checks if `systempaths=unconfined` security option is set, +// and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated +// list of security options is returned with this option removed, because the +// `unconfined` option is handled client-side, and should not be sent to the +// daemon. +func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPaths []string) { + filtered = securityOpts[:0] + for _, opt := range securityOpts { + if opt == "systempaths=unconfined" { + maskedPaths = []string{} + readonlyPaths = []string{} + } else { + filtered = append(filtered, opt) + } + } + + return filtered, maskedPaths, readonlyPaths +} + // parses storage options per container into a map func parseStorageOpts(storageOpts []string) (map[string]string, error) { m := make(map[string]string) diff --git a/cli/command/container/opts_test.go b/cli/command/container/opts_test.go index bf6387162c..6f0ad528fe 100644 --- a/cli/command/container/opts_test.go +++ b/cli/command/container/opts_test.go @@ -800,3 +800,57 @@ func TestValidateDevice(t *testing.T) { } } } + +func TestParseSystemPaths(t *testing.T) { + tests := []struct { + doc string + in, out, masked, readonly []string + }{ + { + doc: "not set", + in: []string{}, + out: []string{}, + }, + { + doc: "not set, preserve other options", + in: []string{ + "seccomp=unconfined", + "apparmor=unconfined", + "label=user:USER", + "foo=bar", + }, + out: []string{ + "seccomp=unconfined", + "apparmor=unconfined", + "label=user:USER", + "foo=bar", + }, + }, + { + doc: "unconfined", + in: []string{"systempaths=unconfined"}, + out: []string{}, + masked: []string{}, + readonly: []string{}, + }, + { + doc: "unconfined and other options", + in: []string{"foo=bar", "bar=baz", "systempaths=unconfined"}, + out: []string{"foo=bar", "bar=baz"}, + masked: []string{}, + readonly: []string{}, + }, + { + doc: "unknown option", + in: []string{"foo=bar", "systempaths=unknown", "bar=baz"}, + out: []string{"foo=bar", "systempaths=unknown", "bar=baz"}, + }, + } + + for _, tc := range tests { + securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(tc.in) + assert.DeepEqual(t, securityOpts, tc.out) + assert.DeepEqual(t, maskedPaths, tc.masked) + assert.DeepEqual(t, readonlyPaths, tc.readonly) + } +}