From d77287e639bf3c1d1052750e5ee01f967285002c Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Thu, 2 Jun 2016 09:53:08 +0200 Subject: [PATCH 1/3] spec/ace: add two linux-specific seccomp isolators --- examples/image.json | 11 ++++++++ spec/ace.md | 69 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/examples/image.json b/examples/image.json index 7f9a4c72..9f811d5a 100644 --- a/examples/image.json +++ b/examples/image.json @@ -60,6 +60,17 @@ { "name": "os/linux/no-new-privileges", "value": true + }, + { + "name": "os/linux/seccomp-remove-set", + "value": { + "errno": "EACCESS", + "set": [ + "clock_settime", + "clock_adjtime", + "reboot" + ] + } } ], "mountPoints": [ diff --git a/spec/ace.md b/spec/ace.md index 296257d7..87712a62 100644 --- a/spec/ace.md +++ b/spec/ace.md @@ -131,7 +131,74 @@ An executor MAY implement a "strict mode" where an image cannot run unless all i ### Linux Isolators These isolators are specific to the Linux kernel and are impossible to represent as a 1-to-1 mapping on other kernels. -The first example is "capabilities" but this will be expanded to include things such as SELinux, SMACK or AppArmor. +This set includes Linux-specific isolators, which may relies on kernel technologies like seccomp, SELinux, SMACK or AppArmor. + +#### os/linux/seccomp-remove-set + +* Scope: app + +**Parameters:** + +* **set** case-sensitive list of syscall names that will be blacklisted (ie. blocked); values starting with `@` MUST be handled as scoped special values (see notes below). This field MUST NOT be empty. All syscalls specified in this set MUST be blocked. This set MAY be augmented by an implementation-specific default blacklist set (see notes below). Syscalls not specified in the union of these two sets MUST NOT be blocked. +* **errno** all-uppercase name of a single [errno code](http://man7.org/linux/man-pages/man3/errno.3.html) that will be returned by blocked system calls, instead of terminating. If missing or empty, by default blocked syscalls MUST result in app termination via `SIGSYS` signal. All codes defined by POSIX.1-2001 and C99 MUST be supported. Implementations SHOULD support all custom Linux codes, but MAY ignore locally unsupported ones. + +**Notes:** + 1. Only a single `os/linux/seccomp-remove-set` isolator can be specified per-app, and it cannot be used in conjunction with the `os/linux/seccomp-retain-set` isolator. + 2. When an app does not have any seccomp isolator (neither `os/linux/seccomp-remove-set` nor `os/linux/seccomp-retain-set` is specified), implementations MAY apply their own set of blocked syscalls. + 3. The implementation-specific default blacklist SHOULD be a security-focused set of syscalls typically not required by apps. For example, implementations may require the `reboot(2)` syscall to be always blocked. This set MAY be empty. + 4. Values starting with `@` identify special wildcards and are scoped by the `/` separator. The `@appc.io/` scope is reserved for usage in this specification; implementations MAY provide additional wildcards in their own namespace (eg. `@implementation/wildcard`). The following special values are currently defined: + * `@appc.io/empty` represents the empty set. In the context of `seccomp-remove-set`, this wildcard masks all other values specified in `set` and can be effectively used to opt-in implementation-specific seccomp filtering. + +**Example:** + +```json +"name": "os/linux/seccomp-remove-set", +"value": { + "errno": "ENOTSUP", + "set": [ + "clock_adjtime", + "clock_settime", + "reboot" + ] +} +``` + +In the example above, the process will not be allowed to invoke `clock_adjtime(2)`, `clock_settime(2)`, and `reboot(2)` (and any other syscall in the implementation-specific blacklist). When invoked, such syscalls will immediately return a `ENOTSUP` error code. All other syscalls will behave as usual. + +#### os/linux/seccomp-retain-set + +* Scope: app + +**Parameters:** + +* **set** case-sensitive list of syscall names that will be whitelisted (ie. not blocked); values starting with `@` MUST be handled as scoped special values (see notes below). This field MUST NOT be empty. All syscalls specified in this set MUST NOT be blocked. This set MAY be augmented by an implementation-specific default whitelist set (see notes below). Syscalls not specified in the union of these two sets MUST be blocked. +* **errno** all-uppercase name of a single [errno code](http://man7.org/linux/man-pages/man3/errno.3.html) that will be returned by blocked system calls, instead of terminating. If missing or empty string, by default blocked syscalls MUST result in app termination via `SIGSYS` signal. All codes defined by POSIX.1-2001 and C99 MUST be supported. Implementations SHOULD support all custom Linux codes, but MAY ignore locally unsupported ones. + +**Notes:** + 1. Only a single `os/linux/seccomp-retain-set` isolator can be specified per-app, and it cannot be used in conjunction with the `os/linux/seccomp-remove-set` isolator. + 2. When an app does not have any seccomp isolator (neither `os/linux/seccomp-remove-set` nor `os/linux/seccomp-retain-set` is specified), implementations MAY apply their own set of blocked syscalls. + 3. The implementation-specific default whitelist MUST be the minimum set of syscalls required for app life-cycle. For example, implementations may require the `exit(2)` syscall to be always allowed for clean app termination. This set MAY be empty. + 4. Values starting with `@` identify special wildcards and are scoped by the `/` separator. The `@appc.io/` top-level scope is reserved for usage in this specification. Implementations MAY provide additional wildcards in their own namespace (eg. `@implementation/wildcard`). The following special values are currently defined: + * `@appc.io/all` represents the set of all available syscalls. In the context of `seccomp-retain-set`, this wildcard masks all other values specified in `set` and can be effectively used to opt-out seccomp filtering. + +**Example:** + +```json +"name": "os/linux/seccomp-retain-set", +"value": { + "errno": "", + "set": [ + "accept", + "bind", + "listen", + "read", + "socket", + "write" + ] +} +``` + +In the example above, the process will be only allowed to invoke syscalls specified in the custom network-related set (and any other syscall in the implementation-specific whitelist). All other syscalls will result in app termination via `SIGSYS` signal. #### os/linux/capabilities-remove-set From 65a06aa441e4b1550f5c3c292b1f46c499277d00 Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Thu, 2 Jun 2016 09:54:30 +0200 Subject: [PATCH 2/3] schema/types: implement linux-specific seccomp isolators --- schema/types/app.go | 3 + schema/types/isolator.go | 52 ++++++ schema/types/isolator_linux_specific.go | 174 ++++++++++++++++++- schema/types/isolator_linux_specific_test.go | 110 ++++++++++++ schema/types/isolator_resources.go | 9 + 5 files changed, 341 insertions(+), 7 deletions(-) diff --git a/schema/types/app.go b/schema/types/app.go index df13bf1c..a97ca025 100644 --- a/schema/types/app.go +++ b/schema/types/app.go @@ -86,5 +86,8 @@ func (a *App) assertValid() error { if err := a.Environment.assertValid(); err != nil { return err } + if err := a.Isolators.assertValid(); err != nil { + return err + } return nil } diff --git a/schema/types/isolator.go b/schema/types/isolator.go index ecdab008..ab8eee1f 100644 --- a/schema/types/isolator.go +++ b/schema/types/isolator.go @@ -16,10 +16,14 @@ package types import ( "encoding/json" + "errors" + "fmt" ) var ( isolatorMap map[ACIdentifier]IsolatorValueConstructor + + ErrIncompatibleIsolator = errors.New("isolators set contains incompatible types") ) func init() { @@ -40,6 +44,29 @@ func AddIsolatorName(n ACIdentifier, ns map[ACIdentifier]struct{}) { // and PodManifest schemas. type Isolators []Isolator +// assertValid checks that every single isolator is valid and that +// the whole set is well built +func (isolators Isolators) assertValid() error { + typesMap := make(map[ACIdentifier]bool) + for _, i := range isolators { + if err := i.Value().AssertValid(); err != nil { + return err + } + if _, ok := typesMap[i.Name]; ok { + if !i.Value().multipleAllowed() { + return fmt.Errorf(`isolators set contains too many instances of type %s"`, i.Name) + } + } + for _, c := range i.Value().Conflicts() { + if _, found := typesMap[c]; found { + return ErrIncompatibleIsolator + } + } + typesMap[i.Name] = true + } + return nil +} + // GetByName returns the last isolator in the list by the given name. func (is *Isolators) GetByName(name ACIdentifier) *Isolator { var i Isolator @@ -52,6 +79,22 @@ func (is *Isolators) GetByName(name ACIdentifier) *Isolator { return nil } +// ReplaceIsolatorsByName overrides matching isolator types with a new +// isolator, deleting them all and appending the new one instead +func (is *Isolators) ReplaceIsolatorsByName(newIs Isolator, oldNames []ACIdentifier) { + var i Isolator + for j := len(*is) - 1; j >= 0; j-- { + i = []Isolator(*is)[j] + for _, name := range oldNames { + if i.Name == name { + *is = append((*is)[:j], (*is)[j+1:]...) + } + } + } + *is = append((*is)[:], newIs) + return +} + // Unrecognized returns a set of isolators that are not recognized. // An isolator is not recognized if it has not had an associated // constructor registered with AddIsolatorValueConstructor. @@ -69,8 +112,17 @@ func (is *Isolators) Unrecognized() Isolators { // serialized as any arbitrary JSON blob. Specific Isolator types should // implement this interface to facilitate unmarshalling and validation. type IsolatorValue interface { + // UnmarshalJSON unserialize a JSON-encoded isolator UnmarshalJSON(b []byte) error + // AssertValid returns a non-nil error value if an IsolatorValue is not valid + // according to appc spec AssertValid() error + // Conflicts returns a list of conflicting isolators types, which cannot co-exist + // together with this IsolatorValue + Conflicts() []ACIdentifier + // multipleAllowed specifies whether multiple isolator instances are allowed + // for this isolator type + multipleAllowed() bool } // Isolator is a model for unmarshalling isolator types from their JSON-encoded diff --git a/schema/types/isolator_linux_specific.go b/schema/types/isolator_linux_specific.go index 678e0bf1..6cb9a867 100644 --- a/schema/types/isolator_linux_specific.go +++ b/schema/types/isolator_linux_specific.go @@ -17,12 +17,15 @@ package types import ( "encoding/json" "errors" + "unicode" ) const ( LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set" LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-remove-set" LinuxNoNewPrivilegesName = "os/linux/no-new-privileges" + LinuxSeccompRemoveSetName = "os/linux/seccomp-remove-set" + LinuxSeccompRetainSetName = "os/linux/seccomp-retain-set" ) var LinuxIsolatorNames = make(map[ACIdentifier]struct{}) @@ -32,6 +35,8 @@ func init() { LinuxCapabilitiesRevokeSetName: func() IsolatorValue { return &LinuxCapabilitiesRevokeSet{} }, LinuxCapabilitiesRetainSetName: func() IsolatorValue { return &LinuxCapabilitiesRetainSet{} }, LinuxNoNewPrivilegesName: func() IsolatorValue { v := LinuxNoNewPrivileges(false); return &v }, + LinuxSeccompRemoveSetName: func() IsolatorValue { return &LinuxSeccompRemoveSet{} }, + LinuxSeccompRetainSetName: func() IsolatorValue { return &LinuxSeccompRetainSet{} }, } { AddIsolatorName(name, LinuxIsolatorNames) AddIsolatorValueConstructor(name, con) @@ -44,6 +49,15 @@ func (l LinuxNoNewPrivileges) AssertValid() error { return nil } +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l LinuxNoNewPrivileges) multipleAllowed() bool { + return true +} +func (l LinuxNoNewPrivileges) Conflicts() []ACIdentifier { + return nil +} + func (l *LinuxNoNewPrivileges) UnmarshalJSON(b []byte) error { var v bool err := json.Unmarshal(b, &v) @@ -56,6 +70,10 @@ func (l *LinuxNoNewPrivileges) UnmarshalJSON(b []byte) error { return nil } +type AsIsolator interface { + AsIsolator() (*Isolator, error) +} + type LinuxCapabilitiesSet interface { Set() []LinuxCapability AssertValid() error @@ -78,6 +96,15 @@ func (l linuxCapabilitiesSetBase) AssertValid() error { return nil } +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l linuxCapabilitiesSetBase) multipleAllowed() bool { + return true +} +func (l linuxCapabilitiesSetBase) Conflicts() []ACIdentifier { + return nil +} + func (l *linuxCapabilitiesSetBase) UnmarshalJSON(b []byte) error { var v linuxCapabilitiesSetValue err := json.Unmarshal(b, &v) @@ -115,17 +142,17 @@ func NewLinuxCapabilitiesRetainSet(caps ...string) (*LinuxCapabilitiesRetainSet, return &l, nil } -func (l LinuxCapabilitiesRetainSet) AsIsolator() Isolator { +func (l LinuxCapabilitiesRetainSet) AsIsolator() (*Isolator, error) { b, err := json.Marshal(l.linuxCapabilitiesSetBase.val) if err != nil { - panic(err) + return nil, err } rm := json.RawMessage(b) - return Isolator{ + return &Isolator{ Name: LinuxCapabilitiesRetainSetName, ValueRaw: &rm, value: &l, - } + }, nil } type LinuxCapabilitiesRevokeSet struct { @@ -149,15 +176,148 @@ func NewLinuxCapabilitiesRevokeSet(caps ...string) (*LinuxCapabilitiesRevokeSet, return &l, nil } -func (l LinuxCapabilitiesRevokeSet) AsIsolator() Isolator { +func (l LinuxCapabilitiesRevokeSet) AsIsolator() (*Isolator, error) { b, err := json.Marshal(l.linuxCapabilitiesSetBase.val) if err != nil { - panic(err) + return nil, err } rm := json.RawMessage(b) - return Isolator{ + return &Isolator{ Name: LinuxCapabilitiesRevokeSetName, ValueRaw: &rm, value: &l, + }, nil +} + +type LinuxSeccompSet interface { + Set() []LinuxSeccompEntry + Errno() LinuxSeccompErrno + AssertValid() error +} + +type LinuxSeccompEntry string +type LinuxSeccompErrno string + +type linuxSeccompValue struct { + Set []LinuxSeccompEntry `json:"set"` + Errno LinuxSeccompErrno `json:"errno"` +} + +type linuxSeccompBase struct { + val linuxSeccompValue +} + +func (l linuxSeccompBase) multipleAllowed() bool { + return false +} + +func (l linuxSeccompBase) AssertValid() error { + if len(l.val.Set) == 0 { + return errors.New("set must be non-empty") + } + if l.val.Errno == "" { + return nil + } + for _, c := range l.val.Errno { + if !unicode.IsUpper(c) { + return errors.New("errno must be an upper case string") + } + } + return nil +} + +func (l *linuxSeccompBase) UnmarshalJSON(b []byte) error { + var v linuxSeccompValue + err := json.Unmarshal(b, &v) + if err != nil { + return err } + l.val = v + return nil +} + +func (l linuxSeccompBase) Set() []LinuxSeccompEntry { + return l.val.Set +} + +func (l linuxSeccompBase) Errno() LinuxSeccompErrno { + return l.val.Errno +} + +type LinuxSeccompRetainSet struct { + linuxSeccompBase +} + +func (l LinuxSeccompRetainSet) Conflicts() []ACIdentifier { + return []ACIdentifier{LinuxSeccompRemoveSetName} +} + +func NewLinuxSeccompRetainSet(errno string, syscall ...string) (*LinuxSeccompRetainSet, error) { + l := LinuxSeccompRetainSet{ + linuxSeccompBase{ + linuxSeccompValue{ + make([]LinuxSeccompEntry, len(syscall)), + LinuxSeccompErrno(errno), + }, + }, + } + for i, c := range syscall { + l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c) + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSeccompRetainSet) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.linuxSeccompBase.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSeccompRetainSetName, + ValueRaw: &rm, + value: &l, + }, nil +} + +type LinuxSeccompRemoveSet struct { + linuxSeccompBase +} + +func (l LinuxSeccompRemoveSet) Conflicts() []ACIdentifier { + return []ACIdentifier{LinuxSeccompRetainSetName} +} + +func NewLinuxSeccompRemoveSet(errno string, syscall ...string) (*LinuxSeccompRemoveSet, error) { + l := LinuxSeccompRemoveSet{ + linuxSeccompBase{ + linuxSeccompValue{ + make([]LinuxSeccompEntry, len(syscall)), + LinuxSeccompErrno(errno), + }, + }, + } + for i, c := range syscall { + l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c) + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSeccompRemoveSet) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.linuxSeccompBase.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSeccompRemoveSetName, + ValueRaw: &rm, + value: &l, + }, nil } diff --git a/schema/types/isolator_linux_specific_test.go b/schema/types/isolator_linux_specific_test.go index 04b373d3..7bd6dffd 100644 --- a/schema/types/isolator_linux_specific_test.go +++ b/schema/types/isolator_linux_specific_test.go @@ -94,3 +94,113 @@ func TestNewLinuxCapabilitiesRevokeSet(t *testing.T) { } } + +func TestNewLinuxSeccompRemoveSet(t *testing.T) { + tests := []struct { + set []string + errno string + + expectedSet []LinuxSeccompEntry + expectedErrno LinuxSeccompErrno + expectedErr bool + }{ + { + []string{"chmod", "chown"}, + "-EPERM", + nil, + "", + true, + }, + { + []string{"@appc.io/empty"}, + "EACCESS", + []LinuxSeccompEntry{"@appc.io/empty"}, + LinuxSeccompErrno("EACCESS"), + false, + }, + { + []string{"chmod", "chown"}, + "", + []LinuxSeccompEntry{"chmod", "chown"}, + LinuxSeccompErrno(""), + false, + }, + { + []string{}, + "", + nil, + "", + true, + }, + } + for i, tt := range tests { + c, err := NewLinuxSeccompRemoveSet(tt.errno, tt.set...) + if tt.expectedErr { + if err == nil { + t.Errorf("#%d: did not get expected error", i) + } + continue + } + if gset := c.Set(); !reflect.DeepEqual(gset, tt.expectedSet) { + t.Errorf("#%d: got set %#v, expected set %#v", i, gset, tt.expectedSet) + } + if gerrno := c.Errno(); !reflect.DeepEqual(gerrno, tt.expectedErrno) { + t.Errorf("#%d: got errno %#v, expected errno %#v", i, gerrno, tt.expectedErrno) + } + } +} + +func TestNewLinuxSeccompRetainSet(t *testing.T) { + tests := []struct { + set []string + errno string + + expectedSet []LinuxSeccompEntry + expectedErrno LinuxSeccompErrno + expectedErr bool + }{ + { + []string{}, + "eaccess", + nil, + "", + true, + }, + { + []string{"chmod"}, + "EACCESS", + []LinuxSeccompEntry{"chmod"}, + LinuxSeccompErrno("EACCESS"), + false, + }, + { + []string{"chmod", "chown"}, + "", + []LinuxSeccompEntry{"chmod", "chown"}, + LinuxSeccompErrno(""), + false, + }, + { + []string{}, + "", + nil, + "", + true, + }, + } + for i, tt := range tests { + c, err := NewLinuxSeccompRetainSet(tt.errno, tt.set...) + if tt.expectedErr { + if err == nil { + t.Errorf("#%d: did not get expected error", i) + } + continue + } + if gset := c.Set(); !reflect.DeepEqual(gset, tt.expectedSet) { + t.Errorf("#%d: got set %#v, expected set %#v", i, gset, tt.expectedSet) + } + if gerrno := c.Errno(); !reflect.DeepEqual(gerrno, tt.expectedErrno) { + t.Errorf("#%d: got errno %#v, expected errno %#v", i, gerrno, tt.expectedErrno) + } + } +} diff --git a/schema/types/isolator_resources.go b/schema/types/isolator_resources.go index 2ac5130d..2b77e304 100644 --- a/schema/types/isolator_resources.go +++ b/schema/types/isolator_resources.go @@ -85,6 +85,15 @@ func (r ResourceBase) AssertValid() error { return nil } +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l ResourceBase) multipleAllowed() bool { + return true +} +func (l ResourceBase) Conflicts() []ACIdentifier { + return nil +} + type ResourceBlockBandwidth struct { ResourceBase } From 682d131b34ea7f1e916decb49ebfb165f394c321 Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Wed, 15 Jun 2016 12:46:12 +0200 Subject: [PATCH 3/3] actool: add support for patching seccomp isolators --- actool/manifest.go | 81 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/actool/manifest.go b/actool/manifest.go index 46662167..cfc69f15 100644 --- a/actool/manifest.go +++ b/actool/manifest.go @@ -51,6 +51,8 @@ var ( patchMounts string patchPorts string patchIsolators string + patchSeccompMode string + patchSeccompSet string catPrettyPrint bool @@ -69,6 +71,8 @@ var ( [--ports=query,protocol=tcp,port=8080[:query2,...]] [--supplementary-groups=gid1,gid2,...] [--isolators=resource/cpu,request=50m,limit=100m[:resource/memory,...]] + [--seccomp-mode=remove|retain[,errno=EPERM]] + [--seccomp-set=syscall1,syscall2,...]] [--replace] INPUT_ACI_FILE [OUTPUT_ACI_FILE]`, @@ -99,6 +103,8 @@ func init() { cmdPatchManifest.Flags.StringVar(&patchMounts, "mounts", "", "Replace mount points") cmdPatchManifest.Flags.StringVar(&patchPorts, "ports", "", "Replace ports") cmdPatchManifest.Flags.StringVar(&patchIsolators, "isolators", "", "Replace isolators") + cmdPatchManifest.Flags.StringVar(&patchSeccompMode, "seccomp-mode", "", "Enable and configure seccomp isolator") + cmdPatchManifest.Flags.StringVar(&patchSeccompSet, "seccomp-set", "", "Set of syscalls for seccomp isolator enforcing") cmdCatManifest.Flags.BoolVar(&catPrettyPrint, "pretty-print", false, "Print with better style") } @@ -211,7 +217,11 @@ func patchManifest(im *schema.ImageManifest) error { if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchCaps, err) } - app.Isolators = append(app.Isolators, caps.AsIsolator()) + isolator, err = caps.AsIsolator() + if err != nil { + return err + } + app.Isolators = append(app.Isolators, *isolator) } if patchRevokeCaps != "" { isolator := app.Isolators.GetByName(types.LinuxCapabilitiesRevokeSetName) @@ -225,7 +235,11 @@ func patchManifest(im *schema.ImageManifest) error { if err != nil { return fmt.Errorf("cannot parse capability %q: %v", patchRevokeCaps, err) } - app.Isolators = append(app.Isolators, caps.AsIsolator()) + isolator, err = caps.AsIsolator() + if err != nil { + return err + } + app.Isolators = append(app.Isolators, *isolator) } if patchMounts != "" { @@ -250,6 +264,18 @@ func patchManifest(im *schema.ImageManifest) error { } } + // Parse seccomp args and override existing seccomp isolators + if patchSeccompMode != "" { + seccompIsolator, err := parseSeccompArgs(patchSeccompMode, patchSeccompSet) + if err != nil { + return err + } + seccompReps := []types.ACIdentifier{types.LinuxSeccompRemoveSetName, types.LinuxSeccompRetainSetName} + app.Isolators.ReplaceIsolatorsByName(*seccompIsolator, seccompReps) + } else if patchSeccompSet != "" { + return fmt.Errorf("--seccomp-set specified without --seccomp-mode") + } + if patchIsolators != "" { isolators := strings.Split(patchIsolators, ":") for _, is := range isolators { @@ -260,14 +286,16 @@ func patchManifest(im *schema.ImageManifest) error { _, ok := types.ResourceIsolatorNames[name] - if name == types.LinuxNoNewPrivilegesName { + switch name { + case types.LinuxNoNewPrivilegesName: ok = true kv := strings.Split(is, ",") if len(kv) != 2 { return fmt.Errorf("isolator %s: invalid format", name) } - isolatorStr = fmt.Sprintf(`{ "name": "%s", "value": %s }`, name, kv[1]) + case types.LinuxSeccompRemoveSetName, types.LinuxSeccompRetainSetName: + ok = false } if !ok { @@ -284,6 +312,51 @@ func patchManifest(im *schema.ImageManifest) error { return nil } +// parseSeccompArgs parses seccomp mode and set CLI flags, preparing an +// appropriate seccomp isolator. +func parseSeccompArgs(patchSeccompMode string, patchSeccompSet string) (*types.Isolator, error) { + // Parse mode flag and additional keyed arguments. + var errno, mode string + args := strings.Split(patchSeccompMode, ",") + for _, a := range args { + kv := strings.Split(a, "=") + switch len(kv) { + case 1: + // mode, either "remove" or "retain" + mode = kv[0] + case 2: + // k=v argument, only "errno" allowed for now + if kv[0] == "errno" { + errno = kv[1] + } else { + return nil, fmt.Errorf("invalid seccomp-mode optional argument: %s", a) + } + default: + return nil, fmt.Errorf("cannot parse seccomp-mode argument: %s", a) + } + } + + // Instantiate an Isolator with the content specified by the --seccomp-set parameter. + var err error + var seccomp types.AsIsolator + switch mode { + case "remove": + seccomp, err = types.NewLinuxSeccompRemoveSet(errno, strings.Split(patchSeccompSet, ",")...) + case "retain": + seccomp, err = types.NewLinuxSeccompRetainSet(errno, strings.Split(patchSeccompSet, ",")...) + default: + err = fmt.Errorf("unknown seccomp mode %s", mode) + } + if err != nil { + return nil, fmt.Errorf("cannot parse seccomp isolator: %s", err) + } + seccompIsolator, err := seccomp.AsIsolator() + if err != nil { + return nil, err + } + return seccompIsolator, nil +} + // extractManifest iterates over the tar reader and locate the manifest. Once // located, the manifest can be printed, replaced or patched. func extractManifest(tr *tar.Reader, tw *tar.Writer, printManifest bool, newManifest []byte) error {