diff --git a/schema/types/isolator_linux_specific.go b/schema/types/isolator_linux_specific.go index df500bd5..3d5dc9fa 100644 --- a/schema/types/isolator_linux_specific.go +++ b/schema/types/isolator_linux_specific.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "unicode" ) @@ -29,6 +30,7 @@ const ( LinuxSeccompRetainSetName = "os/linux/seccomp-retain-set" LinuxOOMScoreAdjName = "os/linux/oom-score-adj" LinuxCPUSharesName = "os/linux/cpu-shares" + LinuxSELinuxContextName = "os/linux/selinux-context" ) var LinuxIsolatorNames = make(map[ACIdentifier]struct{}) @@ -42,6 +44,7 @@ func init() { LinuxCPUSharesName: func() IsolatorValue { v := LinuxCPUShares(1024); return &v }, LinuxSeccompRemoveSetName: func() IsolatorValue { return &LinuxSeccompRemoveSet{} }, LinuxSeccompRetainSetName: func() IsolatorValue { return &LinuxSeccompRetainSet{} }, + LinuxSELinuxContextName: func() IsolatorValue { return &LinuxSELinuxContext{} }, } { AddIsolatorName(name, LinuxIsolatorNames) AddIsolatorValueConstructor(name, con) @@ -430,3 +433,97 @@ func (l LinuxOOMScoreAdj) AsIsolator() Isolator { value: &l, } } + +type LinuxSELinuxUser string +type LinuxSELinuxRole string +type LinuxSELinuxType string +type LinuxSELinuxLevel string + +type linuxSELinuxValue struct { + User LinuxSELinuxUser `json:"user"` + Role LinuxSELinuxRole `json:"role"` + Type LinuxSELinuxType `json:"type"` + Level LinuxSELinuxLevel `json:"level"` +} + +type LinuxSELinuxContext struct { + val linuxSELinuxValue +} + +func (l LinuxSELinuxContext) AssertValid() error { + if l.val.User == "" || strings.Contains(string(l.val.User), ":") { + return fmt.Errorf("invalid user value %q", l.val.User) + } + if l.val.Role == "" || strings.Contains(string(l.val.Role), ":") { + return fmt.Errorf("invalid role value %q", l.val.Role) + } + if l.val.Type == "" || strings.Contains(string(l.val.Type), ":") { + return fmt.Errorf("invalid type value %q", l.val.Type) + } + if l.val.Level == "" { + return fmt.Errorf("invalid level value %q", l.val.Level) + } + return nil +} + +func (l *LinuxSELinuxContext) UnmarshalJSON(b []byte) error { + var v linuxSELinuxValue + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + l.val = v + return nil +} + +func (l LinuxSELinuxContext) User() LinuxSELinuxUser { + return l.val.User +} + +func (l LinuxSELinuxContext) Role() LinuxSELinuxRole { + return l.val.Role +} + +func (l LinuxSELinuxContext) Type() LinuxSELinuxType { + return l.val.Type +} + +func (l LinuxSELinuxContext) Level() LinuxSELinuxLevel { + return l.val.Level +} + +func (l LinuxSELinuxContext) multipleAllowed() bool { + return false +} + +func (l LinuxSELinuxContext) Conflicts() []ACIdentifier { + return nil +} + +func NewLinuxSELinuxContext(selinuxUser, selinuxRole, selinuxType, selinuxLevel string) (*LinuxSELinuxContext, error) { + l := LinuxSELinuxContext{ + linuxSELinuxValue{ + LinuxSELinuxUser(selinuxUser), + LinuxSELinuxRole(selinuxRole), + LinuxSELinuxType(selinuxType), + LinuxSELinuxLevel(selinuxLevel), + }, + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSELinuxContext) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSELinuxContextName, + ValueRaw: &rm, + value: &l, + }, nil +} diff --git a/schema/types/isolator_linux_specific_test.go b/schema/types/isolator_linux_specific_test.go index 7bd6dffd..360dc42a 100644 --- a/schema/types/isolator_linux_specific_test.go +++ b/schema/types/isolator_linux_specific_test.go @@ -57,6 +57,76 @@ func TestNewLinuxCapabilitiesRetainSet(t *testing.T) { } +func TestNewLinuxSELinuxContext(t *testing.T) { + tests := []struct { + inUser string + inRole string + inType string + inLevel string + + wuser LinuxSELinuxUser + wrole LinuxSELinuxRole + wtype LinuxSELinuxType + wlevel LinuxSELinuxLevel + werr bool + }{ + { + "unconfined_u", "object_r", "user_home_t", "s0", + LinuxSELinuxUser("unconfined_u"), + LinuxSELinuxRole("object_r"), + LinuxSELinuxType("user_home_t"), + LinuxSELinuxLevel("s0"), + false, + }, + { + "unconfined_u", "object_r", "user_home_t", "s0-s0:c0", + LinuxSELinuxUser("unconfined_u"), + LinuxSELinuxRole("object_r"), + LinuxSELinuxType("user_home_t"), + LinuxSELinuxLevel("s0-s0:c0"), + false, + }, + { + "", "object_r", "user_home_t", "s0", + "", + "", + "", + "", + true, + }, + { + "unconfined_u:unconfined_t", "object_r", "user_home_t", "s0", + "", + "", + "", + "", + true, + }, + } + for i, tt := range tests { + c, err := NewLinuxSELinuxContext(tt.inUser, tt.inRole, tt.inType, tt.inLevel) + if tt.werr { + if err == nil { + t.Errorf("#%d: did not get expected error", i) + } + continue + } + if guser := c.User(); !reflect.DeepEqual(guser, tt.wuser) { + t.Errorf("#%d: got user %#v, want user %#v", i, guser, tt.wuser) + } + if grole := c.Role(); !reflect.DeepEqual(grole, tt.wrole) { + t.Errorf("#%d: got role %#v, want role %#v", i, grole, tt.wrole) + } + if gtype := c.Type(); !reflect.DeepEqual(gtype, tt.wtype) { + t.Errorf("#%d: got type %#v, want type %#v", i, gtype, tt.wtype) + } + if glevel := c.Level(); !reflect.DeepEqual(glevel, tt.wlevel) { + t.Errorf("#%d: got level %#v, want level %#v", i, glevel, tt.wlevel) + } + } + +} + func TestNewLinuxCapabilitiesRevokeSet(t *testing.T) { tests := []struct { in []string diff --git a/schema/types/isolator_test.go b/schema/types/isolator_test.go index 5e74ce99..ba54cc64 100644 --- a/schema/types/isolator_test.go +++ b/schema/types/isolator_test.go @@ -256,6 +256,34 @@ func TestIsolatorUnmarshal(t *testing.T) { }`, true, }, + { + `{ + "name": "os/linux/selinux-context", + "value": {"user" : "user_u", "role": "role_r", "type": "type_r", "level": "s0-s0"} + }`, + false, + }, + { + `{ + "name": "os/linux/selinux-context", + "value": {"user" : "user_u", "role": "role_r", "type": "type_r", "level": "s0-s0:c0"} + }`, + false, + }, + { + `{ + "name": "os/linux/selinux-context", + "value": {"user" : "user_u", "role": "role_r:type_t", "type": "type_r", "level": "s0-s0"} + }`, + true, + }, + { + `{ + "name": "os/linux/selinux-context", + "value": {"user" : "user_u", "role": "", "type": "type_r", "level": "s0-s0"} + }`, + true, + }, } for i, tt := range tests { diff --git a/spec/ace.md b/spec/ace.md index e05dab66..64aca90b 100644 --- a/spec/ace.md +++ b/spec/ace.md @@ -200,6 +200,38 @@ In the example above, the process will not be allowed to invoke `clock_adjtime(2 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/selinux-context + +* Scope: app/pod + +**Parameters:** + +* **user** case-sensitive string containing the user portion of the SELinux security context to be used to label the current pod or application. +* **role** case-sensitive string containing the role portion of the SELinux security context to be used to label the current pod or application. +* **type** case-sensitive string containing the type/domain portion of the SELinux security context to be used to label the current pod or application. +* **level** case-sensitive string containing the level portion of the SELinux security context to be used to label the current pod or application. + +**Notes:** + 1. Only a single `os/linux/selinux-context` isolator can be specified per-pod. + 2. Only a single `os/linux/selinux-context` isolator can be specified per-app. + 3. If a SELinux security context is specified at pod level, it applies to all processes involved in running the pod. + 4. If specified both at pod and app level, app values override pod ones. + 5. Implementations MAY ignore this isolator if the host does not support SELinux labeling. + +**Example:** + +```json +"name": "os/linux/selinux-context", +"value": { + "user": "system_u", + "role": "system_r", + "type": "dhcpc_t", + "level": "s0", +} +``` + +In the example above, the SELinux security context `system_u:system_r:dhcpc_t:s0` will be applied to a pod or to a single application, depending on where this isolator is specified. + #### os/linux/capabilities-remove-set * Scope: app