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 } 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