diff --git a/checks/cpuLimitsMissing.yaml b/checks/cpuLimitsMissing.yaml index 478c80a0..ccf1ddfe 100644 --- a/checks/cpuLimitsMissing.yaml +++ b/checks/cpuLimitsMissing.yaml @@ -1,5 +1,3 @@ -name: CPULimitsMissing -id: cpuLimitsMissing successMessage: CPU limits are set failureMessage: CPU limits should be set category: Resources diff --git a/checks/cpuRequestsMissing.yaml b/checks/cpuRequestsMissing.yaml index 1c3a445c..77290d8b 100644 --- a/checks/cpuRequestsMissing.yaml +++ b/checks/cpuRequestsMissing.yaml @@ -1,5 +1,3 @@ -name: CPURequestsMissing -id: cpuRequestsMissing successMessage: CPU requests are set failureMessage: CPU requests should be set category: Resources diff --git a/checks/dangerousCapabilities.yaml b/checks/dangerousCapabilities.yaml index 6c88bd2c..d1daa414 100644 --- a/checks/dangerousCapabilities.yaml +++ b/checks/dangerousCapabilities.yaml @@ -1,5 +1,3 @@ -name: DangerousCapabilities -id: dangerousCapabilities successMessage: Container does not have any dangerous capabilities failureMessage: Container should not have dangerous capabilities category: Security diff --git a/checks/hostIPC.yaml b/checks/hostIPCSet.yaml similarity index 88% rename from checks/hostIPC.yaml rename to checks/hostIPCSet.yaml index 158931c0..db62a24f 100644 --- a/checks/hostIPC.yaml +++ b/checks/hostIPCSet.yaml @@ -1,5 +1,3 @@ -name: HostIPCSet -id: hostIPCSet successMessage: Host IPC is not configured failureMessage: Host IPC should not be configured category: Security diff --git a/checks/hostNetwork.yaml b/checks/hostNetworkSet.yaml similarity index 87% rename from checks/hostNetwork.yaml rename to checks/hostNetworkSet.yaml index 191c174b..d54419bf 100644 --- a/checks/hostNetwork.yaml +++ b/checks/hostNetworkSet.yaml @@ -1,5 +1,3 @@ -name: HostNetworkSet -id: hostNetworkSet successMessage: Host network is not configured failureMessage: Host network should not be configured category: Networking diff --git a/checks/hostPID.yaml b/checks/hostPIDSet.yaml similarity index 88% rename from checks/hostPID.yaml rename to checks/hostPIDSet.yaml index 6f3ed894..d2b0a65c 100644 --- a/checks/hostPID.yaml +++ b/checks/hostPIDSet.yaml @@ -1,5 +1,3 @@ -name: HostPIDSet -id: hostPIDSet successMessage: Host PID is not configured failureMessage: Host PID should not be configured category: Security diff --git a/checks/hostPortSet.yaml b/checks/hostPortSet.yaml index 2c26cc2a..cd193ea6 100644 --- a/checks/hostPortSet.yaml +++ b/checks/hostPortSet.yaml @@ -1,5 +1,3 @@ -name: HostPortSet -id: hostPortSet successMessage: Host port is not configured failureMessage: Host port should not be configured category: Networking diff --git a/checks/insecureCapabilities.yaml b/checks/insecureCapabilities.yaml index fd987acf..e5afcfd0 100644 --- a/checks/insecureCapabilities.yaml +++ b/checks/insecureCapabilities.yaml @@ -1,5 +1,3 @@ -name: InsecureCapabilities -id: insecureCapabilities successMessage: Container does not have any insecure capabilities failureMessage: Container should not have insecure capabilities category: Security diff --git a/checks/livenessProbe.yaml b/checks/livenessProbeMissing.yaml similarity index 88% rename from checks/livenessProbe.yaml rename to checks/livenessProbeMissing.yaml index a60d719f..a7827304 100644 --- a/checks/livenessProbe.yaml +++ b/checks/livenessProbeMissing.yaml @@ -1,5 +1,3 @@ -name: LivenessProbeMissing -id: livenessProbeMissing successMessage: Liveness probe is configured failureMessage: Liveness probe should be configured category: Health Checks diff --git a/checks/memoryLimitsMissing.yaml b/checks/memoryLimitsMissing.yaml index e61926bf..f3c14524 100644 --- a/checks/memoryLimitsMissing.yaml +++ b/checks/memoryLimitsMissing.yaml @@ -1,5 +1,3 @@ -name: MemoryLimitsMissing -id: memoryLimitsMissing successMessage: Memory limits are set failureMessage: Memory limits should be set category: Resources diff --git a/checks/memoryRequestsMissing.yaml b/checks/memoryRequestsMissing.yaml index cfd339a9..a3f91781 100644 --- a/checks/memoryRequestsMissing.yaml +++ b/checks/memoryRequestsMissing.yaml @@ -1,5 +1,3 @@ -name: MemoryRequestsMissing -id: memoryRequestsMissing successMessage: Memory requests are set failureMessage: Memory requests should be set category: Resources diff --git a/checks/notReadOnlyRootFileSystem.yaml b/checks/notReadOnlyRootFileSystem.yaml index 509bf2e8..d4e203fe 100644 --- a/checks/notReadOnlyRootFileSystem.yaml +++ b/checks/notReadOnlyRootFileSystem.yaml @@ -1,5 +1,3 @@ -name: NotReadOnlyRootFileSystem -id: notReadOnlyRootFileSystem successMessage: Filesystem is read only failureMessage: Filesystem should be read only category: Security diff --git a/checks/privilegeEscalationAllowed.yaml b/checks/privilegeEscalationAllowed.yaml index 912db92d..6db1f836 100644 --- a/checks/privilegeEscalationAllowed.yaml +++ b/checks/privilegeEscalationAllowed.yaml @@ -1,5 +1,3 @@ -name: PrivilegeEscalationAllowed -id: privilegeEscalationAllowed successMessage: Privilege escalation not allowed failureMessage: Privilege escalation should not be allowed category: Security diff --git a/checks/pullPolicyNotAlways.yaml b/checks/pullPolicyNotAlways.yaml index 5aeac39a..89e591f5 100644 --- a/checks/pullPolicyNotAlways.yaml +++ b/checks/pullPolicyNotAlways.yaml @@ -1,5 +1,3 @@ -name: PullPolicyNotAlways -id: pullPolicyNotAlways successMessage: Image pull policy is "Always" failureMessage: Image pull policy should be "Always" category: Images diff --git a/checks/readinessProbe.yaml b/checks/readinessProbeMissing.yaml similarity index 88% rename from checks/readinessProbe.yaml rename to checks/readinessProbeMissing.yaml index c9d0dba8..1309bab8 100644 --- a/checks/readinessProbe.yaml +++ b/checks/readinessProbeMissing.yaml @@ -1,5 +1,3 @@ -name: ReadinessProbeMissing -id: readinessProbeMissing successMessage: Readiness probe is configured failureMessage: Readiness probe should be configured category: Health Checks diff --git a/checks/runAsPrivileged.yaml b/checks/runAsPrivileged.yaml index 8c9b6ff1..47be7cb7 100644 --- a/checks/runAsPrivileged.yaml +++ b/checks/runAsPrivileged.yaml @@ -1,5 +1,3 @@ -name: RunAsPrivileged -id: runAsPrivileged successMessage: Not running as privileged failureMessage: Should not be running as privileged category: Security diff --git a/checks/runAsRootAllowed.yaml b/checks/runAsRootAllowed.yaml index 468f51e6..3ae2fe49 100644 --- a/checks/runAsRootAllowed.yaml +++ b/checks/runAsRootAllowed.yaml @@ -1,5 +1,3 @@ -name: RunAsRootAllowed -id: runAsRootAllowed successMessage: Is not allowed to run as root failureMessage: Should not be allowed to run as root category: Security diff --git a/checks/tagNotSpecified.yaml b/checks/tagNotSpecified.yaml index 322d806d..102d34c7 100644 --- a/checks/tagNotSpecified.yaml +++ b/checks/tagNotSpecified.yaml @@ -1,5 +1,3 @@ -name: TagNotSpecified -id: tagNotSpecified successMessage: Image tag is specified failureMessage: Image tag should be specified category: Images diff --git a/deploy/dashboard.yaml b/deploy/dashboard.yaml index 2af9813c..ad4d0cda 100644 --- a/deploy/dashboard.yaml +++ b/deploy/dashboard.yaml @@ -154,6 +154,7 @@ metadata: namespace: polaris labels: app: polaris + foo: bar component: dashboard spec: replicas: 1 diff --git a/examples/config.yaml b/examples/config.yaml index faf1c1ef..e468175f 100644 --- a/examples/config.yaml +++ b/examples/config.yaml @@ -1,18 +1,19 @@ -resources: +checks: + # resources cpuRequestsMissing: warning cpuLimitsMissing: warning memoryRequestsMissing: warning memoryLimitsMissing: warning -images: + # images tagNotSpecified: error pullPolicyNotAlways: ignore -healthChecks: + # healthChecks readinessProbeMissing: warning livenessProbeMissing: warning -networking: + # networking hostNetworkSet: warning hostPortSet: warning -security: + # security hostIPCSet: error hostPIDSet: error notReadOnlyRootFileSystem: warning @@ -21,7 +22,7 @@ security: runAsPrivileged: error dangerousCapabilities: error insecureCapabilities: warning -controllers_to_scan: +controllersToScan: - Deployments - StatefulSets - DaemonSets diff --git a/pkg/config/config.go b/pkg/config/config.go index 5aa75381..584539ee 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,13 +29,8 @@ import ( // Configuration contains all of the config for the validation checks. type Configuration struct { DisplayName string `json:"displayName"` - Resources Resources `json:"resources"` - HealthChecks HealthChecks `json:"healthChecks"` - Images Images `json:"images"` - Networking Networking `json:"networking"` - Security Security `json:"security"` Checks map[string]Severity `json:"checks"` - ControllersToScan []SupportedController `json:"controllers_to_scan"` + ControllersToScan []SupportedController `json:"controllersToScan"` CustomChecks map[string]SchemaCheck `json:"customChecks"` Exemptions []Exemption `json:"exemptions"` DisallowExemptions bool `json:"disallowExemptions"` diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 1677fed7..121e40b2 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -29,18 +29,18 @@ import ( var confInvalid = `test` var confValidYAML = ` -resources: +checks: cpuRequestsMissing: warning -controllers_to_scan: +controllersToScan: - Deployments ` var confValidJSON = ` { - "resources": { + "checks": { "cpuRequestsMissing": "warning" }, - "controllers_to_scan": ["Deployments"] + "controllersToScan": ["Deployments"] } ` @@ -96,7 +96,7 @@ func TestConfigNoServerError(t *testing.T) { } func testParsedConfig(t *testing.T, config *Configuration) { - assert.Equal(t, SeverityWarning, config.Resources.CPURequestsMissing) - assert.Equal(t, Severity(""), config.Resources.CPULimitsMissing) + assert.Equal(t, SeverityWarning, config.Checks["cpuRequestsMissing"]) + assert.Equal(t, Severity(""), config.Checks["cpuLimitsMissing"]) assert.ElementsMatch(t, []SupportedController{Deployments}, config.ControllersToScan) } diff --git a/pkg/config/exemptions.go b/pkg/config/exemptions.go index 2da8778d..7b5f55f9 100644 --- a/pkg/config/exemptions.go +++ b/pkg/config/exemptions.go @@ -1,19 +1,12 @@ package config import ( - "reflect" "strings" ) // IsActionable determines whether a check is actionable given the current configuration -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() - if severity, ok := fieldVal.(Severity); ok && !severity.IsActionable() { +func (conf Configuration) IsActionable(ruleID, controllerName string) bool { + if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() { return false } if conf.DisallowExemptions { @@ -33,31 +26,3 @@ func (conf Configuration) IsActionable(subConf interface{}, ruleName, controller } return true } - -// GetCategoryConfig returns the configuration for a particular category name -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 -} - -// GetSeverity returns the severity configured for a particular check -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) -} diff --git a/pkg/config/schema.go b/pkg/config/schema.go index f072d88b..073a0a9d 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -95,7 +95,6 @@ const ( // SchemaCheck is a Polaris check that runs using JSON Schema type SchemaCheck struct { - Name string `yaml:"name"` ID string `yaml:"id"` Category string `yaml:"category"` SuccessMessage string `yaml:"successMessage"` diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index 4c958754..1fdd02d0 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -25,7 +25,7 @@ import ( ) var resourceConfMinimal = `--- -resources: +checks: cpuRequestsMissing: warning memoryRequestsMissing: warning cpuLimitsMissing: error @@ -33,7 +33,7 @@ resources: ` var resourceConfExemptions = `--- -resources: +checks: cpuRequestsMissing: warning memoryRequestsMissing: warning cpuLimitsMissing: error @@ -338,14 +338,14 @@ func TestValidateResourcesFullyValid(t *testing.T) { func TestValidateHealthChecks(t *testing.T) { // Test setup. - p1 := conf.HealthChecks{} - p2 := conf.HealthChecks{ - ReadinessProbeMissing: conf.SeverityIgnore, - LivenessProbeMissing: conf.SeverityIgnore, + p1 := make(map[string]conf.Severity) + p2 := map[string]conf.Severity{ + "readinessProbeMissing": conf.SeverityIgnore, + "livenessProbeMissing": conf.SeverityIgnore, } - p3 := conf.HealthChecks{ - ReadinessProbeMissing: conf.SeverityError, - LivenessProbeMissing: conf.SeverityWarning, + p3 := map[string]conf.Severity{ + "readinessProbeMissing": conf.SeverityError, + "livenessProbeMissing": conf.SeverityWarning, } probe := corev1.Probe{} @@ -375,7 +375,7 @@ func TestValidateHealthChecks(t *testing.T) { var testCases = []struct { name string - probes conf.HealthChecks + probes map[string]conf.Severity cv ContainerValidation errors *[]*ResultMessage warnings *[]*ResultMessage @@ -390,7 +390,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}, "", conf.Deployments, &tt.cv) + err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, "", conf.Deployments, &tt.cv) if err != nil { panic(err) } @@ -408,14 +408,14 @@ func TestValidateHealthChecks(t *testing.T) { } func TestValidateImage(t *testing.T) { - emptyConf := conf.Images{} - standardConf := conf.Images{ - TagNotSpecified: conf.SeverityError, - PullPolicyNotAlways: conf.SeverityIgnore, + emptyConf := make(map[string]conf.Severity) + standardConf := map[string]conf.Severity{ + "tagNotSpecified": conf.SeverityError, + "pullPolicyNotAlways": conf.SeverityIgnore, } - strongConf := conf.Images{ - TagNotSpecified: conf.SeverityError, - PullPolicyNotAlways: conf.SeverityError, + strongConf := map[string]conf.Severity{ + "tagNotSpecified": conf.SeverityError, + "pullPolicyNotAlways": conf.SeverityError, } emptyCV := ContainerValidation{ @@ -437,7 +437,7 @@ func TestValidateImage(t *testing.T) { var testCases = []struct { name string - image conf.Images + image map[string]conf.Severity cv ContainerValidation expected []*ResultMessage }{ @@ -507,7 +507,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}, "", conf.Deployments, &tt.cv) + err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, "", conf.Deployments, &tt.cv) if err != nil { panic(err) } @@ -519,12 +519,12 @@ func TestValidateImage(t *testing.T) { func TestValidateNetworking(t *testing.T) { // Test setup. - emptyConf := conf.Networking{} - standardConf := conf.Networking{ - HostPortSet: conf.SeverityWarning, + emptyConf := make(map[string]conf.Severity) + standardConf := map[string]conf.Severity{ + "hostPortSet": conf.SeverityWarning, } - strongConf := conf.Networking{ - HostPortSet: conf.SeverityError, + strongConf := map[string]conf.Severity{ + "hostPortSet": conf.SeverityError, } emptyCV := ContainerValidation{ @@ -553,7 +553,7 @@ func TestValidateNetworking(t *testing.T) { var testCases = []struct { name string - networkConf conf.Networking + networkConf map[string]conf.Severity cv ContainerValidation expectedMessages []*ResultMessage }{ @@ -629,7 +629,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}, "", conf.Deployments, &tt.cv) + err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, "", conf.Deployments, &tt.cv) if err != nil { panic(err) } @@ -644,22 +644,22 @@ func TestValidateSecurity(t *testing.T) { falseVar := false // Test setup. - emptyConf := conf.Security{} - standardConf := conf.Security{ - RunAsRootAllowed: conf.SeverityWarning, - RunAsPrivileged: conf.SeverityError, - NotReadOnlyRootFileSystem: conf.SeverityWarning, - PrivilegeEscalationAllowed: conf.SeverityError, - DangerousCapabilities: conf.SeverityError, - InsecureCapabilities: conf.SeverityWarning, + emptyConf := map[string]conf.Severity{} + standardConf := map[string]conf.Severity{ + "runAsRootAllowed": conf.SeverityWarning, + "runAsPrivileged": conf.SeverityError, + "notReadOnlyRootFileSystem": conf.SeverityWarning, + "privilegeEscalationAllowed": conf.SeverityError, + "dangerousCapabilities": conf.SeverityError, + "insecureCapabilities": conf.SeverityWarning, } - strongConf := conf.Security{ - RunAsRootAllowed: conf.SeverityError, - RunAsPrivileged: conf.SeverityError, - NotReadOnlyRootFileSystem: conf.SeverityError, - PrivilegeEscalationAllowed: conf.SeverityError, - DangerousCapabilities: conf.SeverityError, - InsecureCapabilities: conf.SeverityError, + strongConf := map[string]conf.Severity{ + "runAsRootAllowed": conf.SeverityError, + "runAsPrivileged": conf.SeverityError, + "notReadOnlyRootFileSystem": conf.SeverityError, + "privilegeEscalationAllowed": conf.SeverityError, + "dangerousCapabilities": conf.SeverityError, + "insecureCapabilities": conf.SeverityError, } emptyCV := ContainerValidation{ @@ -780,7 +780,7 @@ func TestValidateSecurity(t *testing.T) { var testCases = []struct { name string - securityConf conf.Security + securityConf map[string]conf.Severity cv ContainerValidation expectedMessages []*ResultMessage }{ @@ -1119,7 +1119,7 @@ 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) + err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, "", conf.Deployments, &tt.cv) if err != nil { panic(err) } @@ -1135,8 +1135,8 @@ func TestValidateRunAsRoot(t *testing.T) { nonRootUser := int64(1000) rootUser := int64(0) config := conf.Configuration{ - Security: conf.Security{ - RunAsRootAllowed: conf.SeverityWarning, + Checks: map[string]conf.Severity{ + "runAsRootAllowed": conf.SeverityWarning, }, } testCases := []struct { diff --git a/pkg/validator/controller_test.go b/pkg/validator/controller_test.go index 860984f1..90a67486 100644 --- a/pkg/validator/controller_test.go +++ b/pkg/validator/controller_test.go @@ -29,9 +29,9 @@ import ( func TestValidateController(t *testing.T) { c := conf.Configuration{ - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostIPCSet": conf.SeverityError, + "hostPIDSet": conf.SeverityError, }, } deployment := controller.NewDeploymentController(test.MockDeploy()) @@ -64,9 +64,9 @@ func TestValidateController(t *testing.T) { func TestSkipHealthChecks(t *testing.T) { c := conf.Configuration{ - HealthChecks: conf.HealthChecks{ - ReadinessProbeMissing: conf.SeverityError, - LivenessProbeMissing: conf.SeverityWarning, + Checks: map[string]conf.Severity{ + "readinessProbeMissing": conf.SeverityError, + "livenessProbeMissing": conf.SeverityWarning, }, ControllersToScan: []conf.SupportedController{ conf.Deployments, @@ -139,9 +139,9 @@ func TestSkipHealthChecks(t *testing.T) { func TestControllerExemptions(t *testing.T) { c := conf.Configuration{ - HealthChecks: conf.HealthChecks{ - ReadinessProbeMissing: conf.SeverityError, - LivenessProbeMissing: conf.SeverityWarning, + Checks: map[string]conf.Severity{ + "readinessProbeMissing": conf.SeverityError, + "livenessProbeMissing": conf.SeverityWarning, }, ControllersToScan: []conf.SupportedController{ conf.Deployments, diff --git a/pkg/validator/fullaudit_test.go b/pkg/validator/fullaudit_test.go index 863e35e4..af9411c0 100644 --- a/pkg/validator/fullaudit_test.go +++ b/pkg/validator/fullaudit_test.go @@ -16,9 +16,9 @@ func TestGetTemplateData(t *testing.T) { assert.Equal(t, err, nil, "error should be nil") c := conf.Configuration{ - HealthChecks: conf.HealthChecks{ - ReadinessProbeMissing: conf.SeverityError, - LivenessProbeMissing: conf.SeverityWarning, + Checks: map[string]conf.Severity{ + "readinessProbeMissing": conf.SeverityError, + "livenessProbeMissing": conf.SeverityWarning, }, ControllersToScan: []conf.SupportedController{ conf.Deployments, diff --git a/pkg/validator/pod_test.go b/pkg/validator/pod_test.go index d0552a70..fd9df744 100644 --- a/pkg/validator/pod_test.go +++ b/pkg/validator/pod_test.go @@ -24,13 +24,11 @@ import ( func TestValidatePod(t *testing.T) { c := conf.Configuration{ - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, - }, - Networking: conf.Networking{ - HostNetworkSet: conf.SeverityWarning, - HostPortSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostIPCSet": conf.SeverityError, + "hostPIDSet": conf.SeverityError, + "hostNetworkSet": conf.SeverityWarning, + "hostPortSet": conf.SeverityError, }, } @@ -59,8 +57,8 @@ func TestValidatePod(t *testing.T) { expectedMessages := []*ResultMessage{ {ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"}, - {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"}, + {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, } actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments) @@ -72,13 +70,11 @@ func TestValidatePod(t *testing.T) { func TestInvalidIPCPod(t *testing.T) { c := conf.Configuration{ - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, - }, - Networking: conf.Networking{ - HostNetworkSet: conf.SeverityWarning, - HostPortSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostIPCSet": conf.SeverityError, + "hostPIDSet": conf.SeverityError, + "hostNetworkSet": conf.SeverityWarning, + "hostPortSet": conf.SeverityError, }, } @@ -107,8 +103,8 @@ func TestInvalidIPCPod(t *testing.T) { } expectedMessages := []*ResultMessage{ {ID: "hostIPCSet", Message: "Host IPC should not be configured", Type: "error", Category: "Security"}, - {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"}, + {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, } actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments) @@ -120,13 +116,11 @@ func TestInvalidIPCPod(t *testing.T) { func TestInvalidNeworkPod(t *testing.T) { c := conf.Configuration{ - Networking: conf.Networking{ - HostNetworkSet: conf.SeverityWarning, - HostPortSet: conf.SeverityError, - }, - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostNetworkSet": conf.SeverityWarning, + "hostPortSet": conf.SeverityError, + "hostIPCSet": conf.SeverityError, + "hostPIDSet": conf.SeverityError, }, } @@ -170,13 +164,11 @@ func TestInvalidNeworkPod(t *testing.T) { func TestInvalidPIDPod(t *testing.T) { c := conf.Configuration{ - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, - }, - Networking: conf.Networking{ - HostNetworkSet: conf.SeverityWarning, - HostPortSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostIPCSet": conf.SeverityError, + "hostPIDSet": conf.SeverityError, + "hostNetworkSet": conf.SeverityWarning, + "hostPortSet": conf.SeverityError, }, } @@ -219,13 +211,11 @@ func TestInvalidPIDPod(t *testing.T) { func TestExemption(t *testing.T) { c := conf.Configuration{ - Security: conf.Security{ - HostIPCSet: conf.SeverityError, - HostPIDSet: conf.SeverityError, - }, - Networking: conf.Networking{ - HostNetworkSet: conf.SeverityWarning, - HostPortSet: conf.SeverityError, + Checks: map[string]conf.Severity{ + "hostIPCSet": conf.SeverityError, + "hostNetworkSet": conf.SeverityWarning, + "hostPIDSet": conf.SeverityError, + "hostPortSet": conf.SeverityError, }, Exemptions: []conf.Exemption{ conf.Exemption{ @@ -259,8 +249,8 @@ func TestExemption(t *testing.T) { Errors: uint(0), } expectedMessages := []*ResultMessage{ - {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, {ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"}, + {ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"}, } actualPodResult := ValidatePod(c, &pod.Spec, "foo", conf.Deployments) diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index e02341ea..2060991d 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "sort" packr "github.com/gobuffalo/packr/v2" corev1 "k8s.io/api/core/v1" @@ -13,25 +14,22 @@ import ( ) var ( - schemaBox = (*packr.Box)(nil) - checks = map[config.TargetKind][]config.SchemaCheck{ - config.TargetContainer: []config.SchemaCheck{}, - config.TargetPod: []config.SchemaCheck{}, - } + schemaBox = (*packr.Box)(nil) + builtInChecks = map[string]config.SchemaCheck{} // We explicitly set the order to avoid thrash in the // tests as we migrate toward JSON schema checkOrder = []string{ // Pod checks - "hostIPC", - "hostPID", - "hostNetwork", + "hostIPCSet", + "hostPIDSet", + "hostNetworkSet", // Container checks "memoryLimitsMissing", "memoryRequestsMissing", "cpuLimitsMissing", "cpuRequestsMissing", - "readinessProbe", - "livenessProbe", + "readinessProbeMissing", + "livenessProbeMissing", "pullPolicyNotAlways", "tagNotSpecified", "hostPortSet", @@ -46,8 +44,8 @@ var ( func init() { schemaBox = packr.New("Schemas", "../../checks") - for _, file := range checkOrder { - contents, err := schemaBox.Find(file + ".yaml") + for _, checkID := range checkOrder { + contents, err := schemaBox.Find(checkID + ".yaml") if err != nil { panic(err) } @@ -55,7 +53,8 @@ func init() { if err != nil { panic(err) } - checks[check.Target] = append(checks[check.Target], check) + check.ID = checkID + builtInChecks[checkID] = check } } @@ -74,14 +73,21 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) { } func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController, pv *PodValidation) error { - for _, check := range checks[config.TargetPod] { - if !conf.IsActionable(check.Category, check.Name, controllerName) { + checkIDs := getSortedKeys(conf.Checks) + for _, checkID := range checkIDs { + check, ok := conf.CustomChecks[checkID] + if !ok { + check, ok = builtInChecks[checkID] + } + if !ok { + return fmt.Errorf("Check %s not found", checkID) + } + if !conf.IsActionable(check.ID, controllerName) { continue } if !check.IsActionable(config.TargetPod, controllerType, false) { continue } - severity := conf.GetSeverity(check.Category, check.Name) passes, err := check.CheckPod(pod) if err != nil { return err @@ -89,6 +95,7 @@ func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, contr if passes { pv.addSuccess(check.SuccessMessage, check.Category, check.ID) } else { + severity := conf.Checks[checkID] pv.addFailure(check.FailureMessage, severity, check.Category, check.ID) } } @@ -96,43 +103,18 @@ func applyPodSchemaChecks(conf *config.Configuration, pod *corev1.PodSpec, contr } func applyContainerSchemaChecks(conf *config.Configuration, controllerName string, controllerType config.SupportedController, cv *ContainerValidation) error { - for _, check := range checks[config.TargetContainer] { - if !conf.IsActionable(check.Category, check.Name, controllerName) { - continue - } - if !check.IsActionable(config.TargetContainer, controllerType, cv.IsInitContainer) { - continue - } - severity := conf.GetSeverity(check.Category, check.Name) - var passes bool - var err error - if check.SchemaTarget == config.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 - } - if passes { - cv.addSuccess(check.SuccessMessage, check.Category, check.ID) - } else { - cv.addFailure(check.FailureMessage, severity, check.Category, check.ID) - } - } - for checkName, severity := range conf.Checks { - check, ok := conf.CustomChecks[checkName] + checkIDs := getSortedKeys(conf.Checks) + for _, checkID := range checkIDs { + check, ok := conf.CustomChecks[checkID] if !ok { - return fmt.Errorf("Custom check %s not found", checkName) + check, ok = builtInChecks[checkID] + } + if !ok { + return fmt.Errorf("Check %s not found", checkID) + } + if !conf.IsActionable(check.ID, controllerName) { + continue } - // FIXME: check actionability here - /* - if !conf.IsActionable(check.Category, check.Name, controllerName) { - continue - } - */ if !check.IsActionable(config.TargetContainer, controllerType, cv.IsInitContainer) { continue } @@ -151,8 +133,18 @@ func applyContainerSchemaChecks(conf *config.Configuration, controllerName strin if passes { cv.addSuccess(check.SuccessMessage, check.Category, check.ID) } else { + severity := conf.Checks[checkID] cv.addFailure(check.FailureMessage, severity, check.Category, check.ID) } } return nil } + +func getSortedKeys(m map[string]config.Severity) []string { + keys := make([]string, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +}