Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON schema checks #234

Merged
merged 22 commits into from
Jan 2, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move runAsRootAllowed over to jsonschema
  • Loading branch information
rbren committed Dec 23, 2019
commit ad3a8e674897a1c3d7c826211e15910e27a1e440
56 changes: 56 additions & 0 deletions checks/runAsRootAllowed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: RunAsRootAllowed
id: runAsRootAllowed
successMessage: Is not allowed to run as root
failureMessage: Should not be allowed to run as root
category: Security
controllers:
exclude: []
target: Container
schemaTarget: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
definitions:
goodSecurityContext:
type: object
anyOf:
- required:
- runAsUser
properties:
runAsUser:
minimum: 1
- required:
- runAsNonRoot
properties:
runAsNonRoot:
const: true
notBadSecurityContext:
type: object
properties:
runAsUser:
minimum: 1
runAsNonRoot:
const: true
type: object
anyOf:
# non-root specified at pod-level, and not overridden at container level
- required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
containers:
type: array
items:
properties:
securityContext:
$ref: "#/definitions/notBadSecurityContext"
# non-root specified at container level
- properties:
containers:
type: array
items:
required:
- securityContext
properties:
securityContext:
$ref: "#/definitions/goodSecurityContext"
22 changes: 2 additions & 20 deletions pkg/validator/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult,

cv.validateResources(conf, controllerName)

err := applyContainerSchemaChecks(conf, container, controllerName, controllerType, isInit, &cv)
err := applyContainerSchemaChecks(conf, controllerName, controllerType, &cv)
// FIXME: don't panic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this was left on purpose to be fixed in separate PR right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup - I've got another PR coming in after this one, and probably one more refactor after that.

