Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand run-as-non-root template to verify runAsGroup field is nonzero. #804

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/generated/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/generated/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions e2etests/bats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
2 changes: 1 addition & 1 deletion pkg/builtinchecks/yamls/run-as-non-root.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
41 changes: 36 additions & 5 deletions pkg/templates/runasnonroot/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
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
Expand All @@ -34,11 +36,25 @@
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 isNonZero(number *int64) bool {
return number != nil && *number > 0
}

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",
HumanName: "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},
},
Expand All @@ -53,10 +69,25 @@
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)

Check warning on line 72 in pkg/templates/runasnonroot/template.go

View check run for this annotation

Codecov / codecov/patch

pkg/templates/runasnonroot/template.go#L72

Added line #L72 was not covered by tests
// runAsUser and runAsGroup explicitly set to non-root. All good.
if isNonZero(runAsUser) && isNonZero(runAsGroup) {

Check warning on line 74 in pkg/templates/runasnonroot/template.go

View check run for this annotation

Codecov / codecov/patch

pkg/templates/runasnonroot/template.go#L74

Added line #L74 was not covered by tests
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),
})

Check warning on line 82 in pkg/templates/runasnonroot/template.go

View check run for this annotation

Codecov / codecov/patch

pkg/templates/runasnonroot/template.go#L79-L82

Added lines #L79 - L82 were not covered by tests
}
// runAsGroup is not set.
if runAsGroup == nil {
results = append(results, diagnostic.Diagnostic{
Message: fmt.Sprintf("container %q does not have runAsGroup set", container.Name),
})

Check warning on line 88 in pkg/templates/runasnonroot/template.go

View check run for this annotation

Codecov / codecov/patch

pkg/templates/runasnonroot/template.go#L85-L88

Added lines #L85 - L88 were not covered by tests
}

runAsNonRoot := effectiveRunAsNonRoot(podSpec.SecurityContext, container.SecurityContext)
if runAsNonRoot {
// runAsNonRoot set, but runAsUser set to 0. This will result in a runtime failure.
Expand Down
244 changes: 244 additions & 0 deletions pkg/templates/runasnonroot/template_test.go
Original file line number Diff line number Diff line change
@@ -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)
// })
// }
// }
Loading
Loading