From 32e2f71ed8b889eb7be74aee1e07113b054a87a8 Mon Sep 17 00:00:00 2001 From: Robert Brennan Date: Thu, 4 Mar 2021 20:56:01 +0000 Subject: [PATCH] refactor a bunch --- pkg/config/exemptions.go | 8 +- pkg/config/exemptions_test.go | 112 ++++++----- pkg/config/schema.go | 9 +- pkg/kube/{workload.go => resource.go} | 107 ++++------ pkg/kube/resources.go | 94 +++++---- pkg/kube/resources_test.go | 2 +- pkg/validator/arbitrary_test.go | 7 +- pkg/validator/container.go | 4 +- pkg/validator/container_test.go | 12 +- pkg/validator/controller.go | 4 +- pkg/validator/controller_test.go | 12 +- pkg/validator/fullaudit.go | 2 +- pkg/validator/{arbitrary.go => other.go} | 30 +-- pkg/validator/pod.go | 2 +- pkg/validator/pod_test.go | 10 +- pkg/validator/schema.go | 241 ++++++++++++----------- pkg/webhook/webhook.go | 2 +- test/checks_test.go | 12 +- 18 files changed, 340 insertions(+), 330 deletions(-) rename pkg/kube/{workload.go => resource.go} (68%) rename pkg/validator/{arbitrary.go => other.go} (53%) diff --git a/pkg/config/exemptions.go b/pkg/config/exemptions.go index 1ae61984..14c0d508 100644 --- a/pkg/config/exemptions.go +++ b/pkg/config/exemptions.go @@ -2,10 +2,12 @@ package config import ( "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // IsActionable determines whether a check is actionable given the current configuration -func (conf Configuration) IsActionable(ruleID, namespace, controllerName, containerName string) bool { +func (conf Configuration) IsActionable(ruleID string, objMeta metav1.Object, containerName string) bool { if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() { return false } @@ -13,7 +15,7 @@ func (conf Configuration) IsActionable(ruleID, namespace, controllerName, contai return true } for _, exemption := range conf.Exemptions { - if exemption.Namespace != "" && exemption.Namespace != namespace { + if exemption.Namespace != "" && exemption.Namespace != objMeta.GetNamespace() { continue } @@ -27,7 +29,7 @@ func (conf Configuration) IsActionable(ruleID, namespace, controllerName, contai } if len(exemption.Rules) == 0 || checkIfRuleMatches { - if !isExemptionCheckMatched(exemption.ControllerNames, controllerName) { + if !isExemptionCheckMatched(exemption.ControllerNames, objMeta.GetName()) { continue } if isExemptionCheckMatched(exemption.ContainerNames, containerName) { diff --git a/pkg/config/exemptions_test.go b/pkg/config/exemptions_test.go index 844a754c..31c0dd07 100644 --- a/pkg/config/exemptions_test.go +++ b/pkg/config/exemptions_test.go @@ -18,6 +18,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var confContainerTest = ` @@ -68,26 +71,37 @@ exemptions: - namespace: polaris ` +func createMeta(name, namespace string) metav1.Object { + unst := unstructured.Unstructured{} + obj, err := meta.Accessor(&unst) + if err != nil { + panic(err) + } + obj.SetName(name) + obj.SetNamespace(namespace) + return obj +} + func TestNamespaceExemptionForSpecifiedRules(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "container11") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "container11") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "", "container11") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", ""), "container11") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller1", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller1"), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("pullPolicyNotAlways", "prometheus", "controller1", "") + actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("prometheus", "controller1"), "") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "") assert.True(t, actionable) } @@ -95,19 +109,19 @@ func TestNamespaceExemptionForAllRules(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "container11") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "container11") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "", "container11") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", ""), "container11") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "polaris", "controller1", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("polaris", "controller1"), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("pullPolicyNotAlways", "polaris", "controller1", "") + actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("polaris", "controller1"), "") assert.False(t, actionable) } @@ -115,28 +129,28 @@ func TestControllerExemption(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller2", "container21") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller2"), "container21") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "container21") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "container21") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "prometheus", "controller2", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("prometheus", "controller2"), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller3", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller3"), "") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller3", "container31") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller3"), "container31") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "") assert.True(t, actionable) } @@ -144,22 +158,22 @@ func TestOnlyContainerExemption(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container41") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container41") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container42") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container42") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller4", "container41") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller4"), "container41") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container41") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container41") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller4", "container41") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller4"), "container41") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51") assert.True(t, actionable) } @@ -167,25 +181,25 @@ func TestNamespaceAndContainerExemption(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container51") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container51") assert.False(t, actionable) - actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "", "container51") + actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", ""), "container51") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "container51") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "container51") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller5", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller5"), "") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "", "container51") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", ""), "container51") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container51") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container51") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller5", "container51") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller5"), "container51") assert.True(t, actionable) } @@ -193,25 +207,25 @@ func TestControllerAndContainerExemption(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller6", "container61") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller6"), "container61") assert.False(t, actionable) - actionable = parsedConf.IsActionable("priorityClassNotSet", "", "controller6", "container61") + actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("", "controller6"), "container61") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container61") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container61") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container61") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container61") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container61") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container61") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container61") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container61") assert.True(t, actionable) } @@ -219,33 +233,33 @@ func TestContainerExemption(t *testing.T) { parsedConf, err := Parse([]byte(confContainerTest)) assert.NoError(t, err) - actionable := parsedConf.IsActionable("multipleReplicasForDeployment", "", "", "container71") + actionable := parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", ""), "container71") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "", "container71") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", ""), "container71") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "", "controller7", "container71") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("", "controller7"), "container71") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container71") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container71") assert.False(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "insights-agent", "controller7", "container71") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("insights-agent", "controller7"), "container71") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller6", "container71") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller6"), "container71") assert.True(t, actionable) - actionable = parsedConf.IsActionable("multipleReplicasForDeployment", "kube-system", "controller7", "container61") + actionable = parsedConf.IsActionable("multipleReplicasForDeployment", createMeta("kube-system", "controller7"), "container61") assert.True(t, actionable) - actionable = parsedConf.IsActionable("priorityClassNotSet", "kube-system", "controller7", "container71") + actionable = parsedConf.IsActionable("priorityClassNotSet", createMeta("kube-system", "controller7"), "container71") assert.False(t, actionable) - actionable = parsedConf.IsActionable("pullPolicyNotAlways", "kube-system", "controller8", "container71") + actionable = parsedConf.IsActionable("pullPolicyNotAlways", createMeta("kube-system", "controller8"), "container71") assert.True(t, actionable) } diff --git a/pkg/config/schema.go b/pkg/config/schema.go index 0c7ae8ac..cc1add71 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "fmt" + "strings" "github.com/qri-io/jsonschema" corev1 "k8s.io/api/core/v1" @@ -158,13 +159,13 @@ func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) { } // IsActionable decides if this check applies to a particular target -func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool { - if check.Target != target { +func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool { + if check.Target != target && string(check.Target) != kind && !strings.HasSuffix(string(check.Target), "/"+kind) { return false } isIncluded := len(check.Controllers.Include) == 0 for _, inclusion := range check.Controllers.Include { - if inclusion == controllerType { + if inclusion == kind { isIncluded = true break } @@ -173,7 +174,7 @@ func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, return false } for _, exclusion := range check.Controllers.Exclude { - if exclusion == controllerType { + if exclusion == kind { return false } } diff --git a/pkg/kube/workload.go b/pkg/kube/resource.go similarity index 68% rename from pkg/kube/workload.go rename to pkg/kube/resource.go index 3b5fa520..a6881028 100644 --- a/pkg/kube/workload.go +++ b/pkg/kube/resource.go @@ -1,7 +1,6 @@ package kube import ( - "bytes" "context" "encoding/json" "fmt" @@ -13,22 +12,23 @@ import ( kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - k8sYaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/dynamic" ) -// GenericWorkload is a base implementation with some free methods for inherited structs -type GenericWorkload struct { +// GenericResource is a base implementation with some free methods for inherited structs +type GenericResource struct { Kind string - PodSpec kubeAPICoreV1.PodSpec ObjectMeta kubeAPIMetaV1.Object + Resource unstructured.Unstructured + PodSpec *kubeAPICoreV1.PodSpec OriginalObjectJSON []byte } -// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured -func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) { - workload := GenericWorkload{ - Kind: kind, +// NewGenericResourceFromUnstructured creates a workload from an unstructured.Unstructured +func NewGenericResourceFromUnstructured(unst *unstructured.Unstructured) (GenericResource, error) { + workload := GenericResource{ + Kind: unst.GetKind(), + Resource: *unst, } objMeta, err := meta.Accessor(unst) @@ -49,25 +49,27 @@ func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstruct return workload, err } podSpecMap := GetPodSpec(m) - b, err = json.Marshal(podSpecMap) - if err != nil { - return workload, err + if podSpecMap != nil { + b, err = json.Marshal(podSpecMap) + if err != nil { + return workload, err + } + podSpec := kubeAPICoreV1.PodSpec{} + err = json.Unmarshal(b, &podSpec) + if err != nil { + return workload, err + } + workload.PodSpec = &podSpec } - podSpec := kubeAPICoreV1.PodSpec{} - err = json.Unmarshal(b, &podSpec) - if err != nil { - return workload, err - } - workload.PodSpec = podSpec return workload, nil } -// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents -func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) { - workload := GenericWorkload{ +// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents +func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) { + workload := GenericResource{ Kind: "Pod", - PodSpec: podResource.Spec, + PodSpec: &podResource.Spec, ObjectMeta: podResource.ObjectMeta.GetObjectMeta(), } if originalObject != nil { @@ -77,13 +79,12 @@ func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject int } workload.OriginalObjectJSON = bytes - var unst unstructured.Unstructured - err = json.Unmarshal(bytes, &unst.Object) + err = json.Unmarshal(bytes, &workload.Resource.Object) if err != nil { logrus.Error("Couldn't marshal JSON for pod ", err) return workload, err } - objMeta, err := meta.Accessor(&unst) + objMeta, err := meta.Accessor(&workload.Resource) if err != nil { logrus.Error("Couldn't create meta accessor for unstructred ", err) return workload, err @@ -93,20 +94,30 @@ func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject int return workload, nil } -// NewGenericWorkload builds a new workload for a given Pod -func NewGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) { - workload, err := newGenericWorkload(ctx, podResource, dynamicClient, restMapper, objectCache) +// NewGenericResourceFromBytes parses a generic kubernetes resource +func NewGenericResourceFromBytes(contentBytes []byte) (GenericResource, error) { + unst := unstructured.Unstructured{} + err := yaml.Unmarshal(contentBytes, &unst.Object) + if err != nil { + return GenericResource{}, err + } + return NewGenericResourceFromUnstructured(&unst) +} + +// NewGenericResource builds a new workload for a given Pod +func NewGenericResource(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) { + workload, err := newGenericResource(ctx, podResource, dynamicClient, restMapper, objectCache) if err != nil { return workload, err } if len(workload.OriginalObjectJSON) == 0 { - return NewGenericWorkloadFromPod(podResource, podResource) + return NewGenericResourceFromPod(podResource, podResource) } return workload, err } -func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) { - workload, err := NewGenericWorkloadFromPod(podResource, nil) +func newGenericResource(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericResource, error) { + workload, err := NewGenericResourceFromPod(podResource, nil) if err != nil { return workload, err } @@ -207,37 +218,3 @@ func GetPodSpec(yaml map[string]interface{}) interface{} { } return nil } - -// GetWorkloadFromBytes parses a GenericWorkload -func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) { - yamlNode := make(map[string]interface{}) - err := yaml.Unmarshal(contentBytes, &yamlNode) - if err != nil { - logrus.Errorf("Invalid YAML: %s", string(contentBytes)) - return nil, err - } - finalDoc := make(map[string]interface{}) - finalDoc["metadata"] = yamlNode["metadata"] - finalDoc["apiVersion"] = "v1" - finalDoc["kind"] = "Pod" - podSpec := GetPodSpec(yamlNode) - if podSpec == nil { - return nil, nil - } - finalDoc["spec"] = podSpec - marshaledYaml, err := yaml.Marshal(finalDoc) - if err != nil { - logrus.Errorf("Could not marshal yaml: %v", err) - return nil, err - } - decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000) - pod := kubeAPICoreV1.Pod{} - err = decoder.Decode(&pod) - newController, err := NewGenericWorkloadFromPod(pod, yamlNode) - - if err != nil { - return nil, err - } - newController.Kind = yamlNode["kind"].(string) - return &newController, nil -} diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 7c2a8a52..59dcb402 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -31,14 +31,14 @@ import ( // ResourceProvider contains k8s resources to be audited type ResourceProvider struct { - ServerVersion string - CreationTime time.Time - SourceName string - SourceType string - Nodes []corev1.Node - Namespaces []corev1.Namespace - Controllers []GenericWorkload - ArbitraryKinds map[string]*unstructured.Unstructured + ServerVersion string + CreationTime time.Time + SourceName string + SourceType string + Nodes []corev1.Node + Namespaces []corev1.Namespace + Controllers []GenericResource + OtherKinds map[string][]GenericResource } type k8sResource struct { @@ -50,7 +50,7 @@ var podSpecFields = []string{"jobTemplate", "spec", "template"} // CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources func CreateResourceProvider(ctx context.Context, directory, workload string, c conf.Configuration) (*ResourceProvider, error) { if workload != "" { - return CreateResourceProviderFromWorkload(ctx, workload) + return CreateResourceProviderFromResource(ctx, workload) } if directory != "" { return CreateResourceProviderFromPath(directory) @@ -58,8 +58,8 @@ func CreateResourceProvider(ctx context.Context, directory, workload string, c c return CreateResourceProviderFromCluster(ctx, c) } -// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload -func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*ResourceProvider, error) { +// CreateResourceProviderFromResource creates a new ResourceProvider that just contains one workload +func CreateResourceProviderFromResource(ctx context.Context, workload string) (*ResourceProvider, error) { kubeConf, configError := config.GetConfig() if configError != nil { logrus.Errorf("Error fetching KubeConfig: %v", configError) @@ -77,7 +77,7 @@ func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (* } resources := ResourceProvider{ ServerVersion: serverVersion.Major + "." + serverVersion.Minor, - SourceType: "Workload", + SourceType: "Resource", SourceName: workload, CreationTime: time.Now(), Nodes: []corev1.Node{}, @@ -109,13 +109,13 @@ func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (* logrus.Errorf("Could not find workload %s: %v", workload, err) return nil, err } - workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj) + workloadObj, err := NewGenericResourceFromUnstructured(obj) if err != nil { logrus.Errorf("Could not parse workload %s: %v", workload, err) return nil, err } - resources.Controllers = []GenericWorkload{workloadObj} + resources.Controllers = []GenericResource{workloadObj} return &resources, nil } @@ -127,7 +127,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error) SourceName: directory, Nodes: []corev1.Node{}, Namespaces: []corev1.Namespace{}, - Controllers: []GenericWorkload{}, + Controllers: []GenericResource{}, } if directory == "-" { @@ -218,7 +218,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac } } - var arbitraryObjects map[string]*unstructured.Unstructured + otherObjects := map[string][]GenericResource{} for _, kind := range additionalKinds { groupKind := schema.ParseGroupKind(string(kind)) mapping, err := (restMapper).RESTMapping(groupKind) @@ -233,7 +233,14 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac return nil, err } for _, obj := range objects.Items { - arbitraryObjects[string(kind)] = &obj + res, err := NewGenericResourceFromUnstructured(&obj) + if err != nil { + return nil, err + } + if _, ok := otherObjects[res.Kind]; !ok { + otherObjects[res.Kind] = make([]GenericResource, 0) + } + otherObjects[res.Kind] = append(otherObjects[res.Kind], res) } } @@ -246,21 +253,21 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac } api := ResourceProvider{ - ServerVersion: serverVersion.Major + "." + serverVersion.Minor, - SourceType: "Cluster", - SourceName: clusterName, - CreationTime: time.Now(), - Nodes: nodes.Items, - Namespaces: namespaces.Items, - Controllers: controllers, - ArbitraryKinds: arbitraryObjects, + ServerVersion: serverVersion.Major + "." + serverVersion.Minor, + SourceType: "Cluster", + SourceName: clusterName, + CreationTime: time.Now(), + Nodes: nodes.Items, + Namespaces: namespaces.Items, + Controllers: controllers, + OtherKinds: otherObjects, } return &api, nil } // LoadControllers loads a list of controllers from the kubeResources Pods -func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) { - interfaces := []GenericWorkload{} +func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericResource, error) { + interfaces := []GenericResource{} deduped := map[string]corev1.Pod{} for _, pod := range pods { owners := pod.ObjectMeta.OwnerReferences @@ -271,7 +278,7 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod } for _, pod := range deduped { - workload, err := NewGenericWorkload(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache) + workload, err := NewGenericResource(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache) if err != nil { return nil, err } @@ -282,8 +289,8 @@ func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointe // Because the controllers with an Owner take on the name of the Owner, this eliminates any duplicates. // In cases like CronJobs older children can hang around, so this takes the most recent. -func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkload { - controllerMap := make(map[string]GenericWorkload) +func deduplicateControllers(inputControllers []GenericResource) []GenericResource { + controllerMap := make(map[string]GenericResource) for _, controller := range inputControllers { key := controller.ObjectMeta.GetNamespace() + "/" + controller.Kind + "/" + controller.ObjectMeta.GetName() oldController, ok := controllerMap[key] @@ -291,7 +298,7 @@ func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkloa controllerMap[key] = controller } } - results := make([]GenericWorkload, 0) + results := make([]GenericResource, 0) for _, controller := range controllerMap { results = append(results, controller) } @@ -346,32 +353,23 @@ func addResourceFromString(contents string, resources *ResourceProvider) error { if err != nil { return err } - workload, err := NewGenericWorkloadFromPod(pod, pod) + workload, err := NewGenericResourceFromPod(pod, pod) if err != nil { return err } resources.Controllers = append(resources.Controllers, workload) } else { - newController, err := GetWorkloadFromBytes(contentBytes) + newResource, err := NewGenericResourceFromBytes(contentBytes) if err != nil { return err } - if newController != nil { - resources.Controllers = append(resources.Controllers, *newController) - } else if resource.Kind != "" { - unst := unstructured.Unstructured{} - err = decoder.Decode(&unst) - if err != nil { - fmt.Println(resource.Kind) - return err - } - if resources.ArbitraryKinds == nil { - resources.ArbitraryKinds = map[string]*unstructured.Unstructured{ - resource.Kind: &unst, - } - } else { - resources.ArbitraryKinds[resource.Kind] = &unst + if newResource.PodSpec != nil { + resources.Controllers = append(resources.Controllers, newResource) + } else { + if _, ok := resources.OtherKinds[resource.Kind]; !ok { + resources.OtherKinds[resource.Kind] = make([]GenericResource, 0) } + resources.OtherKinds[resource.Kind] = append(resources.OtherKinds[resource.Kind], newResource) } } return err diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index 4f338591..26e22184 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -72,7 +72,7 @@ func TestAddResourcesFromReader(t *testing.T) { SourceName: "-", Nodes: []corev1.Node{}, Namespaces: []corev1.Namespace{}, - Controllers: []GenericWorkload{}, + Controllers: []GenericResource{}, } err = addResourcesFromReader(reader, resources) assert.NoError(t, err) diff --git a/pkg/validator/arbitrary_test.go b/pkg/validator/arbitrary_test.go index adfbc8a9..11012ce7 100644 --- a/pkg/validator/arbitrary_test.go +++ b/pkg/validator/arbitrary_test.go @@ -18,21 +18,22 @@ import ( "testing" conf "github.com/fairwindsops/polaris/pkg/config" + "github.com/fairwindsops/polaris/pkg/kube" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func TestValidateArbitraryKind(t *testing.T) { +func TestValidateOtherKind(t *testing.T) { c := conf.Configuration{ Checks: map[string]conf.Severity{ "pdbDisruptionsAllowedGreaterThanZero": conf.SeverityWarning, }, } pdb := unstructured.Unstructured{} + res, err := kube.NewGenericResourceFromUnstructured(&pdb) - var actualResult Result - actualResult, err := ValidateArbitraryKind(&c, &pdb) + actualResult, err := ValidateOtherKind(&c, res) if err != nil { panic(err) } diff --git a/pkg/validator/container.go b/pkg/validator/container.go index 25c59346..632ad1c2 100644 --- a/pkg/validator/container.go +++ b/pkg/validator/container.go @@ -22,7 +22,7 @@ import ( ) // ValidateContainer validates a single container from a given controller -func ValidateContainer(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) { +func ValidateContainer(conf *config.Configuration, controller kube.GenericResource, container *corev1.Container, isInit bool) (ContainerResult, error) { results, err := applyContainerSchemaChecks(conf, controller, container, isInit) if err != nil { return ContainerResult{}, err @@ -37,7 +37,7 @@ func ValidateContainer(conf *config.Configuration, controller kube.GenericWorklo } // ValidateAllContainers validates both init and regular containers -func ValidateAllContainers(conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) { +func ValidateAllContainers(conf *config.Configuration, controller kube.GenericResource) ([]ContainerResult, error) { results := []ContainerResult{} pod := controller.PodSpec for _, container := range pod.InitContainers { diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index 1de7d77c..010d3273 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -50,8 +50,8 @@ exemptions: - foo ` -func getEmptyWorkload(t *testing.T, name string) kube.GenericWorkload { - workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{ +func getEmptyWorkload(t *testing.T, name string) kube.GenericResource { + workload, err := kube.NewGenericResourceFromPod(corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, @@ -64,7 +64,7 @@ func testValidate(t *testing.T, container *corev1.Container, resourceConf *strin testValidateWithWorkload(t, container, resourceConf, getEmptyWorkload(t, controllerName), expectedDangers, expectedWarnings, expectedSuccesses) } -func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericWorkload, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { +func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourceConf *string, workload kube.GenericResource, expectedDangers []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { parsedConf, err := conf.Parse([]byte(*resourceConf)) assert.NoError(t, err, "Expected no error when parsing config") @@ -921,7 +921,7 @@ func TestValidateSecurity(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil) + workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil) assert.NoError(t, err) results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false) if err != nil { @@ -1066,7 +1066,7 @@ func TestValidateRunAsRoot(t *testing.T) { } for idx, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil) + workload, err := kube.NewGenericResourceFromPod(corev1.Pod{Spec: *tt.pod}, nil) assert.NoError(t, err) results, err := applyContainerSchemaChecks(&config, workload, tt.container, false) if err != nil { @@ -1168,7 +1168,7 @@ func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) { expectedSuccesses := []ResultMessage{} - workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{ + workload, err := kube.NewGenericResourceFromPod(corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Annotations: map[string]string{ diff --git a/pkg/validator/controller.go b/pkg/validator/controller.go index 3decadb1..ae898cca 100644 --- a/pkg/validator/controller.go +++ b/pkg/validator/controller.go @@ -22,14 +22,14 @@ import ( ) // ValidateController validates a single controller, returns a Result. -func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (Result, error) { +func ValidateController(conf *conf.Configuration, controller kube.GenericResource) (Result, error) { podResult, err := ValidatePod(conf, controller) if err != nil { return Result{}, err } var controllerResult ResultSet - controllerResult, err = applyControllerSchemaChecks(conf, controller) + controllerResult, err = applyTopLevelSchemaChecks(conf, controller) if err != nil { return Result{}, err } diff --git a/pkg/validator/controller_test.go b/pkg/validator/controller_test.go index 4834af70..b1351b4b 100644 --- a/pkg/validator/controller_test.go +++ b/pkg/validator/controller_test.go @@ -34,7 +34,7 @@ func TestValidateController(t *testing.T) { "hostPIDSet": conf.SeverityDanger, }, } - deployment, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil) + deployment, err := kube.NewGenericResourceFromPod(test.MockPod(), nil) assert.NoError(t, err) deployment.Kind = "Deployment" expectedSum := CountSummary{ @@ -126,7 +126,7 @@ func TestSkipHealthChecks(t *testing.T) { } pod := test.MockPod() pod.Spec.InitContainers = []corev1.Container{test.MockContainer("test")} - deployment, err := kube.NewGenericWorkloadFromPod(pod, nil) + deployment, err := kube.NewGenericResourceFromPod(pod, nil) assert.NoError(t, err) deployment.Kind = "Deployment" expectedSum := CountSummary{ @@ -149,7 +149,7 @@ func TestSkipHealthChecks(t *testing.T) { assert.EqualValues(t, ResultSet{}, actualResult.PodResult.ContainerResults[0].Results) assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[1].Results) - job, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil) + job, err := kube.NewGenericResourceFromPod(test.MockPod(), nil) assert.NoError(t, err) job.Kind = "Job" expectedSum = CountSummary{ @@ -167,7 +167,7 @@ func TestSkipHealthChecks(t *testing.T) { assert.EqualValues(t, expectedSum, actualResult.GetSummary()) assert.EqualValues(t, expectedResults, actualResult.PodResult.ContainerResults[0].Results) - cronjob, err := kube.NewGenericWorkloadFromPod(test.MockPod(), nil) + cronjob, err := kube.NewGenericResourceFromPod(test.MockPod(), nil) assert.NoError(t, err) cronjob.Kind = "CronJob" expectedSum = CountSummary{ @@ -207,11 +207,11 @@ func TestControllerExemptions(t *testing.T) { pod := test.MockPod() pod.ObjectMeta.Namespace = "foo" - workload, err := kube.NewGenericWorkloadFromPod(pod, nil) + workload, err := kube.NewGenericResourceFromPod(pod, nil) assert.NoError(t, err) workload.Kind = "Deployment" resources := &kube.ResourceProvider{ - Controllers: []kube.GenericWorkload{workload}, + Controllers: []kube.GenericResource{workload}, } actualResults, err = ValidateControllers(&c, resources) diff --git a/pkg/validator/fullaudit.go b/pkg/validator/fullaudit.go index fa35a2c2..f7f14dfa 100644 --- a/pkg/validator/fullaudit.go +++ b/pkg/validator/fullaudit.go @@ -28,7 +28,7 @@ func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider, o } controllerCount := len(results) - arbitraryResults, err := ValidateArbitraryKinds(&config, kubeResources) + arbitraryResults, err := ValidateOtherKinds(&config, kubeResources) if err != nil { return AuditData{}, err } diff --git a/pkg/validator/arbitrary.go b/pkg/validator/other.go similarity index 53% rename from pkg/validator/arbitrary.go rename to pkg/validator/other.go index 26c987a4..b83282ac 100644 --- a/pkg/validator/arbitrary.go +++ b/pkg/validator/other.go @@ -17,34 +17,34 @@ package validator import ( conf "github.com/fairwindsops/polaris/pkg/config" "github.com/fairwindsops/polaris/pkg/kube" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -// ValidateArbitraryKinds validates all the unstructured objects in a ResourceProvider -func ValidateArbitraryKinds(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) { +// ValidateOtherKinds validates all the unstructured objects in a ResourceProvider +func ValidateOtherKinds(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]Result, error) { var results []Result - for _, arb := range kubeResources.ArbitraryKinds { - result, err := ValidateArbitraryKind(config, arb) - if err != nil { - return []Result{}, err + for _, resources := range kubeResources.OtherKinds { + for _, res := range resources { + result, err := ValidateOtherKind(config, res) + if err != nil { + return []Result{}, err + } + results = append(results, result) } - results = append(results, result) } return results, nil } -// ValidateArbitraryKind validates a single unstructured object -func ValidateArbitraryKind(config *conf.Configuration, arb *unstructured.Unstructured) (Result, error) { - results, err := applyArbitrarySchemaChecks(config, arb) +// ValidateOtherKind validates a single unstructured object +func ValidateOtherKind(config *conf.Configuration, res kube.GenericResource) (Result, error) { + results, err := applyTopLevelSchemaChecks(config, res) if err != nil { return Result{}, err } result := Result{ - Kind: arb.GetKind(), - Name: arb.GetName(), - Namespace: arb.GetNamespace(), + Kind: res.Kind, + Name: res.ObjectMeta.GetName(), + Namespace: res.ObjectMeta.GetNamespace(), Results: results, } diff --git a/pkg/validator/pod.go b/pkg/validator/pod.go index 6bef7b91..6a681b28 100644 --- a/pkg/validator/pod.go +++ b/pkg/validator/pod.go @@ -20,7 +20,7 @@ import ( ) // ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult. -func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) { +func ValidatePod(conf *config.Configuration, controller kube.GenericResource) (PodResult, error) { podResults, err := applyPodSchemaChecks(conf, controller) if err != nil { return PodResult{}, err diff --git a/pkg/validator/pod_test.go b/pkg/validator/pod_test.go index 4af40cb1..79590c03 100644 --- a/pkg/validator/pod_test.go +++ b/pkg/validator/pod_test.go @@ -36,7 +36,7 @@ func TestValidatePod(t *testing.T) { } p := test.MockPod() - deployment, err := kube.NewGenericWorkloadFromPod(p, nil) + deployment, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(4), @@ -73,7 +73,7 @@ func TestInvalidIPCPod(t *testing.T) { p := test.MockPod() p.Spec.HostIPC = true - workload, err := kube.NewGenericWorkloadFromPod(p, nil) + workload, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(3), @@ -109,7 +109,7 @@ func TestInvalidNetworkPod(t *testing.T) { p := test.MockPod() p.Spec.HostNetwork = true - workload, err := kube.NewGenericWorkloadFromPod(p, nil) + workload, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(3), @@ -146,7 +146,7 @@ func TestInvalidPIDPod(t *testing.T) { p := test.MockPod() p.Spec.HostPID = true - workload, err := kube.NewGenericWorkloadFromPod(p, nil) + workload, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(3), @@ -192,7 +192,7 @@ func TestExemption(t *testing.T) { p.ObjectMeta = metav1.ObjectMeta{ Name: "foo", } - workload, err := kube.NewGenericWorkloadFromPod(p, nil) + workload, err := kube.NewGenericResourceFromPod(p, nil) assert.NoError(t, err) expectedSum := CountSummary{ Successes: uint(3), diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index d94d1728..ccfc7893 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -9,9 +9,7 @@ import ( "github.com/gobuffalo/packr/v2" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/yaml" "github.com/fairwindsops/polaris/pkg/config" @@ -53,6 +51,21 @@ var ( } ) +type schemaTestCase struct { + Target config.TargetKind + Resource kube.GenericResource + IsInitContianer bool + Container *corev1.Container +} + +func (test schemaTestCase) getName() string { + name := fmt.Sprintf("%s/%s/%s", test.Resource.Kind, test.Resource.ObjectMeta.GetNamespace(), test.Resource.ObjectMeta.GetName()) + if test.Container != nil { + name += "/" + test.Container.Name + } + return name + ": " + string(test.Target) +} + func init() { schemaBox = packr.New("Schemas", "../../checks") for _, checkID := range checkOrder { @@ -83,7 +96,10 @@ func parseCheck(rawBytes []byte) (config.SchemaCheck, error) { } } -func resolveCheck(conf *config.Configuration, checkID, kind string, target config.TargetKind, meta metaV1.Object, containerName string, isInitContainer bool) (*config.SchemaCheck, error) { +func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*config.SchemaCheck, error) { + if !conf.DisallowExemptions && hasExemptionAnnotation(test.Resource.ObjectMeta, checkID) { + return nil, nil + } check, ok := conf.CustomChecks[checkID] if !ok { check, ok = builtInChecks[checkID] @@ -92,12 +108,14 @@ func resolveCheck(conf *config.Configuration, checkID, kind string, target confi return nil, fmt.Errorf("Check %s not found", checkID) } - namespace := meta.GetNamespace() - name := meta.GetName() - if !conf.IsActionable(check.ID, namespace, name, containerName) { + containerName := "" + if test.Container != nil { + containerName = test.Container.Name + } + if !conf.IsActionable(check.ID, test.Resource.ObjectMeta, containerName) { return nil, nil } - if !check.IsActionable(target, kind, isInitContainer) { + if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) { return nil, nil } return &check, nil @@ -121,8 +139,8 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo const exemptionAnnotationKey = "polaris.fairwinds.com/exempt" const exemptionAnnotationPattern = "polaris.fairwinds.com/%s-exempt" -func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool { - annot := ctrl.ObjectMeta.GetAnnotations() +func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool { + annot := objMeta.GetAnnotations() val := annot[exemptionAnnotationKey] if strings.ToLower(val) == "true" { return true @@ -135,129 +153,124 @@ func hasExemptionAnnotation(ctrl kube.GenericWorkload, checkID string) bool { return false } -func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) { +func ApplyAllSchemaChecks(conf *config.Configuration, resource kube.GenericResource) (Result, error) { + finalResult := Result{ + Kind: resource.Kind, + Name: resource.ObjectMeta.GetName(), + Namespace: resource.ObjectMeta.GetNamespace(), + } + resultSet, err := applyTopLevelSchemaChecks(conf, resource) + if err != nil { + return finalResult, err + } + finalResult.Results = resultSet + if resource.PodSpec != nil { + podRS, err := applyPodSchemaChecks(conf, resource) + if err != nil { + return finalResult, err + } + podRes := PodResult{ + Results: podRS, + ContainerResults: []ContainerResult{}, + } + + for _, container := range resource.PodSpec.InitContainers { + results, err := applyContainerSchemaChecks(conf, resource, &container, true) + if err != nil { + return finalResult, err + } + cRes := ContainerResult{ + Name: container.Name, + Results: results, + } + podRes.ContainerResults = append(podRes.ContainerResults, cRes) + } + for _, container := range resource.PodSpec.Containers { + results, err := applyContainerSchemaChecks(conf, resource, &container, false) + if err != nil { + return finalResult, err + } + cRes := ContainerResult{ + Name: container.Name, + Results: results, + } + podRes.ContainerResults = append(podRes.ContainerResults, cRes) + } + + finalResult.PodResult = &podRes + } + return finalResult, nil +} + +func applyTopLevelSchemaChecks(conf *config.Configuration, res kube.GenericResource) (ResultSet, error) { + test := schemaTestCase{ + Target: config.TargetController, + Resource: res, + } + return applySchemaChecks(conf, test) +} + +func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericResource) (ResultSet, error) { + test := schemaTestCase{ + Target: config.TargetPod, + Resource: controller, + } + return applySchemaChecks(conf, test) +} + +func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericResource, container *corev1.Container, isInit bool) (ResultSet, error) { + test := schemaTestCase{ + Target: config.TargetContainer, + Resource: controller, + Container: container, + } + return applySchemaChecks(conf, test) +} + +func applySchemaChecks(conf *config.Configuration, test schemaTestCase) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) for _, checkID := range checkIDs { - if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) { - continue - } - check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetPod, controller.ObjectMeta, "", false) - + result, err := applySchemaCheck(conf, checkID, test) if err != nil { - return nil, err - } else if check == nil { - continue + return results, err } - passes, err := check.CheckPod(&controller.PodSpec) - if err != nil { - return nil, err + if result != nil { + results[checkID] = *result } - results[check.ID] = makeResult(conf, check, passes) } return results, nil } -func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) { - results := ResultSet{} - checkIDs := getSortedKeys(conf.Checks) - for _, checkID := range checkIDs { - if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) { - continue - } - check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetController, controller.ObjectMeta, "", false) - - if err != nil { - return nil, err - } else if check == nil { - continue - } - passes, err := check.CheckController(controller.OriginalObjectJSON) - if err != nil { - return nil, err - } - results[check.ID] = makeResult(conf, check, passes) +func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTestCase) (*ResultMessage, error) { + check, err := resolveCheck(conf, checkID, test) + if err != nil { + return nil, err + } else if check == nil { + return nil, nil } - return results, nil -} - -func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) { - results := ResultSet{} - checkIDs := getSortedKeys(conf.Checks) - for _, checkID := range checkIDs { - if !conf.DisallowExemptions && hasExemptionAnnotation(controller, checkID) { - continue - } - check, err := resolveCheck(conf, checkID, controller.Kind, config.TargetContainer, controller.ObjectMeta, container.Name, isInit) - - if err != nil { - return nil, err - } else if check == nil { - continue - } - var passes bool - if check.SchemaTarget == config.TargetPod { - podCopy := controller.PodSpec + var passes bool + if check.SchemaTarget != "" { + if check.SchemaTarget == config.TargetPod && check.Target == config.TargetContainer { + podCopy := *test.Resource.PodSpec podCopy.InitContainers = []corev1.Container{} - podCopy.Containers = []corev1.Container{*container} + podCopy.Containers = []corev1.Container{*test.Container} passes, err = check.CheckPod(&podCopy) } else { - passes, err = check.CheckContainer(container) + return nil, fmt.Errorf("Unknown combination of target (%s) and schema target (%s)", check.Target, check.SchemaTarget) } - if err != nil { - return nil, err - } - results[check.ID] = makeResult(conf, check, passes) + } else if check.Target == config.TargetPod { + passes, err = check.CheckPod(test.Resource.PodSpec) + } else if check.Target == config.TargetContainer { + passes, err = check.CheckContainer(test.Container) + } else { + passes, err = check.CheckObject(test.Resource.Resource.Object) } - return results, nil -} - -func applyOtherSchemaChecks(conf *config.Configuration, unst *unstructured.Unstructured) (ResultSet, error) { - results := ResultSet{} - checkIDs := getSortedKeys(conf.Checks) - objMeta, err := meta.Accessor(unst) if err != nil { - return results, err + return nil, err } - for _, checkID := range checkIDs { - check, err := resolveCheck(conf, checkID, unst.GetKind(), "", objMeta, "", false) - - if err != nil { - return nil, err - } else if check == nil { - continue - } - passes, err := check.CheckObject(unst) - if err != nil { - return nil, err - } - results[check.ID] = makeResult(conf, check, passes) - } - return results, nil -} - -func applyArbitrarySchemaChecks(conf *config.Configuration, unst *unstructured.Unstructured) (ResultSet, error) { - results := ResultSet{} - objMeta, err := meta.Accessor(unst) - if err != nil { - return results, err - } - checkIDs := getSortedKeys(conf.Checks) - for _, checkID := range checkIDs { - check, err := resolveCheck(conf, checkID, unst.GetKind(), "", objMeta, "", false) - - if err != nil { - return nil, err - } else if check == nil { - continue - } - passes, err := check.CheckObject(unst) - if err != nil { - return nil, err - } - results[check.ID] = makeResult(conf, check, passes) - } - return results, nil + result := makeResult(conf, check, passes) + return &result, nil } func getSortedKeys(m map[string]config.Severity) []string { diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index eb53befd..3e4a8972 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -99,7 +99,7 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.PodResult, } else { pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw) } - controller, err := kube.NewGenericWorkloadFromPod(pod, originalObject) + controller, err := kube.NewGenericResourceFromPod(pod, originalObject) if err != nil { return nil, err } diff --git a/test/checks_test.go b/test/checks_test.go index 4b37de2c..e06b8929 100644 --- a/test/checks_test.go +++ b/test/checks_test.go @@ -1,6 +1,7 @@ package test import ( + "fmt" "io/ioutil" "path/filepath" "runtime" @@ -52,14 +53,17 @@ func init() { func TestChecks(t *testing.T) { for _, tc := range testCases { - workload, err := kube.GetWorkloadFromBytes(tc.input) - assert.NoError(t, err) + res, err := kube.NewGenericResourceFromBytes(tc.input) + if err != nil { + fmt.Println("error parsing", string(tc.input)) + panic(err) + } c, err := config.Parse([]byte("checks:\n " + tc.check + ": danger")) assert.NoError(t, err) - var result validator.Result - result, err = validator.ValidateController(&c, *workload) + result, err := validator.ApplyAllSchemaChecks(&c, res) assert.NoError(t, err) summary := result.GetSummary() + fmt.Println(tc.check, summary) if tc.failure { message := "Check " + tc.check + " passed unexpectedly" assert.Equal(t, uint(0), summary.Successes, message)