diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index 60997cb1..18862efa 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -63,10 +63,14 @@ func getEmptyController(name string) controllers.Interface { } func testValidate(t *testing.T, container *corev1.Container, resourceConf *string, controllerName string, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { + testValidateWithController(t, container, resourceConf, getEmptyController(controllerName), expectedErrors, expectedWarnings, expectedSuccesses) +} + +func testValidateWithController(t *testing.T, container *corev1.Container, resourceConf *string, controller controllers.Interface, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { parsedConf, err := conf.Parse([]byte(*resourceConf)) assert.NoError(t, err, "Expected no error when parsing config") - results, err := applyContainerSchemaChecks(&parsedConf, getEmptyController(controllerName), container, false) + results, err := applyContainerSchemaChecks(&parsedConf, controller, container, false) if err != nil { panic(err) } @@ -1146,3 +1150,52 @@ func TestValidateResourcesExemption(t *testing.T) { testValidate(t, &container, &disallowExemptionsConf, "foo", expectedErrors, expectedWarnings, expectedSuccesses) } + +func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) { + container := corev1.Container{ + Name: "Empty", + } + + expectedWarnings := []ResultMessage{ + { + ID: "memoryRequestsMissing", + Success: false, + Severity: "warning", + Message: "Memory requests should be set", + Category: "Resources", + }, + } + + expectedErrors := []ResultMessage{ + { + ID: "cpuLimitsMissing", + Success: false, + Severity: "error", + Message: "CPU limits should be set", + Category: "Resources", + }, + { + ID: "memoryLimitsMissing", + Success: false, + Severity: "error", + Message: "Memory limits should be set", + Category: "Resources", + }, + } + + expectedSuccesses := []ResultMessage{} + + controller := controllers.NewDeploymentController(appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string { + "polaris.fairwinds.com/cpu-requests-missing-exempt": "true", // Exempt this controller from cpuRequestsMissing + "polaris.fairwinds.com/memory-requests-missing-exempt": "truthy", // Don't actually exempt this controller from memoryRequestsMissing + } , + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{}, + }, + }) + testValidateWithController(t, &container, &resourceConfMinimal, controller, expectedErrors, expectedWarnings, expectedSuccesses) +} \ No newline at end of file diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index a18f19df..b560c226 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "sort" + "regexp" + "strings" packr "github.com/gobuffalo/packr/v2" corev1 "k8s.io/api/core/v1" @@ -105,10 +107,23 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo return result } +func getExemptKey(checkID string) string { + matchWordBoundary := regexp.MustCompile("([a-z])([A-Z])") + matchAcronymWordBoundary := regexp.MustCompile("([A-Z])([A-Z][a-z])") + wordSplitString := matchWordBoundary.ReplaceAllString(checkID, "${1}-${2}") + kebabCase := strings.ToLower(matchAcronymWordBoundary.ReplaceAllString(wordSplitString, "${1}-${2}")) + return fmt.Sprintf("polaris.fairwinds.com/%s-exempt", kebabCase) +} + func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Interface) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) + objectAnnotations := controller.GetObjectMeta().Annotations for _, checkID := range checkIDs { + exemptValue := objectAnnotations[getExemptKey(checkID)] + if strings.ToLower(exemptValue) == "true" { + continue + } check, err := resolveCheck(conf, checkID, controller, config.TargetPod, false) if err != nil { return nil, err @@ -130,7 +145,12 @@ func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Int func applyContainerSchemaChecks(conf *config.Configuration, controller controllers.Interface, container *corev1.Container, isInit bool) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) + objectAnnotations := controller.GetObjectMeta().Annotations for _, checkID := range checkIDs { + exemptValue := objectAnnotations[getExemptKey(checkID)] + if strings.ToLower(exemptValue) == "true" { + continue + } check, err := resolveCheck(conf, checkID, controller, config.TargetContainer, isInit) if err != nil { return nil, err diff --git a/pkg/validator/schema_test.go b/pkg/validator/schema_test.go index 0db08d2b..72f05cca 100644 --- a/pkg/validator/schema_test.go +++ b/pkg/validator/schema_test.go @@ -260,3 +260,32 @@ func TestValidateCustomCheckExemptions(t *testing.T) { } testValidate(t, &container, &customCheckExemptions, "notexempt", expectedErrors, expectedWarnings, expectedSuccesses) } + +func TestGetExemptKey(t *testing.T) { + keyMap := map[string]string { + "hostIPCSet": "polaris.fairwinds.com/host-ipc-set-exempt", + "hostPIDSet": "polaris.fairwinds.com/host-pid-set-exempt", + "hostNetworkSet": "polaris.fairwinds.com/host-network-set-exempt", + "memoryLimitsMissing": "polaris.fairwinds.com/memory-limits-missing-exempt", + "memoryRequestsMissing": "polaris.fairwinds.com/memory-requests-missing-exempt", + "cpuLimitsMissing": "polaris.fairwinds.com/cpu-limits-missing-exempt", + "cpuRequestsMissing": "polaris.fairwinds.com/cpu-requests-missing-exempt", + "readinessProbeMissing": "polaris.fairwinds.com/readiness-probe-missing-exempt", + "livenessProbeMissing": "polaris.fairwinds.com/liveness-probe-missing-exempt", + "pullPolicyNotAlways": "polaris.fairwinds.com/pull-policy-not-always-exempt", + "tagNotSpecified": "polaris.fairwinds.com/tag-not-specified-exempt", + "hostPortSet": "polaris.fairwinds.com/host-port-set-exempt", + "runAsRootAllowed": "polaris.fairwinds.com/run-as-root-allowed-exempt", + "runAsPrivileged": "polaris.fairwinds.com/run-as-privileged-exempt", + "notReadOnlyRootFileSystem": "polaris.fairwinds.com/not-read-only-root-file-system-exempt", + "privilegeEscalationAllowed": "polaris.fairwinds.com/privilege-escalation-allowed-exempt", + "dangerousCapabilities": "polaris.fairwinds.com/dangerous-capabilities-exempt", + "insecureCapabilities": "polaris.fairwinds.com/insecure-capabilities-exempt", + } + for id, key := range keyMap { + exemptKey := getExemptKey(id) + assert.Equal(t, key, exemptKey) + } + + +} \ No newline at end of file