swap out host_network for a schema-based check

This commit is contained in:
Robert Brennan
2019-12-19 21:25:01 +00:00
parent 59785196d4
commit d80d326f7c
5 changed files with 174 additions and 17 deletions

16
checks/host_network.yaml Normal file
View File

@@ -0,0 +1,16 @@
name: HostNetworkSet
id: hostNetworkSet
successMessage: Host network is not configured
failureMessage: Host network should not be configured
category: Networking
controllers:
exclude: []
target: Pod
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
hostNetwork:
not:
enum:
- true

View File

@@ -6,7 +6,10 @@ import (
)
// IsActionable determines whether a check is actionable given the current configuration
func (conf *Configuration) IsActionable(subConf interface{}, ruleName, controllerName string) bool {
func (conf Configuration) IsActionable(subConf interface{}, ruleName, controllerName string) bool {
if subConfStr, ok := subConf.(string); ok {
subConf = conf.GetCategoryConfig(subConfStr)
}
ruleID := GetIDFromField(subConf, ruleName)
subConfRef := reflect.ValueOf(subConf)
fieldVal := reflect.Indirect(subConfRef).FieldByName(ruleName).Interface()
@@ -36,3 +39,29 @@ func (conf *Configuration) IsActionable(subConf interface{}, ruleName, controlle
}
return true
}
func (conf Configuration) GetCategoryConfig(category string) interface{} {
if category == "Networking" {
return conf.Networking
} else if category == "Security" {
return conf.Security
} else if category == "Health Checks" {
return conf.HealthChecks
} else if category == "Resources" {
return conf.Resources
} else if category == "Images" {
return conf.Images
}
return nil
}
func (conf Configuration) GetSeverity(category string, name string) Severity {
subConf := conf.GetCategoryConfig(category)
subConfRef := reflect.ValueOf(subConf)
fieldVal := reflect.Indirect(subConfRef).FieldByName(name).Interface()
if severity, ok := fieldVal.(Severity); ok {
return severity
}
// TODO: don't panic
panic("Unknown severity: " + category + "/" + name)
}

View File

@@ -31,11 +31,12 @@ func ValidateController(conf conf.Configuration, controller controller.Interface
controllerType := controller.GetType()
pod := controller.GetPodSpec()
podResult := ValidatePod(conf, pod, controller.GetName(), controllerType)
return ControllerResult{
result := ControllerResult{
Type: controllerType.String(),
Name: controller.GetName(),
PodResult: podResult,
}
return result
}
// ValidateControllers validates that each deployment conforms to the Polaris config,

View File

@@ -34,7 +34,7 @@ func ValidatePod(conf config.Configuration, pod *corev1.PodSpec, controllerName
}
pv.validateSecurity(&conf, controllerName)
pv.validateNetworking(&conf, controllerName)
applyPodSchemaChecks(&conf, pod, controllerName, &pv)
pRes := PodResult{
Messages: pv.messages(),
@@ -83,17 +83,3 @@ func (pv *PodValidation) validateSecurity(conf *config.Configuration, controller
}
}
}
func (pv *PodValidation) validateNetworking(conf *config.Configuration, controllerName string) {
category := messages.CategoryNetworking
name := "HostNetworkSet"
if conf.IsActionable(conf.Networking, name, controllerName) {
id := config.GetIDFromField(conf.Networking, name)
if pv.Pod.HostNetwork {
pv.addFailure(messages.HostNetworkFailure, conf.Networking.HostNetworkSet, category, id)
} else {
pv.addSuccess(messages.HostNetworkSuccess, category, id)
}
}
}

125
pkg/validator/schema.go Normal file
View File

@@ -0,0 +1,125 @@
package validator
import (
"bytes"
"encoding/json"
"fmt"
"io"
packr "github.com/gobuffalo/packr/v2"
"github.com/qri-io/jsonschema"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/fairwindsops/polaris/pkg/config"
controller "github.com/fairwindsops/polaris/pkg/validator/controllers"
)
type IncludeExcludeList struct {
Include []string `yaml:"include"`
Exclude []string `yaml:"exclude"`
}
type Target string
const (
TargetContainer Target = "Container"
TargetPod Target = "Pod"
)
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"`
Controllers IncludeExcludeList `yaml:"controllers"`
Target Target `yaml:"target"`
Schema jsonschema.RootSchema `yaml:"schema"`
}
var (
schemaBox = (*packr.Box)(nil)
checks = map[Target][]SchemaCheck{
TargetContainer: []SchemaCheck{},
TargetPod: []SchemaCheck{},
}
)
func init() {
schemaBox = packr.New("Schemas", "../../checks")
files := schemaBox.List()
for _, file := range files {
contents, err := schemaBox.Find(file)
if err != nil {
panic(err)
}
check, err := parseCheck(contents)
if err != nil {
panic(err)
}
checks[check.Target] = append(checks[check.Target], check)
}
}
func parseCheck(rawBytes []byte) (SchemaCheck, error) {
reader := bytes.NewReader(rawBytes)
check := SchemaCheck{}
d := yaml.NewYAMLOrJSONDecoder(reader, 4096)
for {
if err := d.Decode(&check); err != nil {
if err == io.EOF {
return check, nil
}
return check, fmt.Errorf("Decoding schema check failed: %v", err)
}
}
}
func (check SchemaCheck) check(controller controller.Interface) (bool, error) {
pod := controller.GetPodSpec()
if check.Target == TargetPod {
return check.checkPod(pod)
} else if check.Target == TargetContainer {
for _, container := range pod.Containers {
bytes, err := json.Marshal(container)
if err != nil {
return false, err
}
errors, err := check.Schema.ValidateBytes(bytes)
if err != nil || len(errors) > 0 {
return false, err
}
}
// TODO: initcontainers
}
return true, nil
}
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
}
func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, pv *PodValidation) error {
for _, check := range checks[TargetPod] {
if !conf.IsActionable(check.Category, check.Name, controllerName) {
continue
}
severity := conf.GetSeverity(check.Category, check.Name)
passes, err := check.checkPod(pod)
if err != nil {
return err
}
if passes {
pv.addSuccess(check.SuccessMessage, check.Category, check.ID)
} else {
pv.addFailure(check.FailureMessage, severity, check.Category, check.ID)
}
}
return nil
}