Skip to content

Commit a3ebfd1

Browse files
bors[bot]Markus Lackner
and
Markus Lackner
authored
Merge #326
326: score/security: added optional, splitted probes as future replacemnt … r=zegl a=markuslackner …for security context probe Fixes #325 ``` RELNOTE: container-security-context now deprecated and added optional probes container-security-context-user-group-id, container-security-context-privileged and container-security-context-readonlyrootfilesystem as replacement ``` Co-authored-by: Markus Lackner <[email protected]>
2 parents d9be66a + c0ad0a1 commit a3ebfd1

8 files changed

+269
-4
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ For a full list of checks, see [README_CHECKS.md](README_CHECKS.md).
3838
* Deployments and StatefulSets should have a `PodDisruptionPolicy`
3939
* Deployments and StatefulSets should have host PodAntiAffinity configured
4040
* Container probes, a readiness should be configured, and should not be identical to the liveness probe. Read more in [README_PROBES.md](README_PROBES.md).
41-
* Container securityContext, run as high number user/group, do not run as root or with privileged root fs
41+
* Container securityContext, run as high number user/group, do not run as root or with privileged root fs. Read more in [README_SECURITYCONTEXT.md](README_SECURITYCONTEXT.md).
4242
* Stable APIs, use a stable API if available (supported: Deployments, StatefulSets, DaemonSet)
4343

4444
## Example output

