Repository: zegl/kube-score
Pull request #632 (2024-11-21 11:28:34)

build(deps): bump k8s.io/api from 0.31.2 to 0.31.3
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some time.

Note: if you make any changes to this PR yourself, they will take precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.31.2 to 0.31.3.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/kubernetes/api/commit/b7783abfc99c11f3b56fee4e5cf99023fcc8120a"><code>b7783ab</code></a> Update dependencies to v0.31.3 tag</li>
<li>See full diff in <a href="https://github.com/kubernetes/api/compare/v0.31.2...v0.31.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k8s.io/api&package-manager=go_modules&previous-version=0.31.2&new-version=0.31.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/go.mod b/go.mod
index 0e89ae2..1a4b63c 100644
--- a/go.mod
+++ b/go.mod
@@ -9,10 +9,10 @@ require (
 	github.com/mattn/go-isatty v0.0.20
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.9.0
-	golang.org/x/term v0.25.0
+	golang.org/x/term v0.26.0
 	gopkg.in/yaml.v3 v3.0.1
-	k8s.io/api v0.31.2
-	k8s.io/apimachinery v0.31.2
+	k8s.io/api v0.31.3
+	k8s.io/apimachinery v0.31.3
 	k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
 )
 
@@ -29,7 +29,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	golang.org/x/net v0.26.0 // indirect
-	golang.org/x/sys v0.26.0 // indirect
+	golang.org/x/sys v0.27.0 // indirect
 	golang.org/x/text v0.16.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 31f18e0..fcdbc9b 100644
