mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-19 15:48:00 +00:00
swap out host_network for a schema-based check
This commit is contained in:
16
checks/host_network.yaml
Normal file
16
checks/host_network.yaml
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
125
pkg/validator/schema.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user