README_CHECKS.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
| pod-networkpolicy | Pod | Makes sure that all Pods are targeted by a NetworkPolicy | default |
1515
| networkpolicy-targets-pod | NetworkPolicy | Makes sure that all NetworkPolicies targets at least one Pod | default |
1616
| pod-probes | Pod | Makes sure that all Pods have safe probe configurations | default |
17-
| container-security-context | Pod | Makes sure that all pods have good securityContexts configured | default |
17+
| container-security-context | Pod | Makes sure that all pods have good securityContexts configured (*deprecated*, see [README_SECURITYCONTEXT.md](README_SECURITYCONTEXT.md) | default |
18+
| container-security-context-user-group-id | Pod | Makes sure that user and group ID are set and > 10000 | optional |
19+
| container-security-context-privileged | Pod | Makes sure that no Containers run in privileged mode | optional |
20+
| container-security-context-readonlyrootfilesystem | Pod | Makes sure that all Containers have a read only root filesystem | optional |
1821
| container-seccomp-profile | Pod | Makes sure that all pods have at a seccomp policy configured. | optional |
1922
| service-targets-pod | Service | Makes sure that all Services targets a Pod | default |
2023
| service-type | Service | Makes sure that the Service type is not NodePort | default |

README_SECURITYCONTEXT.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Security Context
2+
3+
The default `container-security-context` check checks the `SecurityContext`
4+
for
5+
6+
* Containers with writeable root filesystems
7+
* Containers that run with user ID or group ID < 10000
8+
* Privileged containers
9+
10+
If you do not want all of this checks you can disable `container-security-context`
11+
and enable one or more of the following optional checks:
12+
13+
* `container-security-context-user-group-id`
14+
* `container-security-context-privileged`
15+
* `container-security-context-readonlyrootfilesystem`
16+
17+
## Release Timeline
18+
19+
* v1.10: Introduce the three new checks.
20+
* v1.11: Make `container-security-context` optional, and make the three new checks run by default.
21+
* v1.12: Remove `container-security-context`.

score/security/security.go

+100
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,110 @@ import (
1010

1111
func Register(allChecks *checks.Checks) {
1212
allChecks.RegisterPodCheck("Container Security Context", `Makes sure that all pods have good securityContexts configured`, containerSecurityContext)
13+
14+
allChecks.RegisterOptionalPodCheck("Container Security Context User Group ID", `Makes sure that all pods have a security context with valid UID and GID set `, containerSecurityContextUserGroupID)
15+
allChecks.RegisterOptionalPodCheck("Container Security Context Privileged", "Makes sure that all pods have a unprivileged security context set", containerSecurityContextPrivileged)
16+
allChecks.RegisterOptionalPodCheck("Container Security Context ReadOnlyRootFilesystem", "Makes sure that all pods have a security context with read only filesystem set", containerSecurityContextReadOnlyRootFilesystem)
17+
1318
allChecks.RegisterOptionalPodCheck("Container Seccomp Profile", `Makes sure that all pods have at a seccomp policy configured.`, podSeccompProfile)
1419
}
1520

21+
// containerSecurityContextReadOnlyRootFilesystem checks for pods using writeable root filesystems
22+
func containerSecurityContextReadOnlyRootFilesystem(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
23+
allContainers := podTemplate.Spec.InitContainers
24+
allContainers = append(allContainers, podTemplate.Spec.Containers...)
25+
26+
noContextSet := false
27+
hasWritableRootFS := false
28+
29+
for _, container := range allContainers {
30+
if container.SecurityContext == nil {
31+
noContextSet = true
32+
score.AddComment(container.Name, "Container has no configured security context", "Set securityContext to run the container in a more secure context.")
33+
continue
34+
}
35+
sec := container.SecurityContext
36+
if sec.ReadOnlyRootFilesystem == nil || *sec.ReadOnlyRootFilesystem == false {
37+
hasWritableRootFS = true
38+
score.AddComment(container.Name, "The pod has a container with a writable root filesystem", "Set securityContext.readOnlyRootFilesystem to true")
39+
}
40+
}
41+
42+
if noContextSet || hasWritableRootFS {
43+
score.Grade = scorecard.GradeCritical
44+
} else {
45+
score.Grade = scorecard.GradeAllOK
46+
}
47+
48+
return
49+
}
50+
51+
// containerSecurityContextPrivileged checks for privileged containers
52+
func containerSecurityContextPrivileged(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
53+
allContainers := podTemplate.Spec.InitContainers
54+
allContainers = append(allContainers, podTemplate.Spec.Containers...)
55+
hasPrivileged := false
56+
for _, container := range allContainers {
57+
if container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {
58+
hasPrivileged = true
59+
score.AddComment(container.Name, "The container is privileged", "Set securityContext.privileged to false. Privileged containers can access all devices on the host, and grants almost the same access as non-containerized processes on the host.")
60+
}
61+
}
62+
if hasPrivileged {
63+
score.Grade = scorecard.GradeCritical
64+
} else {
65+
score.Grade = scorecard.GradeAllOK
66+
}
67+
return
68+
}
69+
70+
// containerSecurityContextUserGroupID checks that the user and group are valid ( > 10000) in the security context
71+
func containerSecurityContextUserGroupID(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
72+
allContainers := podTemplate.Spec.InitContainers
73+
allContainers = append(allContainers, podTemplate.Spec.Containers...)
74+
podSecurityContext := podTemplate.Spec.SecurityContext
75+
noContextSet := false
76+
hasLowUserID := false
77+
hasLowGroupID := false
78+
for _, container := range allContainers {
79+
if container.SecurityContext == nil && podSecurityContext == nil {
80+
noContextSet = true
81+
score.AddComment(container.Name, "Container has no configured security context", "Set securityContext to run the container in a more secure context.")
82+
continue
83+
}
84+
sec := container.SecurityContext
85+
if sec == nil {
86+
sec = &corev1.SecurityContext{}
87+
}
88+
// Forward values from PodSecurityContext to the (container level) SecurityContext if not set
89+
if podSecurityContext != nil {
90+
if sec.RunAsGroup == nil {
91+
sec.RunAsGroup = podSecurityContext.RunAsGroup
92+
}
93+
if sec.RunAsUser == nil {
94+
sec.RunAsUser = podSecurityContext.RunAsUser
95+
}
96+
}
97+
if sec.RunAsUser == nil || *sec.RunAsUser < 10000 {
98+
hasLowUserID = true
99+
score.AddComment(container.Name, "The container is running with a low user ID", "A userid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsUser to a value > 10000")
100+
}
101+
102+
if sec.RunAsGroup == nil || *sec.RunAsGroup < 10000 {
103+
hasLowGroupID = true
104+
score.AddComment(container.Name, "The container running with a low group ID", "A groupid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsGroup to a value > 10000")
105+
}
106+
}
107+
if noContextSet || hasLowUserID || hasLowGroupID {
108+
score.Grade = scorecard.GradeCritical
109+
} else {
110+
score.Grade = scorecard.GradeAllOK
111+
}
112+
return
113+
}
114+
16115
// containerSecurityContext checks that the recommended securityPolicy options are set
116+
// Deprecated: will be replaced with "Container Security Context User Group ID", "Container Security Context Privileged" and "Container Security Context ReadOnlyRootFilesystem" in future versions
17117
func containerSecurityContext(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
18118
allContainers := podTemplate.Spec.InitContainers
19119
allContainers = append(allContainers, podTemplate.Spec.Containers...)

score/security_test.go

+125-2
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,9 @@ func TestPodSecurityContext(test *testing.T) {
217217
}
218218
}
219219

220-
func TestContainerSecurityContextPrivilegied(t *testing.T) {
220+
func TestContainerSecurityContextPrivileged(t *testing.T) {
221221
t.Parallel()
222-
testExpectedScore(t, "pod-security-context-privilegied.yaml", "Container Security Context", scorecard.GradeCritical)
222+
testExpectedScore(t, "pod-security-context-privileged.yaml", "Container Security Context", scorecard.GradeCritical)
223223
}
224224

225225
func TestContainerSecurityContextLowUser(t *testing.T) {
@@ -266,3 +266,126 @@ func TestContainerSeccompAllGood(t *testing.T) {
266266
EnabledOptionalTests: structMap,
267267
}, "Container Seccomp Profile", scorecard.GradeAllOK)
268268
}
269+
270+
func TestContainerSecurityContextUserGroupIDAllGood(t *testing.T) {
271+
t.Parallel()
272+
structMap := make(map[string]struct{})
273+
structMap["container-security-context-user-group-id"] = struct{}{}
274+
c := testExpectedScoreWithConfig(t, config.Configuration{
275+
AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")},
276+
EnabledOptionalTests: structMap,
277+
}, "Container Security Context User Group ID", scorecard.GradeAllOK)
278+
assert.Empty(t, c)
279+
}
280+
281+
func TestContainerSecurityContextUserGroupIDLowGroup(t *testing.T) {
282+
t.Parallel()
283+
optionalChecks := make(map[string]struct{})
284+
optionalChecks["container-security-context-user-group-id"] = struct{}{}
285+
comments := testExpectedScoreWithConfig(t, config.Configuration{
286+
AllFiles: []ks.NamedReader{testFile("pod-security-context-low-group-id.yaml")},
287+
EnabledOptionalTests: optionalChecks,
288+
}, "Container Security Context User Group ID", scorecard.GradeCritical)
289+
assert.Contains(t, comments, scorecard.TestScoreComment{
290+
Path: "foobar",
291+
Summary: "The container running with a low group ID",
292+
Description: "A groupid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsGroup to a value > 10000",
293+
})
294+
}
295+
296+
func TestContainerSecurityContextUserGroupIDLowUser(t *testing.T) {
297+
t.Parallel()
298+
optionalChecks := make(map[string]struct{})
299+
optionalChecks["container-security-context-user-group-id"] = struct{}{}
300+
comments := testExpectedScoreWithConfig(t, config.Configuration{
301+
AllFiles: []ks.NamedReader{testFile("pod-security-context-low-user-id.yaml")},
302+
EnabledOptionalTests: optionalChecks,
303+
}, "Container Security Context User Group ID", scorecard.GradeCritical)
304+
assert.Contains(t, comments, scorecard.TestScoreComment{
305+
Path: "foobar",
306+
Summary: "The container is running with a low user ID",
307+
Description: "A userid above 10 000 is recommended to avoid conflicts with the host. Set securityContext.runAsUser to a value > 10000",
308+
})
309+
}
310+
311+
func TestContainerSecurityContextUserGroupIDNoSecurityContext(t *testing.T) {
312+
t.Parallel()
313+
optionalChecks := make(map[string]struct{})
314+
optionalChecks["container-security-context-user-group-id"] = struct{}{}
315+
comments := testExpectedScoreWithConfig(t, config.Configuration{
316+
AllFiles: []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")},
317+
EnabledOptionalTests: optionalChecks,
318+
}, "Container Security Context User Group ID", scorecard.GradeCritical)
319+
assert.Contains(t, comments, scorecard.TestScoreComment{
320+
Path: "foobar",
321+
Summary: "Container has no configured security context",
322+
Description: "Set securityContext to run the container in a more secure context.",
323+
})
324+
}
325+
326+
func TestContainerSecurityContextPrivilegedAllGood(t *testing.T) {
327+
t.Parallel()
328+
structMap := make(map[string]struct{})
329+
structMap["container-security-context-privileged"] = struct{}{}
330+
c := testExpectedScoreWithConfig(t, config.Configuration{
331+
AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")},
332+
EnabledOptionalTests: structMap,
333+
}, "Container Security Context Privileged", scorecard.GradeAllOK)
334+
assert.Empty(t, c)
335+
}
336+
337+
func TestContainerSecurityContextPrivilegedPrivileged(t *testing.T) {
338+
t.Parallel()
339+
optionalChecks := make(map[string]struct{})
340+
optionalChecks["container-security-context-privileged"] = struct{}{}
341+
comments := testExpectedScoreWithConfig(t, config.Configuration{
342+
AllFiles: []ks.NamedReader{testFile("pod-security-context-privileged.yaml")},
343+
EnabledOptionalTests: optionalChecks,
344+
}, "Container Security Context Privileged", scorecard.GradeCritical)
345+
assert.Contains(t, comments, scorecard.TestScoreComment{
346+
Path: "foobar",
347+
Summary: "The container is privileged",
348+
Description: "Set securityContext.privileged to false. Privileged containers can access all devices on the host, and grants almost the same access as non-containerized processes on the host.",
349+
})
350+
}
351+
352+
func TestContainerSecurityContextReadOnlyRootFilesystemAllGood(t *testing.T) {
353+
t.Parallel()
354+
structMap := make(map[string]struct{})
355+
structMap["container-security-context-readonlyrootfilesystem"] = struct{}{}
356+
c := testExpectedScoreWithConfig(t, config.Configuration{
357+
AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")},
358+
EnabledOptionalTests: structMap,
359+
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeAllOK)
360+
assert.Empty(t, c)
361+
}
362+
363+
func TestContainerSecurityContextReadOnlyRootFilesystemWriteable(t *testing.T) {
364+
t.Parallel()
365+
optionalChecks := make(map[string]struct{})
366+
optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{}
367+
comments := testExpectedScoreWithConfig(t, config.Configuration{
368+
AllFiles: []ks.NamedReader{testFile("pod-security-context-writeablerootfilesystem.yaml")},
369+
EnabledOptionalTests: optionalChecks,
370+
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical)
371+
assert.Contains(t, comments, scorecard.TestScoreComment{
372+
Path: "foobar",
373+
Summary: "The pod has a container with a writable root filesystem",
374+
Description: "Set securityContext.readOnlyRootFilesystem to true",
375+
})
376+
}
377+
378+
func TestContainerSecurityContextReadOnlyRootFilesystemNoSecurityContext(t *testing.T) {
379+
t.Parallel()
380+
optionalChecks := make(map[string]struct{})
381+
optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{}
382+
comments := testExpectedScoreWithConfig(t, config.Configuration{
383+
AllFiles: []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")},
384+
EnabledOptionalTests: optionalChecks,
385+
}, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical)
386+
assert.Contains(t, comments, scorecard.TestScoreComment{
387+
Path: "foobar",
388+
Summary: "Container has no configured security context",
389+
Description: "Set securityContext to run the container in a more secure context.",
390+
})
391+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: pod-test-1
5+
spec:
6+
containers:
7+
- name: foobar
8+
image: foo/bar:latest
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: pod-test-1
5+
spec:
6+
containers:
7+
- name: foobar
8+
image: foo/bar:latest
9+
securityContext:
10+
privileged: True

0 commit comments

Comments
 (0)