From 85aa2e1828a6d159406fa3051ad653118a553c0f Mon Sep 17 00:00:00 2001 From: ariyonaty Date: Mon, 8 Jul 2024 13:41:19 -0700 Subject: [PATCH 1/2] expand run-as-non-root template with runAsGroup check --- docs/generated/checks.md | 4 +-- docs/generated/templates.md | 4 +-- e2etests/bats-tests.sh | 10 ++++-- pkg/builtinchecks/yamls/run-as-non-root.yaml | 2 +- pkg/templates/runasnonroot/template.go | 33 +++++++++++++++++--- tests/checks/run-as-non-root.yml | 14 ++++++--- 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/docs/generated/checks.md b/docs/generated/checks.md index e5a966404..2641acd0a 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -528,9 +528,9 @@ key: owner **Description**: Indicates when containers are not set to runAsNonRoot. -**Remediation**: Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details. +**Remediation**: Set runAsUser and runAsGroup to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details. -**Template**: [run-as-non-root](templates.md#run-as-non-root-user) +**Template**: [run-as-non-root](templates.md#run-as-non-root) ## scc-deny-privileged-container **Enabled by default**: No diff --git a/docs/generated/templates.md b/docs/generated/templates.md index 5bb23a908..0f6f819b0 100644 --- a/docs/generated/templates.md +++ b/docs/generated/templates.md @@ -703,11 +703,11 @@ KubeLinter supports the following templates: type: string ``` -## Run as non-root user +## Run as non-root **Key**: `run-as-non-root` -**Description**: Flag containers set to run as a root user +**Description**: Flag containers set to run as a root user or group **Supported Objects**: DeploymentLike diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index e4ef523d4..58aae3105 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -781,11 +781,15 @@ get_value_from() { message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message') + message3=$(get_value_from "${lines[0]}" '.Reports[2].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[2].Diagnostic.Message') + message4=$(get_value_from "${lines[0]}" '.Reports[3].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[3].Diagnostic.Message') count=$(get_value_from "${lines[0]}" '.Reports | length') - [[ "${message1}" == "Deployment: container \"app\" is not set to runAsNonRoot" ]] - [[ "${message2}" == "DeploymentConfig: container \"app2\" is not set to runAsNonRoot" ]] - [[ "${count}" == "2" ]] + [[ "${message1}" == "Deployment: container \"app\" has runAsGroup set to 0" ]] + [[ "${message2}" == "Deployment: container \"app\" is not set to runAsNonRoot" ]] + [[ "${message3}" == "DeploymentConfig: container \"app2\" has runAsGroup set to 0" ]] + [[ "${message4}" == "DeploymentConfig: container \"app2\" is not set to runAsNonRoot" ]] + [[ "${count}" == "4" ]] } @test "scc-deny-privileged-container" { diff --git a/pkg/builtinchecks/yamls/run-as-non-root.yaml b/pkg/builtinchecks/yamls/run-as-non-root.yaml index 281e7a22a..d1a04ea7e 100644 --- a/pkg/builtinchecks/yamls/run-as-non-root.yaml +++ b/pkg/builtinchecks/yamls/run-as-non-root.yaml @@ -1,7 +1,7 @@ name: "run-as-non-root" description: "Indicates when containers are not set to runAsNonRoot." remediation: >- - Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. + Set runAsUser and runAsGroup to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details. scope: objectKinds: diff --git a/pkg/templates/runasnonroot/template.go b/pkg/templates/runasnonroot/template.go index 0d67058be..8b69ed767 100644 --- a/pkg/templates/runasnonroot/template.go +++ b/pkg/templates/runasnonroot/template.go @@ -34,11 +34,21 @@ func effectiveRunAsUser(podSC *v1.PodSecurityContext, containerSC *v1.SecurityCo return nil } +func effectiveRunAsGroup(podSC *v1.PodSecurityContext, containerSC *v1.SecurityContext) *int64 { + if containerSC != nil && containerSC.RunAsGroup != nil { + return containerSC.RunAsGroup + } + if podSC != nil { + return podSC.RunAsGroup + } + return nil +} + func init() { templates.Register(check.Template{ - HumanName: "Run as non-root user", + HumanName: "Run as non-root", Key: "run-as-non-root", - Description: "Flag containers set to run as a root user", + Description: "Flag containers set to run as a root user or group", SupportedObjectKinds: config.ObjectKindsDesc{ ObjectKinds: []string{objectkinds.DeploymentLike}, }, @@ -53,10 +63,25 @@ func init() { var results []diagnostic.Diagnostic for _, container := range podSpec.AllContainers() { runAsUser := effectiveRunAsUser(podSpec.SecurityContext, container.SecurityContext) - // runAsUser explicitly set to non-root. All good. - if runAsUser != nil && *runAsUser > 0 { + runAsGroup := effectiveRunAsGroup(podSpec.SecurityContext, container.SecurityContext) + // runAsUser and runAsGroup explicitly set to non-root. All good. + if (runAsUser != nil && *runAsUser > 0) && (runAsGroup != nil && *runAsGroup > 0) { continue } + + // runAsGroup set to 0 + if runAsGroup != nil && *runAsGroup == 0 { + results = append(results, diagnostic.Diagnostic{ + Message: fmt.Sprintf("container %q has runAsGroup set to %d", container.Name, *runAsGroup), + }) + } + // runAsGroup is not set. + if runAsGroup == nil { + results = append(results, diagnostic.Diagnostic{ + Message: fmt.Sprintf("container %q does not have runAsGroup set", container.Name), + }) + } + runAsNonRoot := effectiveRunAsNonRoot(podSpec.SecurityContext, container.SecurityContext) if runAsNonRoot { // runAsNonRoot set, but runAsUser set to 0. This will result in a runtime failure. diff --git a/tests/checks/run-as-non-root.yml b/tests/checks/run-as-non-root.yml index 46ec9a888..0bb11c095 100644 --- a/tests/checks/run-as-non-root.yml +++ b/tests/checks/run-as-non-root.yml @@ -11,8 +11,9 @@ spec: spec: containers: - name: app - runAsUser: 1001 securityContext: + runAsUser: 1001 + runAsGroup: 1001 runAsNonRoot: true --- apiVersion: apps.openshift.io/v1 @@ -26,8 +27,9 @@ spec: spec: containers: - name: app2 - runAsUser: 1001 securityContext: + runAsUser: 1001 + runAsGroup: 1001 runAsNonRoot: true --- apiVersion: apps/v1 @@ -42,7 +44,9 @@ spec: spec: containers: - name: app - runAsUser: 0 + securityContext: + runAsUser: 0 + runAsGroup: 0 --- apiVersion: apps.openshift.io/v1 kind: DeploymentConfig @@ -55,4 +59,6 @@ spec: spec: containers: - name: app2 - runAsUser: 0 \ No newline at end of file + securityContext: + runAsUser: 0 + runAsGroup: 0 From 6331670ffcb8958d595e8eddc36211b9c4bf6826 Mon Sep 17 00:00:00 2001 From: ariyonaty Date: Fri, 9 Aug 2024 16:56:13 -0700 Subject: [PATCH 2/2] Add unit tests for run-as-non-root check --- pkg/templates/runasnonroot/template.go | 10 +- pkg/templates/runasnonroot/template_test.go | 244 ++++++++++++++++++++ 2 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 pkg/templates/runasnonroot/template_test.go diff --git a/pkg/templates/runasnonroot/template.go b/pkg/templates/runasnonroot/template.go index 8b69ed767..cdb1396b4 100644 --- a/pkg/templates/runasnonroot/template.go +++ b/pkg/templates/runasnonroot/template.go @@ -14,6 +14,8 @@ import ( v1 "k8s.io/api/core/v1" ) +const templateKey = "run-as-non-root" + func effectiveRunAsNonRoot(podSC *v1.PodSecurityContext, containerSC *v1.SecurityContext) bool { if containerSC != nil && containerSC.RunAsNonRoot != nil { return *containerSC.RunAsNonRoot @@ -44,10 +46,14 @@ func effectiveRunAsGroup(podSC *v1.PodSecurityContext, containerSC *v1.SecurityC return nil } +func isNonZero(number *int64) bool { + return number != nil && *number > 0 +} + func init() { templates.Register(check.Template{ HumanName: "Run as non-root", - Key: "run-as-non-root", + Key: templateKey, Description: "Flag containers set to run as a root user or group", SupportedObjectKinds: config.ObjectKindsDesc{ ObjectKinds: []string{objectkinds.DeploymentLike}, @@ -65,7 +71,7 @@ func init() { runAsUser := effectiveRunAsUser(podSpec.SecurityContext, container.SecurityContext) runAsGroup := effectiveRunAsGroup(podSpec.SecurityContext, container.SecurityContext) // runAsUser and runAsGroup explicitly set to non-root. All good. - if (runAsUser != nil && *runAsUser > 0) && (runAsGroup != nil && *runAsGroup > 0) { + if isNonZero(runAsUser) && isNonZero(runAsGroup) { continue } diff --git a/pkg/templates/runasnonroot/template_test.go b/pkg/templates/runasnonroot/template_test.go new file mode 100644 index 000000000..93ff8a076 --- /dev/null +++ b/pkg/templates/runasnonroot/template_test.go @@ -0,0 +1,244 @@ +package runasnonroot + +import ( + "testing" + + "golang.stackrox.io/kube-linter/internal/pointers" + v1 "k8s.io/api/core/v1" +) + +func TestEffectiveRunAsNonRoot(t *testing.T) { + tests := []struct { + name string + podSC *v1.PodSecurityContext + containerSC *v1.SecurityContext + expected bool + }{ + { + name: "both nil", + podSC: nil, + containerSC: nil, + expected: false, + }, + { + name: "podSC set", + podSC: &v1.PodSecurityContext{ + RunAsNonRoot: pointers.Bool(true), + }, + containerSC: nil, + expected: true, + }, + { + name: "containerSC set", + podSC: nil, + containerSC: &v1.SecurityContext{ + RunAsNonRoot: pointers.Bool(true), + }, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := effectiveRunAsNonRoot(test.podSC, test.containerSC) + if result != test.expected { + t.Errorf("expected %v, got %v", test.expected, result) + } + }) + } +} + +func TestEffectiveRunAsUser(t *testing.T) { + user := pointers.Int64(1000) + tests := []struct { + name string + podSC *v1.PodSecurityContext + containerSC *v1.SecurityContext + expected *int64 + }{ + { + name: "both nil", + podSC: nil, + containerSC: nil, + expected: nil, + }, + { + name: "podSC set", + podSC: &v1.PodSecurityContext{ + RunAsUser: user, + }, + containerSC: nil, + expected: user, + }, + { + name: "containerSC set", + podSC: nil, + containerSC: &v1.SecurityContext{ + RunAsUser: user, + }, + expected: user, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := effectiveRunAsUser(test.podSC, test.containerSC) + if result != test.expected { + t.Errorf("expected %v, got %v", test.expected, result) + } + }) + } +} + +func TestEffectiveRunAsGroup(t *testing.T) { + group := pointers.Int64(1000) + tests := []struct { + name string + podSC *v1.PodSecurityContext + containerSC *v1.SecurityContext + expected *int64 + }{ + { + name: "both nil", + podSC: nil, + containerSC: nil, + expected: nil, + }, + { + name: "podSC set", + podSC: &v1.PodSecurityContext{ + RunAsGroup: group, + }, + containerSC: nil, + expected: group, + }, + { + name: "containerSC set", + podSC: nil, + containerSC: &v1.SecurityContext{ + RunAsGroup: group, + }, + expected: group, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := effectiveRunAsGroup(test.podSC, test.containerSC) + if result != test.expected { + t.Errorf("expected %v, got %v", test.expected, result) + } + }) + } +} + +func TestIsNonZero(t *testing.T) { + tests := []struct { + name string + number *int64 + expected bool + }{ + { + name: "Nil case", + number: nil, + expected: false, + }, + { + name: "zero case", + number: pointers.Int64(0), + expected: false, + }, + { + name: "non-zero case", + number: pointers.Int64(1000), + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := isNonZero(test.number) + if result != test.expected { + t.Errorf("expected %v, got %v", test.expected, result) + } + }) + } +} + +// func TestRunAsNonRoot(t *testing.T) { +// suite.Run(t, new(RunAsNonRootTestSuite)) +// } +// +// type RunAsNonRootTestSuite struct { +// templates.TemplateTestSuite +// ctx *mocks.MockLintContext +// } +// +// func (s *RunAsNonRootTestSuite) SetupTest() { +// s.Init(templateKey) +// s.ctx = mocks.NewMockContext() +// } +// +// func (s *RunAsNonRootTestSuite) TestEffectiveRunAsGroup() { +// const targetName = "deployment01" +// testCases := []struct { +// name string +// podSC *v1.PodSecurityContext +// containerSC *v1.SecurityContext +// expected map[string][]diagnostic.Diagnostic +// }{ +// { +// name: "both nil", +// podSC: &v1.PodSecurityContext{ +// RunAsGroup: pointers.Int64(1000), +// }, +// containerSC: nil, +// expected: nil, +// }, +// { +// name: "both nil", +// podSC: nil, +// containerSC: nil, +// expected: nil, +// }, +// { +// name: "both nil", +// podSC: nil, +// containerSC: nil, +// expected: nil, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// s.ctx.AddMockDeployment(s.T(), targetName) +// s.Validate(s.ctx, []templates.TestCase{{ +// Diagnostics: tc.expected, +// }}) +// }) +// } +// } +// +// // [[ "${message1}" == "Deployment: container \"app\" has runAsGroup set to 0" ]] +// // [[ "${message2}" == "Deployment: container \"app\" is not set to runAsNonRoot" ]] +// // [[ "${message3}" == "DeploymentConfig: container \"app2\" has runAsGroup set to 0" ]] +// // [[ "${message4}" == "DeploymentConfig: container \"app2\" is not set to runAsNonRoot" ]] +// +// func (suite *RunAsNonRootTestSuite) TestIsNonZero() { +// tests := []struct { +// name string +// number *int64 +// expected bool +// }{ +// {"Nil number", nil, false}, +// {"Zero number", pointers.Int64(0), false}, +// {"Non-zero number", pointers.Int64(1), true}, +// } +// +// for _, test := range tests { +// suite.Run(test.name, func() { +// result := isNonZero(test.number) +// suite.Equal(test.expected, result) +// }) +// } +// }