diff --git a/docs/generated/checks.md b/docs/generated/checks.md index 772475bc0..b95c3b5a2 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -586,9 +586,9 @@ key: owner **Enabled by default**: Yes -**Description**: Indicates when containers are not set to runAsNonRoot. +**Description**: Indicates when containers are not set to runAsNonRoot or explicitly use the root group. -**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 non-zero numbers 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) ## scc-deny-privileged-container diff --git a/docs/generated/templates.md b/docs/generated/templates.md index 9fda75d79..8d378db6f 100644 --- a/docs/generated/templates.md +++ b/docs/generated/templates.md @@ -865,7 +865,7 @@ KubeLinter supports the following templates: **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 56dbeb06a..baff55d4a 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -921,11 +921,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" ]] + [[ "${message3}" == "Deployment: container \"app3\" is set to runAsGroup 0" ]] + [[ "${message4}" == "DeploymentConfig: container \"app4\" is set to runAsGroup 0" ]] + [[ "${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..1f4b25e7f 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." +description: "Indicates when containers are not set to runAsNonRoot or explicitly use the root group." remediation: >- - Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. + Set runAsUser and runAsGroup to non-zero numbers 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..4f08b18b4 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 @@ -34,11 +36,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", - Key: "run-as-non-root", - Description: "Flag containers set to run as a root user", + Key: templateKey, + Description: "Flag containers set to run as a root user or group", SupportedObjectKinds: config.ObjectKindsDesc{ ObjectKinds: []string{objectkinds.DeploymentLike}, }, @@ -52,6 +64,13 @@ func init() { } var results []diagnostic.Diagnostic for _, container := range podSpec.AllContainers() { + runAsGroup := effectiveRunAsGroup(podSpec.SecurityContext, container.SecurityContext) + if runAsGroup != nil && *runAsGroup == 0 { + results = append(results, diagnostic.Diagnostic{ + Message: fmt.Sprintf("container %q is set to runAsGroup 0", container.Name), + }) + } + runAsUser := effectiveRunAsUser(podSpec.SecurityContext, container.SecurityContext) // runAsUser explicitly set to non-root. All good. if runAsUser != nil && *runAsUser > 0 { diff --git a/pkg/templates/runasnonroot/template_test.go b/pkg/templates/runasnonroot/template_test.go new file mode 100644 index 000000000..d4637ac71 --- /dev/null +++ b/pkg/templates/runasnonroot/template_test.go @@ -0,0 +1,122 @@ +package runasnonroot + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "golang.stackrox.io/kube-linter/pkg/diagnostic" + "golang.stackrox.io/kube-linter/pkg/lintcontext/mocks" + "golang.stackrox.io/kube-linter/pkg/templates" + "golang.stackrox.io/kube-linter/pkg/templates/runasnonroot/internal/params" + v1 "k8s.io/api/core/v1" +) + +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) TestContainerRunAsGroupZero() { + const deploymentName = "container-group-zero" + + s.ctx.AddMockDeployment(s.T(), deploymentName) + s.ctx.AddContainerToDeployment(s.T(), deploymentName, v1.Container{ + Name: "app", + SecurityContext: &v1.SecurityContext{ + RunAsUser: int64Ptr(1000), + RunAsGroup: int64Ptr(0), + }, + }) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + deploymentName: { + {Message: `container "app" is set to runAsGroup 0`}, + }, + }, + }, + }) +} + +func (s *RunAsNonRootTestSuite) TestPodRunAsGroupZero() { + const deploymentName = "pod-group-zero" + + s.ctx.AddMockDeployment(s.T(), deploymentName) + s.ctx.AddSecurityContextToDeployment(s.T(), deploymentName, &v1.PodSecurityContext{ + RunAsGroup: int64Ptr(0), + }) + s.ctx.AddContainerToDeployment(s.T(), deploymentName, v1.Container{ + Name: "app", + SecurityContext: &v1.SecurityContext{ + RunAsUser: int64Ptr(1000), + }, + }) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + deploymentName: { + {Message: `container "app" is set to runAsGroup 0`}, + }, + }, + }, + }) +} + +func (s *RunAsNonRootTestSuite) TestContainerRunAsGroupOverridesPodRunAsGroup() { + const deploymentName = "container-group-overrides-pod" + + s.ctx.AddMockDeployment(s.T(), deploymentName) + s.ctx.AddSecurityContextToDeployment(s.T(), deploymentName, &v1.PodSecurityContext{ + RunAsGroup: int64Ptr(0), + }) + s.ctx.AddContainerToDeployment(s.T(), deploymentName, v1.Container{ + Name: "app", + SecurityContext: &v1.SecurityContext{ + RunAsUser: int64Ptr(1000), + RunAsGroup: int64Ptr(1000), + }, + }) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: nil, + }, + }) +} + +func (s *RunAsNonRootTestSuite) TestMissingRunAsGroupAllowedWhenRunAsUserNonRoot() { + const deploymentName = "non-root-user-no-group" + + s.ctx.AddMockDeployment(s.T(), deploymentName) + s.ctx.AddContainerToDeployment(s.T(), deploymentName, v1.Container{ + Name: "app", + SecurityContext: &v1.SecurityContext{ + RunAsUser: int64Ptr(1000), + }, + }) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: nil, + }, + }) +} + +func int64Ptr(v int64) *int64 { + return &v +} diff --git a/tests/checks/run-as-non-root.yml b/tests/checks/run-as-non-root.yml index 46ec9a888..db99d39b2 100644 --- a/tests/checks/run-as-non-root.yml +++ b/tests/checks/run-as-non-root.yml @@ -55,4 +55,35 @@ spec: spec: containers: - name: app2 - runAsUser: 0 \ No newline at end of file + runAsUser: 0 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: group-zero +spec: + selector: + matchLabels: + app.kubernetes.io/name: app3 + template: + spec: + containers: + - name: app3 + securityContext: + runAsUser: 1001 + runAsGroup: 0 +--- +apiVersion: apps.openshift.io/v1 +kind: DeploymentConfig +metadata: + name: group-zero +spec: + selector: + app.kubernetes.io/name: app4 + template: + spec: + containers: + - name: app4 + securityContext: + runAsUser: 1001 + runAsGroup: 0