mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-23 22:26:34 +00:00
refactor a bunch
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+46
-48
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
+127
-114
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+8
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user