diff --git a/checks/dangerousCapabilities.yaml b/checks/dangerousCapabilities.yaml new file mode 100644 index 000000000..6c88bd2c2 --- /dev/null +++ b/checks/dangerousCapabilities.yaml @@ -0,0 +1,27 @@ +name: DangerousCapabilities +id: dangerousCapabilities +successMessage: Container does not have any dangerous capabilities +failureMessage: Container should not have dangerous capabilities +category: Security +target: Container +schema: + '$schema': http://json-schema.org/draft-07/schema + type: object + properties: + securityContext: + type: object + properties: + capabilities: + type: object + properties: + add: + type: array + not: + contains: + const: ALL + not: + contains: + const: SYS_ADMIN + not: + contains: + const: NET_ADMIN diff --git a/checks/insecureCapabilities.yaml b/checks/insecureCapabilities.yaml new file mode 100644 index 000000000..fd987acf2 --- /dev/null +++ b/checks/insecureCapabilities.yaml @@ -0,0 +1,33 @@ +name: InsecureCapabilities +id: insecureCapabilities +successMessage: Container does not have any insecure capabilities +failureMessage: Container should not have insecure capabilities +category: Security +target: Container +schema: + '$schema': http://json-schema.org/draft-07/schema + type: object + properties: + securityContext: + type: object + properties: + capabilities: + type: object + properties: + add: + enum: + - CHOWN + - DAC_OVERRIDE + - FSETID + - FOWNER + - MKNOD + - NET_RAW + - SETGID + - SETUID + - SETFCAP + - SETPCAP + - NET_BIND_SERVICE + - SYS_CHROOT + - KILL + - AUDIT_WRITE + diff --git a/examples/config.yaml b/examples/config.yaml index 1e8ac06e4..faf1c1efc 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -19,28 +19,8 @@ security: privilegeEscalationAllowed: error runAsRootAllowed: warning runAsPrivileged: error - capabilities: - error: - ifAnyAdded: - - SYS_ADMIN - - NET_ADMIN - - ALL - warning: - ifAnyAddedBeyond: - - CHOWN - - DAC_OVERRIDE - - FSETID - - FOWNER - - MKNOD - - NET_RAW - - SETGID - - SETUID - - SETFCAP - - SETPCAP - - NET_BIND_SERVICE - - SYS_CHROOT - - KILL - - AUDIT_WRITE + dangerousCapabilities: error + insecureCapabilities: warning controllers_to_scan: - Deployments - StatefulSets diff --git a/pkg/config/config.go b/pkg/config/config.go index 841d8ada9..351f1277b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,7 +23,6 @@ import ( "strings" packr "github.com/gobuffalo/packr/v2" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/yaml" ) @@ -99,26 +98,14 @@ type Networking struct { // Security contains the config for security validations. type Security struct { - HostIPCSet Severity `json:"hostIPCSet"` - HostPIDSet Severity `json:"hostPIDSet"` - RunAsRootAllowed Severity `json:"runAsRootAllowed"` - RunAsPrivileged Severity `json:"runAsPrivileged"` - NotReadOnlyRootFileSystem Severity `json:"notReadOnlyRootFileSystem"` - PrivilegeEscalationAllowed Severity `json:"privilegeEscalationAllowed"` - Capabilities SecurityCapabilities `json:"capabilities"` -} - -// SecurityCapabilities contains the config for security capabilities validations. -type SecurityCapabilities struct { - Error SecurityCapabilityLists `json:"error"` - Warning SecurityCapabilityLists `json:"warning"` -} - -// SecurityCapabilityLists contains the config for security capabilitie list validations. -type SecurityCapabilityLists struct { - IfAnyAdded []corev1.Capability `json:"ifAnyAdded"` - IfAnyAddedBeyond []corev1.Capability `json:"ifAnyAddedBeyond"` - IfAnyNotDropped []corev1.Capability `json:"ifAnyNotDropped"` + HostIPCSet Severity `json:"hostIPCSet"` + HostPIDSet Severity `json:"hostPIDSet"` + RunAsRootAllowed Severity `json:"runAsRootAllowed"` + RunAsPrivileged Severity `json:"runAsPrivileged"` + NotReadOnlyRootFileSystem Severity `json:"notReadOnlyRootFileSystem"` + PrivilegeEscalationAllowed Severity `json:"privilegeEscalationAllowed"` + DangerousCapabilities Severity `json:"dangerousCapabilities"` + InsecureCapabilities Severity `json:"insecureCapabilities"` } // ParseFile parses config from a file. diff --git a/pkg/validator/container.go b/pkg/validator/container.go index 85beade5c..813526840 100644 --- a/pkg/validator/container.go +++ b/pkg/validator/container.go @@ -64,8 +64,6 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, panic(err) } - cv.validateSecurity(conf, controllerName) - cRes := ContainerResult{ Name: container.Name, Messages: cv.messages(), @@ -153,147 +151,3 @@ func (cv *ContainerValidation) validateResourceRange(id, resourceName string, ra cv.addSuccess(fmt.Sprintf(messages.ResourceAmountSuccess, resourceName), category, id) } } - -func (cv *ContainerValidation) validateSecurity(conf *config.Configuration, controllerName string) { - securityContext := cv.Container.SecurityContext - podSecurityContext := cv.parentPodSpec.SecurityContext - - // Support an empty container security context - if securityContext == nil { - securityContext = &corev1.SecurityContext{} - } - - // Support an empty pod security context - if podSecurityContext == nil { - podSecurityContext = &corev1.PodSecurityContext{} - } - - name := "Capabilities" - if conf.IsActionable(conf.Security, name, controllerName) { - cv.validateCapabilities(&conf.Security.Capabilities.Warning, &conf.Security.Capabilities.Error) - } -} - -func (cv *ContainerValidation) validateCapabilities(warningLists *config.SecurityCapabilityLists, errorLists *config.SecurityCapabilityLists) { - category := messages.CategorySecurity - capabilities := &corev1.Capabilities{} - if cv.Container.SecurityContext != nil && cv.Container.SecurityContext.Capabilities != nil { - capabilities = cv.Container.SecurityContext.Capabilities - } - allLists := []*config.SecurityCapabilityLists{warningLists, errorLists} - - addID := "capabilitiesAdded" - hasAddFailure := false - hasAddCheck := false - for _, confLists := range allLists { - if len(confLists.IfAnyAdded) == 0 && len(confLists.IfAnyAddedBeyond) == 0 { - continue - } - hasAddCheck = true - var severity config.Severity - if confLists == warningLists { - severity = config.SeverityWarning - } else { - severity = config.SeverityError - } - badAdds := make([]corev1.Capability, 0) - if len(confLists.IfAnyAdded) > 0 { - intersectAdds := capIntersection(capabilities.Add, confLists.IfAnyAdded) - badAdds = append(badAdds, intersectAdds...) - } - if len(confLists.IfAnyAddedBeyond) > 0 { - differentAdds := capDifference(capabilities.Add, confLists.IfAnyAddedBeyond) - differentAdds = capDifference(differentAdds, badAdds) - badAdds = append(badAdds, differentAdds...) - } - if capContains(capabilities.Add, "ALL") && !capContains(badAdds, "ALL") { - badAdds = append(badAdds, "ALL") - } - if len(badAdds) > 0 { - hasAddFailure = true - capsString := commaSeparatedCapabilities(badAdds) - cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesAddedFailure, capsString), severity, category, addID) - } - } - if hasAddCheck && !hasAddFailure { - cv.addSuccess(messages.SecurityCapabilitiesAddedSuccess, category, addID) - } - - dropID := "capabilitiesDropped" - hasDropCheck := false - hasDropFailure := false - for _, confLists := range allLists { - if len(confLists.IfAnyNotDropped) == 0 { - continue - } - hasDropCheck = true - var severity config.Severity - if confLists == warningLists { - severity = config.SeverityWarning - } else { - severity = config.SeverityError - } - missingDrops := capDifference(confLists.IfAnyNotDropped, capabilities.Drop) - id := "capabilitiesNotDropped" - if len(missingDrops) > 0 && !capContains(capabilities.Drop, "ALL") { - hasDropFailure = true - capsString := commaSeparatedCapabilities(missingDrops) - cv.addFailure(fmt.Sprintf(messages.SecurityCapabilitiesNotDroppedFailure, capsString), severity, category, id) - } - } - if hasDropCheck && !hasDropFailure { - cv.addSuccess(messages.SecurityCapabilitiesNotDroppedSuccess, category, dropID) - } -} - -func commaSeparatedCapabilities(caps []corev1.Capability) string { - capsString := "" - for _, cap := range caps { - capsString = fmt.Sprintf("%s, %s", capsString, cap) - } - return capsString[2:] -} - -func capIntersection(a, b []corev1.Capability) []corev1.Capability { - result := []corev1.Capability{} - hash := map[corev1.Capability]bool{} - - for _, s := range a { - hash[s] = true - } - - for _, s := range b { - if hash[s] { - result = append(result, s) - } - } - - return result -} - -func capDifference(b, a []corev1.Capability) []corev1.Capability { - result := []corev1.Capability{} - hash := map[corev1.Capability]bool{} - - for _, s := range a { - hash[s] = true - } - - for _, s := range b { - if !hash[s] { - result = append(result, s) - } - } - - return result -} - -func capContains(list []corev1.Capability, val corev1.Capability) bool { - for _, s := range list { - if s == val { - return true - } - } - - return false -} diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index 20ff2c170..927aa5f50 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -671,29 +671,16 @@ func TestValidateSecurity(t *testing.T) { RunAsPrivileged: conf.SeverityError, NotReadOnlyRootFileSystem: conf.SeverityWarning, PrivilegeEscalationAllowed: conf.SeverityError, - Capabilities: conf.SecurityCapabilities{ - Error: conf.SecurityCapabilityLists{ - IfAnyAdded: []corev1.Capability{"ALL", "SYS_ADMIN", "NET_ADMIN"}, - }, - Warning: conf.SecurityCapabilityLists{ - IfAnyAddedBeyond: []corev1.Capability{"NONE"}, - }, - }, + DangerousCapabilities: conf.SeverityError, + InsecureCapabilities: conf.SeverityWarning, } strongConf := conf.Security{ RunAsRootAllowed: conf.SeverityError, RunAsPrivileged: conf.SeverityError, NotReadOnlyRootFileSystem: conf.SeverityError, PrivilegeEscalationAllowed: conf.SeverityError, - Capabilities: conf.SecurityCapabilities{ - Error: conf.SecurityCapabilityLists{ - IfAnyAdded: []corev1.Capability{"ALL", "SYS_ADMIN", "NET_ADMIN"}, - IfAnyNotDropped: []corev1.Capability{"NET_BIND_SERVICE", "DAC_OVERRIDE", "SYS_CHROOT"}, - }, - Warning: conf.SecurityCapabilityLists{ - IfAnyAddedBeyond: []corev1.Capability{"NONE"}, - }, - }, + DangerousCapabilities: conf.SeverityError, + InsecureCapabilities: conf.SeverityError, } emptyCV := ContainerValidation{ @@ -708,7 +695,7 @@ func TestValidateSecurity(t *testing.T) { Privileged: &trueVar, AllowPrivilegeEscalation: &trueVar, Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"AUDIT_CONTROL", "SYS_ADMIN", "NET_ADMIN"}, + Add: []corev1.Capability{"AUDIT_WRITE", "SYS_ADMIN", "NET_ADMIN"}, }, }}, ResourceValidation: &ResourceValidation{}, @@ -721,7 +708,7 @@ func TestValidateSecurity(t *testing.T) { Privileged: &trueVar, AllowPrivilegeEscalation: &trueVar, Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"AUDIT_CONTROL", "SYS_ADMIN", "NET_ADMIN"}, + Add: []corev1.Capability{"AUDIT_WRITE", "SYS_ADMIN", "NET_ADMIN"}, }, }}, ResourceValidation: &ResourceValidation{}, @@ -739,7 +726,7 @@ func TestValidateSecurity(t *testing.T) { Privileged: &trueVar, AllowPrivilegeEscalation: &trueVar, Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"AUDIT_CONTROL", "SYS_ADMIN", "NET_ADMIN"}, + Add: []corev1.Capability{"AUDIT_WRITE", "SYS_ADMIN", "NET_ADMIN"}, }, }}, ResourceValidation: &ResourceValidation{}, @@ -849,8 +836,13 @@ func TestValidateSecurity(t *testing.T) { Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", + Type: "success", + Category: "Security", + }, { + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", Type: "success", Category: "Security", }}, @@ -860,8 +852,8 @@ func TestValidateSecurity(t *testing.T) { securityConf: standardConf, cv: badCV, expectedMessages: []*ResultMessage{{ - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: SYS_ADMIN, NET_ADMIN", + ID: "dangerousCapabilities", + Message: "Container should not have dangerous capabilities", Type: "error", Category: "Security", }, { @@ -875,8 +867,8 @@ func TestValidateSecurity(t *testing.T) { Type: "error", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: AUDIT_CONTROL, SYS_ADMIN, NET_ADMIN", + ID: "insecureCapabilities", + Message: "Container should not have insecure capabilities", Type: "warning", Category: "Security", }, { @@ -896,8 +888,8 @@ func TestValidateSecurity(t *testing.T) { securityConf: standardConf, cv: badCVWithGoodPodSpec, expectedMessages: []*ResultMessage{{ - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: SYS_ADMIN, NET_ADMIN", + ID: "dangerousCapabilities", + Message: "Container should not have dangerous capabilities", Type: "error", Category: "Security", }, { @@ -911,8 +903,8 @@ func TestValidateSecurity(t *testing.T) { Type: "error", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: AUDIT_CONTROL, SYS_ADMIN, NET_ADMIN", + ID: "insecureCapabilities", + Message: "Container should not have insecure capabilities", Type: "warning", Category: "Security", }, { @@ -932,10 +924,15 @@ func TestValidateSecurity(t *testing.T) { securityConf: standardConf, cv: badCVWithBadPodSpec, expectedMessages: []*ResultMessage{{ - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: SYS_ADMIN, NET_ADMIN", + ID: "dangerousCapabilities", + Message: "Container should not have dangerous capabilities", Type: "error", Category: "Security", + }, { + ID: "insecureCapabilities", + Message: "Container should not have insecure capabilities", + Type: "warning", + Category: "Security", }, { ID: "privilegeEscalationAllowed", Message: "Privilege escalation should not be allowed", @@ -946,11 +943,6 @@ func TestValidateSecurity(t *testing.T) { Message: "Should not be running as privileged", Type: "error", Category: "Security", - }, { - ID: "capabilitiesAdded", - Message: "The following security capabilities should not be added: AUDIT_CONTROL, SYS_ADMIN, NET_ADMIN", - Type: "warning", - Category: "Security", }, { ID: "runAsRootAllowed", Message: "Should not be allowed to run as root", @@ -988,8 +980,13 @@ func TestValidateSecurity(t *testing.T) { Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", + Type: "success", + Category: "Security", + }, { + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", Type: "success", Category: "Security", }}, @@ -999,13 +996,13 @@ func TestValidateSecurity(t *testing.T) { securityConf: strongConf, cv: goodCV, expectedMessages: []*ResultMessage{{ - ID: "capabilitiesNotDropped", - Message: "The following security capabilities should be dropped: DAC_OVERRIDE, SYS_CHROOT", - Type: "error", + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", + Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", Type: "success", Category: "Security", }, { @@ -1055,13 +1052,13 @@ func TestValidateSecurity(t *testing.T) { Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", Type: "success", Category: "Security", }, { - ID: "capabilitiesDropped", - Message: "All disallowed security capabilities have been dropped", + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", Type: "success", Category: "Security", }}, @@ -1091,13 +1088,13 @@ func TestValidateSecurity(t *testing.T) { Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", Type: "success", Category: "Security", }, { - ID: "capabilitiesDropped", - Message: "All disallowed security capabilities have been dropped", + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", Type: "success", Category: "Security", }}, @@ -1127,13 +1124,13 @@ func TestValidateSecurity(t *testing.T) { Type: "success", Category: "Security", }, { - ID: "capabilitiesAdded", - Message: "Disallowed security capabilities have not been added", + ID: "dangerousCapabilities", + Message: "Container does not have any dangerous capabilities", Type: "success", Category: "Security", }, { - ID: "capabilitiesDropped", - Message: "All disallowed security capabilities have been dropped", + ID: "insecureCapabilities", + Message: "Container does not have any insecure capabilities", Type: "success", Category: "Security", }}, @@ -1147,9 +1144,8 @@ func TestValidateSecurity(t *testing.T) { if err != nil { panic(err) } - tt.cv.validateSecurity(&conf.Configuration{Security: tt.securityConf}, "") assert.Len(t, tt.cv.messages(), len(tt.expectedMessages)) - assert.ElementsMatch(t, tt.cv.messages(), tt.expectedMessages) + assert.ElementsMatch(t, tt.expectedMessages, tt.cv.messages()) }) } } diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index f67623818..8b9a045b7 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -63,6 +63,8 @@ var ( "runAsPrivileged", "notReadOnlyRootFileSystem", "privilegeEscalationAllowed", + "dangerousCapabilities", + "insecureCapabilities", } )