diff --git a/.golangci.yml b/.golangci.yml
index eb8c96ece..de30f05e3 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -55,6 +55,7 @@ linters-settings:
- importShadow
- emptyStringTest
- hugeParam
+ - rangeValCopy
nolintlint:
allow-leading-space: false # require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
diff --git a/docs/generated/checks.md b/docs/generated/checks.md
index 47cc1057f..39d949f86 100644
--- a/docs/generated/checks.md
+++ b/docs/generated/checks.md
@@ -2,6 +2,8 @@ The following table enumerates built-in checks:
| Name | Enabled by default | Description | Template | Parameters |
| ---- | ------------------ | ----------- | -------- | ---------- |
- | env-var-secret | No | Alert on objects using a secret in an environment variable | env-var |- `name`: `.*secret.*`
|
+ | env-var-secret | Yes | Alert on objects using a secret in an environment variable | env-var |- `name`: `.*secret.*`
|
+ | no-read-only-root-fs | Yes | Alert on containers not running with a read-only root filesystem | read-only-root-fs | none |
| privileged-container | Yes | Alert on deployments with containers running in privileged mode | privileged | none |
| required-label-owner | No | Alert on objects without the 'owner' label | required-label |- `key`: `owner`
|
+ | run-as-non-root | Yes | Alert on containers not set to runAsNonRoot | run-as-non-root | none |
diff --git a/docs/generated/templates.md b/docs/generated/templates.md
index 8d281f6db..627a4d7c8 100644
--- a/docs/generated/templates.md
+++ b/docs/generated/templates.md
@@ -4,4 +4,6 @@ The following table enumerates supported check templates:
| ---- | ----------- | ----------------- | ---------- |
| env-var | Flag environment variables that match the provided patterns | DeploymentLike |- `name` (required): A regex for the env var name
- `value`: A regex for the env var value
|
| privileged | Flag privileged containers | DeploymentLike | none |
+ | read-only-root-fs | Flag containers without read-only root file systems | DeploymentLike | none |
| required-label | Flag objects not carrying at least one label matching the provided patterns | Any |- `key` (required): A regex for the key of the required label
- `value`: A regex for the value of the required label
|
+ | run-as-non-root | Flag containers set to run as a root user | DeploymentLike | none |
diff --git a/internal/builtinchecks/yamls/read-only-root-fs.yaml b/internal/builtinchecks/yamls/read-only-root-fs.yaml
new file mode 100644
index 000000000..ec74a7a8d
--- /dev/null
+++ b/internal/builtinchecks/yamls/read-only-root-fs.yaml
@@ -0,0 +1,6 @@
+name: "no-read-only-root-fs"
+description: "Alert on containers not running with a read-only root filesystem"
+scope:
+ objectKinds:
+ - DeploymentLike
+template: "read-only-root-fs"
diff --git a/internal/builtinchecks/yamls/run-as-non-root.yaml b/internal/builtinchecks/yamls/run-as-non-root.yaml
new file mode 100644
index 000000000..2802f04dc
--- /dev/null
+++ b/internal/builtinchecks/yamls/run-as-non-root.yaml
@@ -0,0 +1,6 @@
+name: "run-as-non-root"
+description: "Alert on containers not set to runAsNonRoot"
+scope:
+ objectKinds:
+ - DeploymentLike
+template: "run-as-non-root"
diff --git a/internal/defaultchecks/default_checks.go b/internal/defaultchecks/default_checks.go
index 716f3fcb0..53ec130e9 100644
--- a/internal/defaultchecks/default_checks.go
+++ b/internal/defaultchecks/default_checks.go
@@ -8,5 +8,8 @@ var (
// List is the list of built-in checks that are enabled by default.
List = set.NewFrozenStringSet(
"privileged-container",
+ "env-var-secret",
+ "no-read-only-root-fs",
+ "run-as-non-root",
)
)
diff --git a/internal/defaultchecks/default_test.go b/internal/defaultchecks/default_test.go
new file mode 100644
index 000000000..cf388eb54
--- /dev/null
+++ b/internal/defaultchecks/default_test.go
@@ -0,0 +1,22 @@
+package defaultchecks
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.stackrox.io/kube-linter/internal/builtinchecks"
+ "golang.stackrox.io/kube-linter/internal/set"
+)
+
+func TestListReferencesOnlyValidChecks(t *testing.T) {
+ allChecks, err := builtinchecks.List()
+ require.NoError(t, err)
+ allCheckNames := set.NewStringSet()
+ for _, check := range allChecks {
+ allCheckNames.Add(check.Name)
+ }
+ for _, defaultCheck := range List.AsSlice() {
+ assert.True(t, allCheckNames.Contains(defaultCheck), "default check %s invalid", defaultCheck)
+ }
+}
diff --git a/internal/templates/all/all.go b/internal/templates/all/all.go
index 5ff875bf3..bb2b978bc 100644
--- a/internal/templates/all/all.go
+++ b/internal/templates/all/all.go
@@ -4,5 +4,7 @@ import (
// Import all check templates.
_ "golang.stackrox.io/kube-linter/internal/templates/envvar"
_ "golang.stackrox.io/kube-linter/internal/templates/privileged"
+ _ "golang.stackrox.io/kube-linter/internal/templates/readonlyrootfs"
_ "golang.stackrox.io/kube-linter/internal/templates/requiredlabel"
+ _ "golang.stackrox.io/kube-linter/internal/templates/runasnonroot"
)
diff --git a/internal/templates/readonlyrootfs/template.go b/internal/templates/readonlyrootfs/template.go
new file mode 100644
index 000000000..370cbc03a
--- /dev/null
+++ b/internal/templates/readonlyrootfs/template.go
@@ -0,0 +1,39 @@
+package readonlyrootfs
+
+import (
+ "fmt"
+
+ "golang.stackrox.io/kube-linter/internal/check"
+ "golang.stackrox.io/kube-linter/internal/diagnostic"
+ "golang.stackrox.io/kube-linter/internal/extract"
+ "golang.stackrox.io/kube-linter/internal/lintcontext"
+ "golang.stackrox.io/kube-linter/internal/objectkinds"
+ "golang.stackrox.io/kube-linter/internal/templates"
+)
+
+func init() {
+ templates.Register(check.Template{
+ Name: "read-only-root-fs",
+ Description: "Flag containers without read-only root file systems",
+ SupportedObjectKinds: check.ObjectKindsDesc{
+ ObjectKinds: []string{objectkinds.DeploymentLike},
+ },
+ Parameters: nil,
+ Instantiate: func(_ map[string]string) (check.Func, error) {
+ return func(_ *lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
+ podSpec, found := extract.PodSpec(object.K8sObject)
+ if !found {
+ return nil
+ }
+ var results []diagnostic.Diagnostic
+ for _, container := range podSpec.Containers {
+ sc := container.SecurityContext
+ if sc == nil || sc.ReadOnlyRootFilesystem == nil || !*sc.ReadOnlyRootFilesystem {
+ results = append(results, diagnostic.Diagnostic{Message: fmt.Sprintf("container %q does not have a read-only root file system", container.Name)})
+ }
+ }
+ return results
+ }, nil
+ },
+ })
+}
diff --git a/internal/templates/runasnonroot/template.go b/internal/templates/runasnonroot/template.go
new file mode 100644
index 000000000..0c13e8ddb
--- /dev/null
+++ b/internal/templates/runasnonroot/template.go
@@ -0,0 +1,72 @@
+package runasnonroot
+
+import (
+ "fmt"
+
+ "golang.stackrox.io/kube-linter/internal/check"
+ "golang.stackrox.io/kube-linter/internal/diagnostic"
+ "golang.stackrox.io/kube-linter/internal/extract"
+ "golang.stackrox.io/kube-linter/internal/lintcontext"
+ "golang.stackrox.io/kube-linter/internal/objectkinds"
+ "golang.stackrox.io/kube-linter/internal/templates"
+ v1 "k8s.io/api/core/v1"
+)
+
+func effectiveRunAsNonRoot(podSC *v1.PodSecurityContext, containerSC *v1.SecurityContext) bool {
+ if containerSC != nil && containerSC.RunAsNonRoot != nil {
+ return *containerSC.RunAsNonRoot
+ }
+ if podSC != nil && podSC.RunAsNonRoot != nil {
+ return *podSC.RunAsNonRoot
+ }
+ return false
+}
+
+func effectiveRunAsUser(podSC *v1.PodSecurityContext, containerSC *v1.SecurityContext) *int64 {
+ if containerSC != nil && containerSC.RunAsUser != nil {
+ return containerSC.RunAsUser
+ }
+ if podSC != nil {
+ return podSC.RunAsUser
+ }
+ return nil
+}
+
+func init() {
+ templates.Register(check.Template{
+ Name: "run-as-non-root",
+ Description: "Flag containers set to run as a root user",
+ SupportedObjectKinds: check.ObjectKindsDesc{
+ ObjectKinds: []string{objectkinds.DeploymentLike},
+ },
+ Parameters: nil,
+ Instantiate: func(_ map[string]string) (check.Func, error) {
+ return func(_ *lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
+ podSpec, found := extract.PodSpec(object.K8sObject)
+ if !found {
+ return nil
+ }
+ var results []diagnostic.Diagnostic
+ for _, container := range podSpec.Containers {
+ runAsUser := effectiveRunAsUser(podSpec.SecurityContext, container.SecurityContext)
+ // runAsUser explicitly set to non-root. All good.
+ if runAsUser != nil && *runAsUser > 0 {
+ continue
+ }
+ runAsNonRoot := effectiveRunAsNonRoot(podSpec.SecurityContext, container.SecurityContext)
+ if runAsNonRoot {
+ // runAsNonRoot set, but runAsUser set to 0. This will result in a runtime failure.
+ if runAsUser != nil && *runAsUser == 0 {
+ results = append(results, diagnostic.Diagnostic{
+ Message: fmt.Sprintf("container %q is set to runAsNonRoot, but runAsUser set to %d", container.Name, *runAsUser),
+ })
+ }
+ continue
+ }
+ results = append(results, diagnostic.Diagnostic{Message: fmt.Sprintf("container %q is not set to runAsNonRoot", container.Name)})
+ }
+ return results
+ }, nil
+ },
+ })
+}