--- a/go.sum
+++ b/go.sum
@@ -75,10 +75,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
+golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
@@ -101,10 +101,10 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
-k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
-k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
-k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
+k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8=
+k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE=
+k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4=
+k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
diff --git a/score/score.go b/score/score.go
index 390d7af..df8a06e 100644
--- a/score/score.go
+++ b/score/score.go
@@ -28,6 +28,10 @@ import (
 func RegisterAllChecks(allObjects ks.AllTypes, checksConfig *checks.Config, runConfig *config.RunConfiguration) *checks.Checks {
 	allChecks := checks.New(checksConfig)
 
+	if runConfig == nil {
+		runConfig = &config.RunConfiguration{}
+	}
+
 	deployment.Register(allChecks, allObjects)
 	ingress.Register(allChecks, allObjects)
 	cronjob.Register(allChecks)
diff --git a/score/security/security.go b/score/security/security.go
index 4262a8d..6cbc10d 100644
--- a/score/security/security.go
+++ b/score/security/security.go
@@ -109,20 +109,51 @@ func containerSecurityContextUserGroupID(ps ks.PodSpecer) (score scorecard.TestS
 	return
 }
 
-// podSeccompProfile checks that a Seccommp profile is configured for the pod
+// podSeccompProfile checks that a Seccommp profile is configured. The
+// seccompProfile can be specified either through annotation or securityContext.
+// There are two ways to specify the seccomp profile via securityContext --
+// at the pod level or  container level.
+// Pod level seccomp profile is preferred since it is applied to all containers.
 func podSeccompProfile(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
 	metadata := ps.GetPodTemplateSpec().ObjectMeta
 
-	seccompAnnotated := false
+	secured := false
+
+	// Check if the seccomp profile is set via annotation
 	if metadata.Annotations != nil {
 		if _, ok := metadata.Annotations["seccomp.security.alpha.kubernetes.io/defaultProfileName"]; ok {
-			seccompAnnotated = true
+			secured = true
+		}
+	}
+
+	//Check if seccomp is set via securityContext at Pod or Container Level
+	if !secured {
+		elements := make(map[string]bool)
+		if ps.GetPodTemplateSpec().Spec.SecurityContext != nil && ps.GetPodTemplateSpec().Spec.SecurityContext.SeccompProfile != nil {
+			secured = true
+		} else {
+			// This does not check initContainers, only Containers
+			for _, container := range ps.GetPodTemplateSpec().Spec.Containers {
+				if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil {
+					elements[container.Name] = true
+					secured = true
+				} else {
+					score.AddComment(container.Name, "The container has not configured Seccomp", "Running containers with Seccomp is recommended to reduce the kernel attack surface")
+					elements[container.Name] = false
+				}
+			}
+		}
+
+		// one unsecured container is enough to fail the test
+		for _, value := range elements {
+			if !value {
+				secured = false
+			}
 		}
 	}
 
-	if !seccompAnnotated {
+	if !secured {
 		score.Grade = scorecard.GradeWarning
-		score.AddComment(metadata.Name, "The pod has not configured Seccomp for its containers", "Running containers with Seccomp is recommended to reduce the kernel attack surface")
 	} else {
 		score.Grade = scorecard.GradeAllOK
 	}


Test patch:
diff --git a/score/security_test.go b/score/security_test.go
index 2f077ea..2c3aa6f 100644
--- a/score/security_test.go
+++ b/score/security_test.go
@@ -21,6 +21,50 @@ func TestContainerSeccompMissing(t *testing.T) {
 
 }
 
+func TestPodSecurityContextPresent(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-securecontext-ok.yaml")}, nil, &config.RunConfiguration{
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", scorecard.GradeAllOK)
+}
+
+func TestContainerSecurityContextSeccompPresent(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-container-securecontext-ok.yaml")}, nil, &config.RunConfiguration{
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", scorecard.GradeAllOK)
+}
+
+func TestPodSecurityContextSeccompAbsent(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-securecontext-warning.yaml")}, nil, &config.RunConfiguration{
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", scorecard.GradeWarning)
+}
+
+func TestContainerSecurityContextSeccompAbsent(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-seccomp-profile"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-container-securecontext-warning.yaml")}, nil, &config.RunConfiguration{
+		EnabledOptionalTests: structMap,
+	}, "Container Seccomp Profile", scorecard.GradeWarning)
+}
+
 func TestContainerSeccompMissingNotRunByDefault(t *testing.T) {
 	t.Parallel()
 	skipped := wasSkipped(t, []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, nil, nil, "Container Seccomp Profile")
diff --git a/score/testdata/pod-seccomp-container-securecontext-ok.yaml b/score/testdata/pod-seccomp-container-securecontext-ok.yaml
new file mode 100644
index 0000000..51ea534
--- /dev/null
+++ b/score/testdata/pod-seccomp-container-securecontext-ok.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+    securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True
+      seccompProfile:
+        type: RuntimeDefault 
+
diff --git a/score/testdata/pod-seccomp-container-securecontext-warning.yaml b/score/testdata/pod-seccomp-container-securecontext-warning.yaml
new file mode 100644
index 0000000..e5bc54d
--- /dev/null
+++ b/score/testdata/pod-seccomp-container-securecontext-warning.yaml
@@ -0,0 +1,31 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+    securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True
+      seccompProfile:
+        type: RuntimeDefault 
+  - name: foobaz
+    image: foo/baz:latest
+    securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True
+  - name: foozed
+    image: foo/zed:latest
+    securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True
+      seccompProfile:
+        type: RuntimeDefault 
\ No newline at end of file
diff --git a/score/testdata/pod-seccomp-securecontext-ok.yaml b/score/testdata/pod-seccomp-securecontext-ok.yaml
new file mode 100644
index 0000000..c1eba33
--- /dev/null
+++ b/score/testdata/pod-seccomp-securecontext-ok.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  securityContext:
+    seccompProfile:
+      type: RuntimeDefault
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+    securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True
diff --git a/score/testdata/pod-seccomp-securecontext-warning.yaml b/score/testdata/pod-seccomp-securecontext-warning.yaml
new file mode 100644
index 0000000..26014f0
--- /dev/null
+++ b/score/testdata/pod-seccomp-securecontext-warning.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  securityContext:
+      privileged: False
+      runAsUser: 30000
+      runAsGroup: 30000
+      readOnlyRootFilesystem: True 
+  containers:
+  - name: foobar
+    image: foo/bar:latest
+  - name: foobaz
+    image: foo/baz:latest