Repository: zegl/kube-score (go)

Issue:
Feature Request: Check for Seccomp profiles on pods
Pods allow configuring a Seccomp profile for their pods to reduce the kernel attack surface by adding an annotation. e.g.:
```
annotations:
    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'runtime/default'
```
This would be a great security check to come with kube-score.


Pull request description:
score/security: optional check for seccomp annotation on pods
<!--
    Optional: Add this change to the release notes by adding a RELNOTE comment
    If this shouldn't appear in the notes, simply remove this.
-->

```
RELNOTE: New check for seccomp profiles on pods. Can be enabled with 
`--enable-optional-test container-seccomp-profile`.
```

resolves #191 

Patch:
diff --git a/README_CHECKS.md b/README_CHECKS.md
index 6c2f106..4ab5186 100644
--- a/README_CHECKS.md
+++ b/README_CHECKS.md
@@ -13,6 +13,7 @@
 | networkpolicy-targets-pod | NetworkPolicy | Makes sure that all NetworkPolicies targets at least one Pod | default |
 | pod-probes | Pod | Makes sure that all Pods have bot a readinessProbe and a libenessProbe configured | default |
 | container-security-context | Pod | Makes sure that all pods have good securityContexts configured | default |
+| container-seccomp-profile | Pod | Makes sure that all pods have a Seccomp profile configured | optional |
 | service-targets-pod | Service | Makes sure that all Services targets a Pod | default |
 | service-type | Service | Makes sure that the Service type is not NodePort | default |
 | stable-version | all | Checks if the object is using a deprecated apiVersion | default |
diff --git a/score/security/security.go b/score/security/security.go
index 1a74ec7..a0d768c 100644
--- a/score/security/security.go
+++ b/score/security/security.go
@@ -10,6 +10,7 @@ import (
 
 func Register(allChecks *checks.Checks) {
 	allChecks.RegisterPodCheck("Container Security Context", `Makes sure that all pods have good securityContexts configured`, containerSecurityContext)
+	allChecks.RegisterOptionalPodCheck("Container Seccomp Profile", `Makes sure that all pods have at a seccomp policy configured.`, podSeccompProfile)
 }
 
 // containerSecurityContext checks that the recommended securityPolicy options are set
@@ -78,3 +79,24 @@ func containerSecurityContext(podTemplate corev1.PodTemplateSpec, typeMeta metav
 
 	return
 }
+
+// podSeccompProfile checks if the any Seccommp profile is configured for the pod
+func podSeccompProfile(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
+	metadata := podTemplate.ObjectMeta
+
+	seccompAnnotated := false
+	if metadata.Annotations != nil {
+		if _, ok := metadata.Annotations["seccomp.security.alpha.kubernetes.io/defaultProfileName"]; ok {
+			seccompAnnotated = true
+		}
+	}
+
+	if !seccompAnnotated {
+		score.Grade = scorecard.GradeWarning
+		score.AddComment(metadata.Name, "The pod has not configured Seccomp for its containers", "Running containers with Seccomp is reccomended to reduce the kernel attack surface")
+	} else {
+		score.Grade = scorecard.GradeAllOK
+	}
+
+	return
+}


Test patch:
diff --git a/score/security_test.go b/score/security_test.go
index 7a009b0..01982f8 100644
--- a/score/security_test.go
+++ b/score/security_test.go
@@ -3,12 +3,14 @@ package score
 import (
 	"bytes"
 	"github.com/stretchr/testify/assert"
+	"io"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/yaml"
 	"testing"
 
+	"github.com/zegl/kube-score/config"
 	"github.com/zegl/kube-score/scorecard"
 )
 
@@ -204,3 +206,27 @@ func TestContainerSecurityContextAllGood(t *testing.T) {
 	c := testExpectedScore(t, "pod-security-context-all-good.yaml", "Container Security Context", 10)
 	assert.Empty(t, c)
 }
+
+func TestContainerSeccompMissing(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, config.Configuration{
+		AllFiles:             []io.Reader{testFile("pod-seccomp-no-annotation.yaml")},
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", 5)
+}
+
+func TestContainerSeccompAllGood(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, config.Configuration{
+		AllFiles:             []io.Reader{testFile("pod-seccomp-annotated.yaml")},
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", 10)
+}
diff --git a/score/testdata/pod-seccomp-annotated.yaml b/score/testdata/pod-seccomp-annotated.yaml
new file mode 100644
index 0000000..3b3ec38
--- /dev/null
+++ b/score/testdata/pod-seccomp-annotated.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+  annotations:
+    seccomp.security.alpha.kubernetes.io/defaultProfileName:  'runtime/default'
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+    
\ No newline at end of file
diff --git a/score/testdata/pod-seccomp-no-annotation.yaml b/score/testdata/pod-seccomp-no-annotation.yaml
new file mode 100644
index 0000000..0957461
--- /dev/null
+++ b/score/testdata/pod-seccomp-no-annotation.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+    
\ No newline at end of file