if err != nil {
panic(err)
Expand Down Expand Up @@ -169,25 +169,7 @@ func (cv *ContainerValidation) validateSecurity(conf *config.Configuration, cont
podSecurityContext = &corev1.PodSecurityContext{}
}

name := "RunAsRootAllowed"
if conf.IsActionable(conf.Security, name, controllerName) {
id := config.GetIDFromField(conf.Security, name)
runAsRootSuccess := false
if getBoolValue(securityContext.RunAsNonRoot) || (securityContext.RunAsUser != nil && *securityContext.RunAsUser > 0) {
// Check if the container is explicitly set to True (pass)
runAsRootSuccess = true
} else if securityContext.RunAsNonRoot == nil && securityContext.RunAsUser == nil {
// Or if the container values are unset, check the pod values
runAsRootSuccess = getBoolValue(podSecurityContext.RunAsNonRoot) || (podSecurityContext.RunAsUser != nil && *podSecurityContext.RunAsUser > 0)
}
if runAsRootSuccess {
cv.addSuccess(messages.RunAsRootSuccess, category, id)
} else {
cv.addFailure(messages.RunAsRootFailure, conf.Security.RunAsRootAllowed, category, id)
}
}

name = "RunAsPrivileged"
name := "RunAsPrivileged"
if conf.IsActionable(conf.Security, name, controllerName) {
id := config.GetIDFromField(conf.Security, name)
if getBoolValue(securityContext.Privileged) {
Expand Down
30 changes: 24 additions & 6 deletions pkg/validator/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ func TestValidateHealthChecks(t *testing.T) {

for idx, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
err := applyContainerSchemaChecks(&conf.Configuration{HealthChecks: tt.probes}, tt.cv.Container, "", conf.Deployments, tt.cv.IsInitContainer, &tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{HealthChecks: tt.probes}, "", conf.Deployments, &tt.cv)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -528,7 +528,7 @@ func TestValidateImage(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
tt.cv = resetCV(tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{Images: tt.image}, tt.cv.Container, "", conf.Deployments, tt.cv.IsInitContainer, &tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{Images: tt.image}, "", conf.Deployments, &tt.cv)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -650,7 +650,7 @@ func TestValidateNetworking(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
tt.cv = resetCV(tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{Networking: tt.networkConf}, tt.cv.Container, "", conf.Deployments, tt.cv.IsInitContainer, &tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{Networking: tt.networkConf}, "", conf.Deployments, &tt.cv)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1143,6 +1143,10 @@ func TestValidateSecurity(t *testing.T) {
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
tt.cv = resetCV(tt.cv)
err := applyContainerSchemaChecks(&conf.Configuration{Security: tt.securityConf}, "", conf.Deployments, &tt.cv)
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)
Expand All @@ -1161,10 +1165,12 @@ func TestValidateRunAsRoot(t *testing.T) {
},
}
testCases := []struct {
name string
cv ContainerValidation
message ResultMessage
}{
{
name: "pod=false,container=nil",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
Expand All @@ -1184,6 +1190,7 @@ func TestValidateRunAsRoot(t *testing.T) {
},
},
{
name: "pod=false,container=true",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
Expand All @@ -1203,6 +1210,7 @@ func TestValidateRunAsRoot(t *testing.T) {
},
},
{
name: "pod=nil,container=runAsUser",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
Expand All @@ -1217,6 +1225,7 @@ func TestValidateRunAsRoot(t *testing.T) {
},
},
{
name: "pod=runAsUser,container=nil",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{}},
Expand All @@ -1234,6 +1243,7 @@ func TestValidateRunAsRoot(t *testing.T) {
},
},
{
name: "pod=runAsUser,container=runAsUser0",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
Expand All @@ -1253,6 +1263,7 @@ func TestValidateRunAsRoot(t *testing.T) {
},
},
{
name: "pod=false,container=runAsUser",
cv: ContainerValidation{
ResourceValidation: &ResourceValidation{},
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
Expand All @@ -1273,9 +1284,16 @@ func TestValidateRunAsRoot(t *testing.T) {
},
}
for idx, tt := range testCases {
tt.cv.validateSecurity(&config, "")
assert.Len(t, tt.cv.messages(), 1)
assert.Equal(t, &tt.message, tt.cv.messages()[0], fmt.Sprintf("Test case %d failed", idx))
t.Run(tt.name, func(t *testing.T) {
err := applyContainerSchemaChecks(&config, "", conf.Deployments, &tt.cv)
if err != nil {
panic(err)
}
assert.Len(t, tt.cv.messages(), 1)
if len(tt.cv.messages()) > 0 {
assert.Equal(t, &tt.message, tt.cv.messages()[0], fmt.Sprintf("Test case %d failed", idx))
}
})
}
}

Expand Down
33 changes: 21 additions & 12 deletions pkg/validator/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ type SchemaCheck struct {
Name string `yaml:"name"`
ID string `yaml:"id"`
Category string `yaml:"category"`
SuccessMessage string `yaml:"success_message"`
FailureMessage string `yaml:"failure_message"`
SuccessMessage string `yaml:"successMessage"`
FailureMessage string `yaml:"failureMessage"`
Controllers IncludeExcludeList `yaml:"controllers"`
Containers IncludeExcludeList `yaml:"containers"`
Target Target `yaml:"target"`
SchemaTarget Target `yaml:"schemaTarget"`
Schema jsonschema.RootSchema `yaml:"schema"`
}

Expand All @@ -58,6 +59,7 @@ var (
"pullPolicyNotAlways",
"tagNotSpecified",
"hostPortSet",
"runAsRootAllowed",
}
)

Expand Down Expand Up @@ -111,16 +113,15 @@ func (check SchemaCheck) check(controller controller.Interface) (bool, error) {
}

func (check SchemaCheck) checkPod(pod *corev1.PodSpec) (bool, error) {
bytes, err := json.Marshal(pod)
if err != nil {
return false, err
}
errors, err := check.Schema.ValidateBytes(bytes)
return len(errors) == 0, err
return check.checkObject(pod)
}

func (check SchemaCheck) checkContainer(container *corev1.Container) (bool, error) {
bytes, err := json.Marshal(container)
return check.checkObject(container)
}

func (check SchemaCheck) checkObject(obj interface{}) (bool, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -189,16 +190,24 @@ func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, contr
return nil
}

func applyContainerSchemaChecks(conf *config.Configuration, container *corev1.Container, controllerName string, controllerType config.SupportedController, isInit bool, cv *ContainerValidation) error {
func applyContainerSchemaChecks(conf *config.Configuration, controllerName string, controllerType config.SupportedController, cv *ContainerValidation) error {
for _, check := range checks[TargetContainer] {
if !conf.IsActionable(check.Category, check.Name, controllerName) {
continue
}
if !check.isActionable(TargetContainer, controllerType, isInit) {
if !check.isActionable(TargetContainer, controllerType, cv.IsInitContainer) {
continue
}
severity := conf.GetSeverity(check.Category, check.Name)
passes, err := check.checkContainer(container)
var passes bool
var err error
if check.SchemaTarget == TargetPod {
cv.parentPodSpec.Containers = []corev1.Container{*cv.Container}
passes, err = check.checkPod(&cv.parentPodSpec)
cv.parentPodSpec.Containers = []corev1.Container{}
} else {
passes, err = check.checkContainer(cv.Container)
}
if err != nil {
return err
}
Expand Down