diff --git a/checks/all/all.go b/checks/all/all.go index e835ee8..6675d49 100644 --- a/checks/all/all.go +++ b/checks/all/all.go @@ -6,4 +6,5 @@ import ( _ "github.com/digitalocean/clusterlint/checks/basic" _ "github.com/digitalocean/clusterlint/checks/doks" _ "github.com/digitalocean/clusterlint/checks/noop" + _ "github.com/digitalocean/clusterlint/checks/security" ) diff --git a/checks/basic/latest_tag_test.go b/checks/basic/latest_tag_test.go index fbb459f..a2d0186 100644 --- a/checks/basic/latest_tag_test.go +++ b/checks/basic/latest_tag_test.go @@ -153,33 +153,37 @@ func TestLatestTagWarning(t *testing.T) { func initPod() *kube.Objects { objs := &kube.Objects{ - Pods: &corev1.PodList{}, + Pods: &corev1.PodList{ + Items: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"}, + }, + }, + }, } return objs } func container(image string) *kube.Objects { objs := initPod() - objs.Pods = &corev1.PodList{ - Items: []corev1.Pod{ + objs.Pods.Items[0].Spec = corev1.PodSpec{ + Containers: []corev1.Container{ { - ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"}, - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "bar", Image: image}}}, - }, - }, + Name: "bar", + Image: image, + }}, } return objs } func initContainer(image string) *kube.Objects { objs := initPod() - objs.Pods = &corev1.PodList{ - Items: []corev1.Pod{ + objs.Pods.Items[0].Spec = corev1.PodSpec{ + InitContainers: []corev1.Container{ { - ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"}, - Spec: corev1.PodSpec{InitContainers: []corev1.Container{{Name: "bar", Image: image}}}, - }, - }, + Name: "bar", + Image: image, + }}, } return objs } diff --git a/checks/security/privileged_containers.go b/checks/security/privileged_containers.go new file mode 100644 index 0000000..1b3f834 --- /dev/null +++ b/checks/security/privileged_containers.go @@ -0,0 +1,59 @@ +package security + +import ( + "fmt" + + "github.com/digitalocean/clusterlint/checks" + "github.com/digitalocean/clusterlint/kube" + corev1 "k8s.io/api/core/v1" +) + +func init() { + checks.Register(&privilegedContainerCheck{}) +} + +type privilegedContainerCheck struct{} + +// Name returns a unique name for this check. +func (pc *privilegedContainerCheck) Name() string { + return "privileged-containers" +} + +// Groups returns a list of group names this check should be part of. +func (pc *privilegedContainerCheck) Groups() []string { + return []string{"security"} +} + +// Description returns a detailed human-readable description of what this check +// does. +func (pc *privilegedContainerCheck) Description() string { + return "Checks if there are pods with containers in privileged mode" +} + +// Run runs this check on a set of Kubernetes objects. It can return warnings +// (low-priority problems) and errors (high-priority problems) as well as an +// error value indicating that the check failed to run. +func (pc *privilegedContainerCheck) Run(objects *kube.Objects) (warnings []error, errors []error, err error) { + var w []error + + for _, pod := range objects.Pods.Items { + podName := pod.GetName() + namespace := pod.GetNamespace() + w = append(w, checkPrivileged(pod.Spec.Containers, podName, namespace)...) + w = append(w, checkPrivileged(pod.Spec.InitContainers, podName, namespace)...) + } + + return w, nil, nil +} + +// checkPrivileged checks if the container is running in privileged mode +// Adds a warning if it finds any privileged container +func checkPrivileged(containers []corev1.Container, podName string, namespace string) []error { + var w []error + for _, container := range containers { + if container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged { + w = append(w, fmt.Errorf("[Best Practice] Privileged container '%s' found in pod '%s', namespace '%s'.", container.Name, podName, namespace)) + } + } + return w +} diff --git a/checks/security/privileged_containers_test.go b/checks/security/privileged_containers_test.go new file mode 100644 index 0000000..b5be196 --- /dev/null +++ b/checks/security/privileged_containers_test.go @@ -0,0 +1,181 @@ +package security + +import ( + "fmt" + "testing" + + "github.com/digitalocean/clusterlint/checks" + "github.com/digitalocean/clusterlint/kube" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPrivilegedContainersCheckMeta(t *testing.T) { + privilegedContainerCheck := privilegedContainerCheck{} + assert.Equal(t, "privileged-containers", privilegedContainerCheck.Name()) + assert.Equal(t, "Checks if there are pods with containers in privileged mode", privilegedContainerCheck.Description()) + assert.Equal(t, []string{"security"}, privilegedContainerCheck.Groups()) +} + +func TestPrivilegedContainersCheckRegistration(t *testing.T) { + privilegedContainerCheck := &privilegedContainerCheck{} + check, err := checks.Get("privileged-containers") + assert.Equal(t, check, privilegedContainerCheck) + assert.Nil(t, err) +} + +func TestPrivilegedContainerWarning(t *testing.T) { + scenarios := []struct { + name string + arg *kube.Objects + expected []error + }{ + { + name: "no pods", + arg: initPod(), + expected: nil, + }, + { + name: "pod with container in privileged mode", + arg: container(true), + expected: warnings(), + }, + { + name: "pod with container.SecurityContext = nil", + arg: containerSecurityContextNil(), + expected: nil, + }, + { + name: "pod with container.SecurityContext.Privileged = nil", + arg: containerPrivilegedNil(), + expected: nil, + }, + { + name: "pod with container in regular mode", + arg: container(false), + expected: nil, + }, + { + name: "pod with init container in privileged mode", + arg: initContainer(true), + expected: warnings(), + }, + { + name: "pod with initContainer.SecurityContext = nil", + arg: initContainerSecurityContextNil(), + expected: nil, + }, + { + name: "pod with initContainer.SecurityContext.Privileged = nil", + arg: initContainerPrivilegedNil(), + expected: nil, + }, + { + name: "pod with init container in regular mode", + arg: initContainer(false), + expected: nil, + }, + } + + privilegedContainerCheck := privilegedContainerCheck{} + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + w, e, err := privilegedContainerCheck.Run(scenario.arg) + assert.ElementsMatch(t, scenario.expected, w) + assert.Empty(t, e) + assert.Nil(t, err) + }) + } +} + +func initPod() *kube.Objects { + objs := &kube.Objects{ + Pods: &corev1.PodList{ + Items: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"}, + }, + }, + }, + } + return objs +} + +func container(privileged bool) *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "bar", + SecurityContext: &corev1.SecurityContext{Privileged: &privileged}, + }}, + } + return objs +} + +func containerSecurityContextNil() *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "bar", + }}, + } + return objs +} + +func containerPrivilegedNil() *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "bar", + SecurityContext: &corev1.SecurityContext{}, + }}, + } + return objs +} + +func initContainer(privileged bool) *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "bar", + SecurityContext: &corev1.SecurityContext{Privileged: &privileged}, + }}, + } + return objs +} + +func initContainerSecurityContextNil() *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "bar", + }}, + } + return objs +} + +func initContainerPrivilegedNil() *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Spec = corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "bar", + SecurityContext: &corev1.SecurityContext{}, + }}, + } + return objs +} + +func warnings() []error { + w := []error{ + fmt.Errorf("[Best Practice] Privileged container 'bar' found in pod 'pod_foo', namespace 'k8s'."), + } + return w +}