Skip to content

Commit

Permalink
implement capabilities checks in JSON schema
Browse files Browse the repository at this point in the history
  • Loading branch information
rbren committed Jan 2, 2020
1 parent 1067255 commit 7b0fe81
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 247 deletions.
27 changes: 27 additions & 0 deletions checks/dangerousCapabilities.yaml
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions checks/insecureCapabilities.yaml
Original file line number Diff line number Diff line change
@@ -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

24 changes: 2 additions & 22 deletions examples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 8 additions & 21 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.
Expand Down
146 changes: 0 additions & 146 deletions pkg/validator/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 7b0fe81

Please sign in to comment.