├── docs ├── .nojekyll ├── CNAME ├── _navbar.md ├── _sidebar.md └── _coverpage.md ├── image ├── bin │ └── .gitignore ├── Dockerfile └── Dockerfile_alpine ├── .github ├── CODEOWNERS ├── workflows │ └── auto-merge.yaml ├── release.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── dependabot.yaml ├── e2etests ├── testdata │ ├── all-built-in-config.yaml │ ├── schema-validation-config.yaml │ ├── statefulset-volumeclaimtemplate-annotation-config.yaml │ ├── kubeconform-config.yaml │ └── forbidden-annotation-config.yaml ├── empty.go ├── bats-support-clone.bash └── check-bats-tests.sh ├── pkg ├── templates │ ├── gen.go │ ├── startupport │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── duplicatenvvar │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── hostipc │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── hostpid │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── livenessport │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── namespace │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── privileged │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── readinessport │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── readsecret │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── targetport │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglinghpa │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglingingress │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── hostnetwork │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── livenessprobe │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── nodeaffinity │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── nonisolatedpod │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── pdbminavailable │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── privilegedports │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── readinessprobe │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── readonlyrootfs │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── restartpolicy │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── runasnonroot │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── sortedkeys │ │ ├── testdata │ │ │ ├── sorted-deployment.yaml │ │ │ ├── unsorted-top-level.yaml │ │ │ ├── configmap-unsorted-data.yaml │ │ │ ├── edge-cases.yaml │ │ │ ├── numeric-keys.yaml │ │ │ ├── non-recursive.yaml │ │ │ ├── unsorted-nested.yaml │ │ │ ├── mixed-sorting.yaml │ │ │ ├── reused-labels-sorted.yaml │ │ │ ├── reused-labels-unsorted.yaml │ │ │ ├── configmap-merge-unsorted.yaml │ │ │ ├── complex-service.yaml │ │ │ ├── multi-container.yaml │ │ │ ├── deeply-nested-unsorted.yaml │ │ │ ├── complex-pod-spec.yaml │ │ │ ├── all-sorted-complex.yaml │ │ │ └── container-with-merge.yaml │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── unsafeprocmount │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── wildcardinrules │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── mismatchingselector │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── pdbmaxunavailable │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── privilegeescalation │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── writablehostmount │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── clusteradminrolebinding │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglingnetworkpolicy │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglingnetworkpolicypeer │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglingservicemonitor │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── deprecatedserviceaccount │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── nonexistentserviceaccount │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── jobttlsecondsafterfinished │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── pdbunhealthypodevictionpolicy │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── sccdenypriv │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── ports │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── replicas │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── hpareplicas │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── volumeclaimtemplates │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── serviceaccount │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── dnsconfigoptions │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── sysctl │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── requiredlabel │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── requiredannotation │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── envvar │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── kubeconform │ │ ├── testdata │ │ │ └── demonset.yaml │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── forbiddenannotation │ │ ├── internal │ │ │ └── params │ │ │ │ └── params.go │ │ └── template.go │ ├── hostmounts │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── servicetype │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── priorityclassname │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── danglingservice │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── imagepullpolicy │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── cel │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── latesttag │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── antiaffinity │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── util │ │ ├── value_in_range.go │ │ ├── json.go │ │ └── map_structure.go │ ├── disallowedgvk │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── envvarvaluefrom │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── containercapabilities │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── accesstoresources │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── memoryrequirements │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── cpurequirements │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ ├── all │ │ └── all_test.go │ ├── updateconfig │ │ └── internal │ │ │ └── params │ │ │ └── params.go │ └── registry.go ├── config │ ├── gen.go │ └── check.go ├── command │ ├── lint │ │ ├── testdata │ │ │ ├── invalid.yaml │ │ │ └── valid-pod.yaml │ │ └── command_test.go │ ├── version │ │ └── command.go │ └── root │ │ ├── command_test.go │ │ └── command.go ├── builtinchecks │ ├── yamls │ │ ├── hostipc.yaml │ │ ├── hostpid.yaml │ │ ├── hostnetwork.yaml │ │ ├── privileged.yaml │ │ ├── privilegedports.yaml │ │ ├── duplicate-env-var.yaml │ │ ├── dangling-ingress.yaml │ │ ├── dangling-service.yaml │ │ ├── read-only-root-fs.yaml │ │ ├── writable-host-mount.yaml │ │ ├── restart-policy.yaml │ │ ├── mismatching-selector.yaml │ │ ├── non-existent-service-account.yaml │ │ ├── startup-port.yaml │ │ ├── dangling-networkpolicy.yaml │ │ ├── liveness-port.yaml │ │ ├── readiness-port.yaml │ │ ├── schema-validation.yaml │ │ ├── required-label-owner.yaml │ │ ├── sorted-keys.yaml │ │ ├── servicetype.yaml │ │ ├── dangling-horizontalpodautoscaler.yaml │ │ ├── latest-tag.yaml │ │ ├── ssh-port.yaml │ │ ├── cluster-admin-role-binding.yaml │ │ ├── minimum-replicas.yaml │ │ ├── deprecated-service-account.yaml │ │ ├── no-liveness-probe.yaml │ │ ├── non-isolated-pod.yaml │ │ ├── no-readiness-probe.yaml │ │ ├── hpa-minimum-replicas.yaml │ │ ├── job-ttl-seconds-after-finished.yaml │ │ ├── dangling-servicemonitor.yaml │ │ ├── priority-class-name.yaml │ │ ├── env-var-value-from.yaml │ │ ├── run-as-non-root.yaml │ │ ├── required-annotation-email.yaml │ │ ├── default-service-account.yaml │ │ ├── host-mounts.yaml │ │ ├── no-extensions-v1beta.yaml │ │ ├── usenamespace.yaml │ │ ├── no-node-affinity.yaml │ │ ├── pdb-unhealthy-pod-eviction-policy.yaml │ │ ├── dnsconfig-options.yaml │ │ ├── env-var-secret.yaml │ │ ├── pdbs-max-unavailable.yaml │ │ ├── dangling-networkpolicypeer-podselector.yaml │ │ ├── drop-net-raw-capability.yaml │ │ ├── unset-cpu-requirements.yaml │ │ ├── no-rolling-update-strategy.yaml │ │ ├── unset-memory-requirements.yaml │ │ ├── pdbs-min-available.yaml │ │ ├── privilege-escalation.yaml │ │ ├── unsafe-proc-mount.yaml │ │ ├── read-secret-from-env-var.yaml │ │ ├── wildcard-use-in-rules.yaml │ │ ├── docker-sock.yaml │ │ ├── access-to-secrets.yaml │ │ ├── scc-deny-privileged-container.yaml │ │ ├── access-to-create-pods.yaml │ │ ├── invalid-target-ports.yaml │ │ ├── sysctls.yaml │ │ └── no-anti-affinity.yaml │ └── built_in_checks_test.go ├── k8sutil │ └── object.go ├── extract │ ├── gvk.go │ ├── metadata.go │ ├── scc_spec.go │ ├── job_spec.go │ └── sts_spec.go ├── objectkinds │ ├── job_like.go │ ├── any.go │ ├── pvc_test.go │ ├── pod.go │ ├── job.go │ ├── service.go │ ├── types.go │ ├── cronjob.go │ ├── daemonset.go │ ├── deployment.go │ ├── replicaset.go │ ├── statefulset.go │ ├── serviceaccount.go │ ├── deploymentconfig.go │ ├── replicationcontroller.go │ ├── pvc.go │ ├── ingress.go │ ├── role.go │ ├── rolebinding.go │ ├── networkpolicy.go │ ├── securitycontext.go │ ├── poddisruptionbudget.go │ ├── clusterrole.go │ ├── clusterrolebinding.go │ ├── serviceMonitor.go │ ├── scaledobject.go │ └── deployment_like.go ├── diagnostic │ └── diagnostic.go ├── lintcontext │ └── mocks │ │ ├── container.go │ │ ├── job.go │ │ ├── service.go │ │ ├── cronjob.go │ │ ├── scc.go │ │ ├── ingress.go │ │ ├── role.go │ │ ├── clusterrole.go │ │ ├── networkpolicy.go │ │ ├── rolebinding.go │ │ ├── clusterrolebinding.go │ │ ├── servicemonitor.go │ │ └── scaledobject.go ├── pathutil │ └── path.go ├── matcher │ └── string.go ├── ignore │ └── ignore.go ├── crds │ └── keda │ │ └── v1alpha1 │ │ ├── gvkr_types.go │ │ └── identifier.go ├── configresolver │ └── config_resolver_test.go └── checkregistry │ └── check_registry.go ├── images └── logo │ └── favicon.ico ├── tests ├── testdata │ ├── mychart-0.1.0.tgz │ ├── mychart │ │ ├── charts │ │ │ └── subchart-0.1.0.tgz │ │ ├── Chart.lock │ │ ├── Chart.yaml │ │ ├── templates │ │ │ ├── serviceaccount.yaml │ │ │ ├── service.yaml │ │ │ ├── tests │ │ │ │ └── test-connection.yaml │ │ │ ├── hpa.yaml │ │ │ └── ingress.yaml │ │ └── .helmignore │ ├── subchart │ │ ├── Chart.yaml │ │ └── .helmignore │ ├── mykustomize │ │ ├── base │ │ │ ├── service.yaml │ │ │ └── deployment.yaml │ │ └── kustomization.yaml │ ├── mykustomize-deprecated │ │ ├── base │ │ │ ├── service.yaml │ │ │ └── deployment.yaml │ │ └── kustomization.yaml │ └── splunk.yaml └── checks │ ├── dangling-networkpolicy.yml │ ├── no-extensions-v1beta.yml │ ├── use-namespace.yml │ ├── no-rolling-update-strategy.yml │ ├── pdb-max-unavailable.yaml │ ├── exposed-services.yml │ ├── dangling-networkpolicypeer-podselector.yml │ ├── no-node-affinity.yml │ ├── wildcard-in-rules.yml │ ├── unset-memory-requirements.yml │ ├── unset-cpu-requirements.yml │ ├── minimum-three-replicas.yml │ ├── unsafe-proc-mount.yml │ ├── unsafe-sysctls.yml │ ├── required-label-owner.yml │ ├── non-existent-service-account.yml │ ├── hpa-minimum-three-replicas.yml │ ├── required-annotation-email.yml │ ├── host-ipc.yml │ ├── host-pid.yml │ ├── host-network.yml │ ├── pdb-unhealthy-pod-eviction-policy.yaml │ ├── default-service-account.yml │ ├── deprecated-service-account-field.yml │ ├── cluster-admin-role-binding.yml │ ├── read-secret-from-env-var.yml │ ├── access-to-secrets.yml │ ├── access-to-create-pods.yml │ ├── forbidden-annotation.yml │ ├── latest-tag.yml │ ├── sensitive-host-mounts.yml │ ├── writable-host-mount.yml │ ├── duplicate-env-var.yaml │ ├── privileged-ports.yml │ ├── privileged-container.yml │ ├── drop-net-raw-capability.yml │ ├── env-var-secret.yml │ ├── no-read-only-root-fs.yml │ ├── privilege-escalation-container.yml │ ├── dangling-hpa.yml │ ├── mismatching-selector.yml │ ├── dangling-service.yml │ ├── run-as-non-root.yml │ ├── statefulset-volumeclaimtemplate-annotation.yml │ └── ssh-port.yml ├── tool-imports ├── empty.go └── tools.go ├── kubelinter-cosign.pub ├── SECURITY.md ├── internal ├── utils │ ├── ignore_error.go │ └── must.go ├── stringutils │ ├── ternary.go │ ├── repeat.go │ ├── default.go │ ├── split.go │ └── consume.go ├── version │ └── version.go ├── consts │ └── consts.go ├── pointers │ └── pointers.go └── defaultchecks │ ├── default_test.go │ └── default_checks.go ├── .gitignore ├── cmd └── kube-linter │ └── kube-linter.go ├── .pre-commit-hooks.yaml ├── config.yaml.example └── CONTRIBUTING.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.kubelinter.io -------------------------------------------------------------------------------- /image/bin/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @janisz 2 | * @rhybrillou 3 | -------------------------------------------------------------------------------- /e2etests/testdata/all-built-in-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | addAllBuiltIn: true 3 | -------------------------------------------------------------------------------- /pkg/templates/gen.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | //go:generate go run ./codegen 4 | -------------------------------------------------------------------------------- /pkg/config/gen.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | //go:generate go run ./codegen flags.go 4 | -------------------------------------------------------------------------------- /image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | COPY kube-linter / 4 | 5 | ENTRYPOINT ["/kube-linter"] 6 | -------------------------------------------------------------------------------- /images/logo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackrox/kube-linter/HEAD/images/logo/favicon.ico -------------------------------------------------------------------------------- /e2etests/empty.go: -------------------------------------------------------------------------------- 1 | package e2etests 2 | 3 | // Empty file with no build tag to keep the Go compiler happy. 4 | -------------------------------------------------------------------------------- /image/Dockerfile_alpine: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | COPY kube-linter / 4 | 5 | ENTRYPOINT ["/kube-linter"] 6 | -------------------------------------------------------------------------------- /pkg/command/lint/testdata/invalid.yaml: -------------------------------------------------------------------------------- 1 | this is malformed YAML that should fail to parse: { 2 | invalid: unclosed bracket -------------------------------------------------------------------------------- /tests/testdata/mychart-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackrox/kube-linter/HEAD/tests/testdata/mychart-0.1.0.tgz -------------------------------------------------------------------------------- /e2etests/testdata/schema-validation-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | addAllBuiltIn: false 3 | include: 4 | - "schema-validation" 5 | -------------------------------------------------------------------------------- /tool-imports/empty.go: -------------------------------------------------------------------------------- 1 | package toolimports 2 | 3 | // Empty file to ensure that this directory has at least one Go file even without build tags. 4 | -------------------------------------------------------------------------------- /tests/testdata/mychart/charts/subchart-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackrox/kube-linter/HEAD/tests/testdata/mychart/charts/subchart-0.1.0.tgz -------------------------------------------------------------------------------- /pkg/templates/startupport/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct{} 5 | -------------------------------------------------------------------------------- /pkg/templates/duplicatenvvar/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct{} 5 | -------------------------------------------------------------------------------- /pkg/templates/hostipc/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/hostpid/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/livenessport/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct{} 5 | -------------------------------------------------------------------------------- /pkg/templates/namespace/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/privileged/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/readinessport/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct{} 5 | -------------------------------------------------------------------------------- /pkg/templates/readsecret/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/targetport/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/danglinghpa/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/danglingingress/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/hostnetwork/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/livenessprobe/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/nodeaffinity/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/nonisolatedpod/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/pdbminavailable/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/privilegedports/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/readinessprobe/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/readonlyrootfs/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/restartpolicy/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/runasnonroot/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/sorted-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sorted-deployment 5 | spec: 6 | replicas: 3 7 | -------------------------------------------------------------------------------- /pkg/templates/unsafeprocmount/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/wildcardinrules/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/mismatchingselector/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/pdbmaxunavailable/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/privilegeescalation/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/unsorted-top-level.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | metadata: 3 | name: unsorted-deployment 4 | spec: 5 | replicas: 1 6 | kind: Deployment 7 | -------------------------------------------------------------------------------- /pkg/templates/writablehostmount/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/clusteradminrolebinding/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/danglingnetworkpolicy/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/danglingnetworkpolicypeer/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/danglingservicemonitor/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/deprecatedserviceaccount/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/nonexistentserviceaccount/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /tests/testdata/subchart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.16.0 3 | description: A Helm chart for Kubernetes 4 | name: subchart 5 | type: application 6 | version: 0.1.0 7 | -------------------------------------------------------------------------------- /pkg/templates/jobttlsecondsafterfinished/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /pkg/templates/pdbunhealthypodevictionpolicy/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | } 6 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | [![GitHub Repo stars](https://img.shields.io/github/stars/stackrox/kube-linter?label=Give%20us%20a%20star%20on%20GitHub&style=social)](https://github.com/stackrox/kube-linter) -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/configmap-unsorted-data.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | zebra: "z" 4 | apple: "a" 5 | kind: ConfigMap 6 | metadata: 7 | name: unsorted-configmap 8 | -------------------------------------------------------------------------------- /kubelinter-cosign.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl0HCkCRzYv0qH5QiazoXeXe2qwFX 3 | DmAszeH26g1s3OSsG/focPWkN88wEKQ5eiE95v+Z2snUQPl/mjPdvqpyjA== 4 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /tests/checks/dangling-networkpolicy.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: NetworkPolicy 3 | metadata: 4 | name: app 5 | spec: 6 | podSelector: 7 | matchLabels: 8 | app.kubernetes.io/name: app1 -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a vulnerability 2 | 3 | If you've found a security issue that you'd like to disclose confidentially please contact [Red Hat's Product Security team](https://access.redhat.com/security/team/contact). 4 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/edge-cases.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: {} 5 | labels: 6 | single: value 7 | name: edge-cases 8 | spec: 9 | replicas: 1 10 | -------------------------------------------------------------------------------- /tests/checks/no-extensions-v1beta.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: app 6 | --- 7 | apiVersion: extensions/v1beta1 8 | kind: NetworkPolicy 9 | metadata: 10 | name: app -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [Introduction](/) 2 | * [Using KubeLinter](/using-kubelinter.md) 3 | * [Configuring KubeLinter](/configuring-kubelinter.md) 4 | * [KubeLinter checks](/generated/checks.md) 5 | * [KubeLinter templates](/generated/templates.md) -------------------------------------------------------------------------------- /pkg/templates/sccdenypriv/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // allowPrivilegedContainer value 7 | AllowPrivilegedContainer bool 8 | } 9 | -------------------------------------------------------------------------------- /internal/utils/ignore_error.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // IgnoreError is useful when you want to defer a func that returns an error, 4 | // but ignore the error without having the linter complain. 5 | func IgnoreError(f func() error) { 6 | _ = f() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/templates/ports/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The port 7 | Port int 8 | 9 | // The protocol 10 | Protocol string 11 | } 12 | -------------------------------------------------------------------------------- /pkg/templates/replicas/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The minimum number of replicas a deployment should have 7 | MinReplicas int 8 | } 9 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: test-service 5 | spec: 6 | selector: 7 | app: test-app 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 80 12 | -------------------------------------------------------------------------------- /internal/stringutils/ternary.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | // Ternary does a ternary based on the condition. 4 | func Ternary(condition bool, ifTrue, ifFalse string) string { 5 | if condition { 6 | return ifTrue 7 | } 8 | return ifFalse 9 | } 10 | -------------------------------------------------------------------------------- /tests/checks/use-namespace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | namespace: namespace-dev 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: app1 12 | namespace: default -------------------------------------------------------------------------------- /tests/testdata/mychart/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: subchart 3 | repository: file://../subchart 4 | version: 0.1.0 5 | digest: sha256:f8645a52640009a9d8905d929d3362e04720964c71e37af357b8b5e2852c365d 6 | generated: "2023-11-23T15:35:33.69184771+01:00" 7 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize-deprecated/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: test-service 5 | spec: 6 | selector: 7 | app: test-app 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 80 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Settings files for JetBrains IDEs 2 | /.idea 3 | 4 | # Vim swap files 5 | *.swp 6 | 7 | # Project-specific $GOBIN 8 | /.gobin 9 | 10 | # Empty file touched by `make deps` 11 | /deps 12 | 13 | e2etests/test_helper 14 | dist/ 15 | coverage.out 16 | -------------------------------------------------------------------------------- /pkg/templates/hpareplicas/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The minimum number of replicas a HorizontalPodAutoscaler should have 7 | MinReplicas int 8 | } 9 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/numeric-keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | "10-config": value10 4 | "2-config": value2 5 | "1-config": value1 6 | config-z: valuez 7 | config-a: valuea 8 | kind: ConfigMap 9 | metadata: 10 | name: numeric-keys 11 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - base/deployment.yaml 5 | - base/service.yaml 6 | namePrefix: kustomize- 7 | labels: 8 | - pairs: 9 | environment: test 10 | -------------------------------------------------------------------------------- /pkg/templates/volumeclaimtemplates/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // Annotation specifies the required annotation to match. 6 | // +required 7 | Annotation string 8 | } 9 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/hostipc.yaml: -------------------------------------------------------------------------------- 1 | name: "host-ipc" 2 | description: "Alert on pods/deployment-likes with sharing host's IPC namespace" 3 | remediation: "Ensure the host's IPC namespace is not shared." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "host-ipc" 8 | -------------------------------------------------------------------------------- /tests/testdata/mychart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: 1.16.0 3 | description: A Helm chart for Kubernetes 4 | name: mychart 5 | type: application 6 | version: 0.1.0 7 | dependencies: 8 | - name: subchart 9 | version: 0.1.0 10 | repository: file://../subchart 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/hostpid.yaml: -------------------------------------------------------------------------------- 1 | name: "host-pid" 2 | description: "Alert on pods/deployment-likes with sharing host's process namespace" 3 | remediation: "Ensure the host's process namespace is not shared." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "host-pid" 8 | -------------------------------------------------------------------------------- /pkg/templates/serviceaccount/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // A regex specifying the required service account to match. 7 | // +required 8 | ServiceAccount string 9 | } 10 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/non-recursive.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: non-recursive 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: app 10 | metadata: 11 | labels: 12 | app: myapp 13 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // Recursive determines whether to check keys recursively at all nesting levels. 6 | // Default is true. 7 | Recursive bool 8 | } 9 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/hostnetwork.yaml: -------------------------------------------------------------------------------- 1 | name: "host-network" 2 | description: "Alert on pods/deployment-likes with sharing host's network namespace" 3 | remediation: "Ensure the host's network namespace is not shared." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "host-network" 8 | -------------------------------------------------------------------------------- /pkg/templates/dnsconfigoptions/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // Key of the dnsConfig option. 7 | Key string 8 | 9 | // Value of the dnsConfig option. 10 | Value string 11 | } 12 | -------------------------------------------------------------------------------- /pkg/templates/sysctl/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // An array of unsafe system controls 6 | // +noregex 7 | // +notnegatable 8 | UnsafeSysCtls []string `json:"unsafeSysCtls"` 9 | } 10 | -------------------------------------------------------------------------------- /e2etests/testdata/statefulset-volumeclaimtemplate-annotation-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | addAllBuiltIn: false 3 | customChecks: 4 | - name: "statefulset-volumeclaimtemplate-annotation" 5 | template: "statefulset-volumeclaimtemplate-annotation" 6 | params: 7 | annotation: required-annotation 8 | -------------------------------------------------------------------------------- /pkg/k8sutil/object.go: -------------------------------------------------------------------------------- 1 | package k8sutil 2 | 3 | import ( 4 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | ) 7 | 8 | // Object is a combination of `runtime.Object` and `metav1.Object`. 9 | type Object interface { 10 | runtime.Object 11 | metaV1.Object 12 | } 13 | -------------------------------------------------------------------------------- /pkg/templates/requiredlabel/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // Key of the required label. 7 | // +required 8 | Key string 9 | 10 | // Value of the required label. 11 | Value string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/templates/requiredannotation/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // Key of the required label. 7 | // +required 8 | Key string 9 | 10 | // Value of the required label. 11 | Value string 12 | } 13 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/internal/stringutils" 5 | ) 6 | 7 | var ( 8 | version string //XDef:VERSION 9 | ) 10 | 11 | // Get returns the version. 12 | func Get() string { 13 | return stringutils.OrDefault(version, "development") 14 | } 15 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/privileged.yaml: -------------------------------------------------------------------------------- 1 | name: "privileged-container" 2 | description: "Indicates when deployments have containers running in privileged mode." 3 | remediation: "Do not run your container as privileged unless it is required." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "privileged" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/privilegedports.yaml: -------------------------------------------------------------------------------- 1 | name: "privileged-ports" 2 | description: "Alert on deployments with privileged ports mapped in containers" 3 | remediation: "Ensure privileged ports [0, 1024] are not mapped within containers." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "privileged-ports" 8 | -------------------------------------------------------------------------------- /pkg/templates/envvar/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The name of the environment variable. 7 | // +required 8 | Name string 9 | 10 | // The value of the environment variable. 11 | Value string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/templates/kubeconform/testdata/demonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: nginx-ds 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | k8s-app: true 10 | template: 11 | spec: 12 | containers: 13 | - image: nginx 14 | name: nginx -------------------------------------------------------------------------------- /tests/checks/no-rolling-update-strategy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | strategy: 8 | type: Other 9 | --- 10 | apiVersion: apps.openshift.io/v1 11 | kind: DeploymentConfig 12 | metadata: 13 | name: app2 14 | spec: 15 | strategy: 16 | type: Other -------------------------------------------------------------------------------- /internal/utils/must.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // Must panics if any of the errors are not nil. 4 | // It is intended for use in cases where an error returned would 5 | // mean a programming error. 6 | func Must(errs ...error) { 7 | for _, err := range errs { 8 | if err != nil { 9 | panic(err) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg/templates/forbiddenannotation/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // Key of the forbidden annotation. 7 | // +required 8 | Key string 9 | 10 | // Value of the forbidden annotation. 11 | Value string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/unsorted-nested.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: unsorted-nested 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: app 10 | image: myapp:latest 11 | metadata: 12 | labels: 13 | app: myapp 14 | -------------------------------------------------------------------------------- /pkg/templates/hostmounts/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // An array of regular expressions specifying system directories to be mounted on containers. e.g. ^/usr$ for /usr 6 | // +notnegatable 7 | Dirs []string `json:"dirs"` 8 | } 9 | -------------------------------------------------------------------------------- /pkg/templates/servicetype/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // An array of service types that should not be used 6 | // +noregex 7 | // +notnegatable 8 | ForbiddenServiceTypes []string `json:"forbiddenServiceTypes"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/duplicate-env-var.yaml: -------------------------------------------------------------------------------- 1 | name: "duplicate-env-var" 2 | description: "Check that duplicate named env vars aren't passed to a deployment like." 3 | remediation: "Confirm that your DeploymentLike doesn't have duplicate env vars names." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "duplicate-env-var" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-ingress.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-ingress" 2 | description: "Indicates when ingress do not have any associated services." 3 | remediation: "Confirm that your ingress's backend correctly matches the name and port on one of your services." 4 | scope: 5 | objectKinds: 6 | - Ingress 7 | template: "dangling-ingress" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-service.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-service" 2 | description: "Indicates when services do not have any associated deployments." 3 | remediation: "Confirm that your service's selector correctly matches the labels on one of your deployments." 4 | scope: 5 | objectKinds: 6 | - Service 7 | template: "dangling-service" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/read-only-root-fs.yaml: -------------------------------------------------------------------------------- 1 | name: "no-read-only-root-fs" 2 | description: "Indicates when containers are running without a read-only root filesystem." 3 | remediation: "Set readOnlyRootFilesystem to true in the container securityContext." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "read-only-root-fs" 8 | -------------------------------------------------------------------------------- /pkg/extract/gvk.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/k8sutil" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | // GVK extracts the GroupVersionKind of an object. 9 | func GVK(object k8sutil.Object) schema.GroupVersionKind { 10 | return object.GetObjectKind().GroupVersionKind() 11 | } 12 | -------------------------------------------------------------------------------- /tests/checks/pdb-max-unavailable.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: baz 6 | namespace: bar 7 | spec: 8 | maxUnavailable: 1 9 | --- 10 | apiVersion: policy/v1 11 | kind: PodDisruptionBudget 12 | metadata: 13 | name: foo 14 | namespace: bar 15 | spec: 16 | maxUnavailable: 0 17 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/writable-host-mount.yaml: -------------------------------------------------------------------------------- 1 | name: "writable-host-mount" 2 | description: "Indicates when containers mount a host path as writable." 3 | remediation: "Set containers to mount host paths as readOnly, if you need to access files on the host." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "writable-host-mount" 8 | -------------------------------------------------------------------------------- /pkg/objectkinds/job_like.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime/schema" 5 | ) 6 | 7 | const ( 8 | JobLike = "JobLike" 9 | ) 10 | 11 | func init() { 12 | RegisterObjectKind(JobLike, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 13 | return gvk == jobGVK || gvk == cronJobGVK 14 | })) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/templates/priorityclassname/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // Array of all priority class names that are accepted. 6 | // +noregex 7 | // +notnegatable 8 | AcceptedPriorityClassNames []string `json:"acceptedPriorityClassNames"` 9 | } 10 | -------------------------------------------------------------------------------- /tests/checks/exposed-services.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | selector: 8 | app: app 9 | ports: 10 | - protocol: TCP 11 | port: 80 12 | targetPort: 8080 13 | --- 14 | apiVersion: v1 15 | kind: Service 16 | metadata: 17 | name: app 18 | spec: 19 | type: NodePort -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/restart-policy.yaml: -------------------------------------------------------------------------------- 1 | name: "restart-policy" 2 | description: "Indicates when a deployment-like object does not use a restart policy" 3 | remediation: >- 4 | Set up the restart policy for your object to 'Always' or 'OnFailure' to increase the fault tolerance. 5 | scope: 6 | objectKinds: 7 | - DeploymentLike 8 | template: "restart-policy" -------------------------------------------------------------------------------- /pkg/templates/danglingservice/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // A list of labels that will not cause the check to fail. For example, a label that is known to be populated at runtime by Kubernetes. 6 | IgnoredLabels []string `json:"ignoredLabels"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/stringutils/repeat.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Repeat repeats the given string `n` times efficiently. 8 | func Repeat(s string, n int) string { 9 | var sb strings.Builder 10 | sb.Grow(len([]byte(s)) * n) 11 | for i := 0; i < n; i++ { 12 | sb.WriteString(s) 13 | } 14 | return sb.String() 15 | } 16 | -------------------------------------------------------------------------------- /pkg/templates/imagepullpolicy/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // list of forbidden image pull policy 6 | // +noregex 7 | // +notnegatable 8 | // +enum=Always 9 | // +enum=IfNotPresent 10 | // +enum=Never 11 | ForbiddenPolicies []string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/mismatching-selector.yaml: -------------------------------------------------------------------------------- 1 | name: "mismatching-selector" 2 | description: "Indicates when deployment selectors fail to match the pod template labels." 3 | remediation: "Confirm that your deployment selector correctly matches the labels in its pod template." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "mismatching-selector" 8 | -------------------------------------------------------------------------------- /pkg/objectkinds/any.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime/schema" 5 | ) 6 | 7 | const ( 8 | // Any represents the ObjectKind that matches any object. 9 | Any = "Any" 10 | ) 11 | 12 | func init() { 13 | RegisterObjectKind(Any, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 14 | return true 15 | })) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/non-existent-service-account.yaml: -------------------------------------------------------------------------------- 1 | name: "non-existent-service-account" 2 | description: "Indicates when pods reference a service account that is not found." 3 | remediation: "Create the missing service account, or refer to an existing service account." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "non-existent-service-account" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/startup-port.yaml: -------------------------------------------------------------------------------- 1 | name: "startup-port" 2 | description: "Indicates when containers have a startup probe to a not exposed port." 3 | remediation: >- 4 | Check which ports you've exposed and ensure they match what you have specified 5 | in the startup probe. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "startup-port" 10 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/stackrox/kube-linter/main/images/logo/KubeLinter-horizontal.svg) 2 | 3 | > Static analysis for Kubernetes YAML files and Helm charts. 4 | 5 |
Made with
by StackRox
6 | 7 | [GitHub](https://github.com/stackrox/kube-linter) 8 | [Get Started](README) -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-networkpolicy" 2 | description: "Indicates when networkpolicies do not have any associated deployments." 3 | remediation: "Confirm that your networkPolicy's podselector correctly matches the labels on one of your deployments." 4 | scope: 5 | objectKinds: 6 | - NetworkPolicy 7 | template: "dangling-networkpolicy" -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/liveness-port.yaml: -------------------------------------------------------------------------------- 1 | name: "liveness-port" 2 | description: "Indicates when containers have a liveness probe to a not exposed port." 3 | remediation: >- 4 | Check which ports you've exposed and ensure they match what you have specified 5 | in the liveness probe. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "liveness-port" 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/readiness-port.yaml: -------------------------------------------------------------------------------- 1 | name: "readiness-port" 2 | description: "Indicates when containers have a readiness probe to a not exposed port." 3 | remediation: >- 4 | Check which ports you've exposed and ensure they match what you have specified 5 | in the readiness probe. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "readiness-port" 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/schema-validation.yaml: -------------------------------------------------------------------------------- 1 | name: "schema-validation" 2 | description: "Validate Kubernetes resources against their schemas using kubeconform" 3 | remediation: "Fix the resource to conform to the Kubernetes API schema." 4 | scope: 5 | objectKinds: 6 | - Any 7 | template: "kubeconform" 8 | params: 9 | strict: true 10 | ignoreMissingSchemas: true 11 | -------------------------------------------------------------------------------- /pkg/objectkinds/pvc_test.go: -------------------------------------------------------------------------------- 1 | package objectkinds_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | ) 9 | 10 | func TestGetPersistentVolumeClaimAPIVersion(t *testing.T) { 11 | apiVersion := objectkinds.GetPersistentVolumeClaimAPIVersion() 12 | assert.NotEmpty(t, apiVersion) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/required-label-owner.yaml: -------------------------------------------------------------------------------- 1 | name: "required-label-owner" 2 | description: "Indicates when objects do not have an email annotation with an owner label." 3 | remediation: "Add an email annotation to your object with the name of the object's owner." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "required-label" 8 | params: 9 | key: "owner" 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/sorted-keys.yaml: -------------------------------------------------------------------------------- 1 | name: "sorted-keys" 2 | description: "Check that YAML keys are sorted in alphabetical order wherever possible." 3 | remediation: "Ensure that keys in your YAML manifest are sorted in alphabetical order to improve consistency and readability." 4 | scope: 5 | objectKinds: 6 | - Any 7 | template: "sorted-keys" 8 | params: 9 | recursive: true 10 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize-deprecated/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | # Use deprecated 'bases' field instead of 'resources' 4 | # This triggers a deprecation warning from kustomize 5 | bases: 6 | - base/deployment.yaml 7 | - base/service.yaml 8 | namePrefix: deprecated-kustomize- 9 | labels: 10 | - pairs: 11 | environment: test 12 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/servicetype.yaml: -------------------------------------------------------------------------------- 1 | name: "exposed-services" 2 | description: "Alert on services for forbidden types" 3 | remediation: "Ensure containers are not exposed through a forbidden service type such as NodePort or LoadBalancer." 4 | scope: 5 | objectKinds: 6 | - Service 7 | template: "forbidden-service-types" 8 | params: 9 | forbiddenServiceTypes: ["NodePort", "LoadBalancer"] 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-horizontalpodautoscaler.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-horizontalpodautoscaler" 2 | description: "Indicates when HorizontalPodAutoscalers target a missing resource." 3 | remediation: "Confirm that your HorizontalPodAutoscaler's scaleTargetRef correctly matches one of your deployments." 4 | scope: 5 | objectKinds: 6 | - HorizontalPodAutoscaler 7 | template: "dangling-horizontalpodautoscaler" -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/latest-tag.yaml: -------------------------------------------------------------------------------- 1 | name: "latest-tag" 2 | description: "Indicates when a deployment-like object is running a container with an invalid container image" 3 | remediation: "Use a container image with a specific tag other than latest." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "latest-tag" 8 | params: 9 | BlockList: [".*:(latest)$", "^[^:]*$", "(.*/[^:]+)$"] 10 | -------------------------------------------------------------------------------- /cmd/kube-linter/kube-linter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "golang.stackrox.io/kube-linter/pkg/command/root" 8 | // Register templates 9 | _ "golang.stackrox.io/kube-linter/pkg/templates/all" 10 | ) 11 | 12 | func main() { 13 | c := root.Command() 14 | if err := c.Execute(); err != nil { 15 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/ssh-port.yaml: -------------------------------------------------------------------------------- 1 | name: "ssh-port" 2 | description: "Indicates when deployments expose port 22, which is commonly reserved for SSH access." 3 | remediation: "Ensure that non-SSH services are not using port 22. Confirm that any actual SSH servers have been vetted." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "ports" 8 | params: 9 | port: 22 10 | protocol: "TCP" 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/cluster-admin-role-binding.yaml: -------------------------------------------------------------------------------- 1 | name: "cluster-admin-role-binding" 2 | description: "CIS Benchmark 5.1.1 Ensure that the cluster-admin role is only used where required" 3 | remediation: "Create and assign a separate role that has access to specific resources/actions needed for the service account." 4 | scope: 5 | objectKinds: 6 | - ClusterRoleBinding 7 | template: "cluster-admin-role-binding" 8 | -------------------------------------------------------------------------------- /pkg/templates/cel/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params defines the configuration parameters for this template. 4 | type Params struct { 5 | // Check contains a CEL expression for validation logic. Two predefined variables are available: 'object' (the current Kubernetes object being processed) and 'objects' (all objects being linted). 6 | // +required 7 | // +noregex 8 | Check string 9 | } 10 | -------------------------------------------------------------------------------- /tests/testdata/mychart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "mychart.serviceAccountName" . }} 6 | labels: 7 | {{- include "mychart.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/minimum-replicas.yaml: -------------------------------------------------------------------------------- 1 | name: "minimum-three-replicas" 2 | description: "Indicates when a deployment uses less than three replicas" 3 | remediation: >- 4 | Increase the number of replicas in the deployment to at least three to increase the fault tolerance of the deployment. 5 | scope: 6 | objectKinds: 7 | - DeploymentLike 8 | template: "minimum-replicas" 9 | params: 10 | minReplicas: 3 11 | -------------------------------------------------------------------------------- /pkg/templates/latesttag/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // list of regular expressions specifying pattern(s) for container images that will be blocked. */ 7 | BlockList []string 8 | 9 | // list of regular expressions specifying pattern(s) for container images that will be allowed. 10 | AllowList []string 11 | } 12 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/deprecated-service-account.yaml: -------------------------------------------------------------------------------- 1 | name: "deprecated-service-account-field" 2 | description: "Indicates when deployments use the deprecated serviceAccount field." 3 | remediation: "Use the serviceAccountName field instead. If you must specify serviceAccount, ensure values for serviceAccount and serviceAccountName match." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "deprecated-service-account-field" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-liveness-probe.yaml: -------------------------------------------------------------------------------- 1 | name: "no-liveness-probe" 2 | description: "Indicates when containers fail to specify a liveness probe." 3 | remediation: >- 4 | Specify a liveness probe in your container. 5 | Refer to https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "liveness-probe" 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/non-isolated-pod.yaml: -------------------------------------------------------------------------------- 1 | name: "non-isolated-pod" 2 | description: "Alert on deployment-like objects that are not selected by any NetworkPolicy." 3 | remediation: "Ensure pod does not accept unsafe traffic by isolating it with a NetworkPolicy. See https://cloud.redhat.com/blog/guide-to-kubernetes-ingress-network-policies for more details." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "non-isolated-pod" -------------------------------------------------------------------------------- /tests/checks/dangling-networkpolicypeer-podselector.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: NetworkPolicy 3 | metadata: 4 | name: app 5 | spec: 6 | podSelector: 7 | matchLabels: 8 | app.kubernetes.io/name: app1 9 | ingress: 10 | - from: 11 | - podSelector: 12 | matchLabels: 13 | app.kubernetes.io/name: app2 14 | ports: 15 | - protocol: TCP 16 | port: 8080 -------------------------------------------------------------------------------- /tests/checks/no-node-affinity.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: nginx 9 | replicas: 1 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.14.2 18 | ports: 19 | - containerPort: 80 -------------------------------------------------------------------------------- /tool-imports/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package toolimports 5 | 6 | // This file declares dependencies on tool for `go mod` purposes. 7 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 8 | // for an explanation of the approach. 9 | 10 | import ( 11 | _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" 12 | _ "github.com/goreleaser/goreleaser" 13 | ) 14 | -------------------------------------------------------------------------------- /internal/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const ( 4 | // ProgramName is for displaying help, etc. 5 | ProgramName = "kube-linter" 6 | // MainURL is where the project info can be found. 7 | MainURL = "https://github.com/stackrox/kube-linter" 8 | // TemplateURLFormat when formatted with template id, provides help link for the given template. 9 | TemplateURLFormat = "https://docs.kubelinter.io/#/generated/templates?id=%s" 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-readiness-probe.yaml: -------------------------------------------------------------------------------- 1 | name: "no-readiness-probe" 2 | description: "Indicates when containers fail to specify a readiness probe." 3 | remediation: >- 4 | Specify a readiness probe in your container. 5 | Refer to https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "readiness-probe" 10 | -------------------------------------------------------------------------------- /pkg/extract/metadata.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/k8sutil" 5 | ) 6 | 7 | // Labels extracts labels from the given object. 8 | func Labels(object k8sutil.Object) map[string]string { 9 | return object.GetLabels() 10 | } 11 | 12 | // Annotations extracts annotations from the given object. 13 | func Annotations(object k8sutil.Object) map[string]string { 14 | return object.GetAnnotations() 15 | } 16 | -------------------------------------------------------------------------------- /internal/pointers/pointers.go: -------------------------------------------------------------------------------- 1 | package pointers 2 | 3 | // Bool returns a pointer to a bool. 4 | func Bool(b bool) *bool { 5 | return &b 6 | } 7 | 8 | // Int32 returns a pointer to an int32. 9 | func Int32(i int32) *int32 { 10 | return &i 11 | } 12 | 13 | // Int64 returns a pointer to an int64. 14 | func Int64(i int64) *int64 { 15 | return &i 16 | } 17 | 18 | // Int returns a pointer to an int. 19 | func Int(i int) *int { 20 | return &i 21 | } 22 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/hpa-minimum-replicas.yaml: -------------------------------------------------------------------------------- 1 | name: "hpa-minimum-three-replicas" 2 | description: "Indicates when a HorizontalPodAutoscaler specifies less than three minReplicas" 3 | remediation: >- 4 | Increase the number of replicas in the HorizontalPodAutoscaler to at least three to increase fault tolerance. 5 | scope: 6 | objectKinds: 7 | - HorizontalPodAutoscaler 8 | template: "hpa-minimum-replicas" 9 | params: 10 | minReplicas: 3 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/job-ttl-seconds-after-finished.yaml: -------------------------------------------------------------------------------- 1 | name: "job-ttl-seconds-after-finished" 2 | description: "Indicates when standalone jobs do not set ttlSecondsAfterFinished and when jobs managed by cronjob do set ttlSecondsAfterFinished." 3 | remediation: "Set Job.spec.ttlSecondsAfterFinished. Unset CronJob.Spec.JobTemplate.Spec.ttlSecondsAfterFinished." 4 | scope: 5 | objectKinds: 6 | - JobLike 7 | template: "job-ttl-seconds-after-finished" 8 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/mixed-sorting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: test 6 | version: v1 7 | name: mixed-sorting 8 | spec: 9 | selector: 10 | matchLabels: 11 | version: v1 12 | app: test 13 | template: 14 | metadata: 15 | labels: 16 | app: test 17 | spec: 18 | containers: 19 | - image: test:latest 20 | name: test 21 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/reused-labels-sorted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: &labels 5 | app: myapp 6 | environment: production 7 | version: v1.0 8 | name: anchor-deployment 9 | spec: 10 | selector: 11 | matchLabels: *labels 12 | template: 13 | metadata: 14 | labels: *labels 15 | spec: 16 | containers: 17 | - image: myapp:latest 18 | name: app -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/reused-labels-unsorted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: &labels 5 | version: v1.0 6 | app: myapp 7 | environment: production 8 | name: anchor-unsorted 9 | spec: 10 | selector: 11 | matchLabels: *labels 12 | template: 13 | metadata: 14 | labels: *labels 15 | spec: 16 | containers: 17 | - name: app 18 | image: myapp:latest -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-servicemonitor" 2 | description: "Indicates when a service monitor's selectors don't match any service. ServiceMonitors are a custom resource only used by the Prometheus operator (https://prometheus-operator.dev/docs/operator/design/#servicemonitor)." 3 | remediation: "Check selectors and your services." 4 | scope: 5 | objectKinds: 6 | - ServiceMonitor 7 | template: "dangling-servicemonitor" 8 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/priority-class-name.yaml: -------------------------------------------------------------------------------- 1 | name: "priority-class-name" 2 | description: "Indicates when a deployment-like object does not use a valid priority class name" 3 | remediation: >- 4 | Set up the priority class name for your object to any accepted values. 5 | scope: 6 | objectKinds: 7 | - DeploymentLike 8 | template: "priority-class-name" 9 | params: 10 | acceptedPriorityClassNames: ["system-cluster-critical", "system-node-critical"] -------------------------------------------------------------------------------- /pkg/templates/antiaffinity/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The minimum number of replicas a deployment must have before anti-affinity is enforced on it 7 | MinReplicas int 8 | 9 | // The topology key that the anti-affinity term should use. 10 | // If not specified, it defaults to "kubernetes.io/hostname". 11 | TopologyKey string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/env-var-value-from.yaml: -------------------------------------------------------------------------------- 1 | # customChecks defines custom checks. 2 | name: "env-value-from" 3 | template: "env-value-from" 4 | description: "Indicates when objects use a secret or configmap not included in the deployment." 5 | remediation: >- 6 | Change the name or key to match a secret / configmap in the deployment. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | params: 11 | IgnoredSecrets: [] 12 | IgnoredConfigMaps: [] 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/run-as-non-root.yaml: -------------------------------------------------------------------------------- 1 | name: "run-as-non-root" 2 | description: "Indicates when containers are not set to runAsNonRoot." 3 | remediation: >- 4 | Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. 5 | Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "run-as-non-root" 10 | -------------------------------------------------------------------------------- /pkg/templates/util/value_in_range.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // ValueInRange returns whether the given quantity is in the range between the lowerBound and the upperBound (inclusive). 4 | // A nil upper bound is interpreted as infinity. 5 | func ValueInRange(value, lowerBound int, upperBound *int) bool { 6 | if value < lowerBound { 7 | return false 8 | } 9 | if upperBound != nil && value > *upperBound { 10 | return false 11 | } 12 | return true 13 | } 14 | -------------------------------------------------------------------------------- /e2etests/testdata/kubeconform-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | addAllBuiltIn: false 3 | customChecks: 4 | - name: "kubeconform-validation" 5 | description: "Validate Kubernetes resources against their schemas using kubeconform" 6 | remediation: "Fix the resource to conform to the Kubernetes API schema" 7 | scope: 8 | objectKinds: 9 | - Any 10 | template: "kubeconform" 11 | params: 12 | strict: true 13 | ignoreMissingSchemas: true -------------------------------------------------------------------------------- /pkg/objectkinds/pod.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | coreV1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Pod represents Kubernetes Pod objects. 10 | Pod = "Pod" 11 | ) 12 | 13 | var ( 14 | podGVK = coreV1.SchemeGroupVersion.WithKind("Pod") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(Pod, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == podGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /tests/testdata/mychart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "mychart.fullname" . }} 5 | labels: 6 | {{- include "mychart.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "mychart.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yaml: -------------------------------------------------------------------------------- 1 | name: auto-merge 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | auto-merge: 8 | runs-on: ubuntu-latest 9 | if: github.actor == 'dependabot[bot]' 10 | steps: 11 | - uses: ahmadnassri/action-dependabot-auto-merge@v2.6 12 | with: 13 | github-token: '${{ secrets.RHACS_BOT_GITHUB_TOKEN }}' 14 | command: "squash and merge" 15 | approve: true 16 | target: minor 17 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/required-annotation-email.yaml: -------------------------------------------------------------------------------- 1 | name: "required-annotation-email" 2 | description: "Indicates when objects do not have an email annotation with a valid email address." 3 | remediation: "Add an email annotation to your object with the email address of the object's owner." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "required-annotation" 8 | params: 9 | key: "email" 10 | value: '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+' 11 | -------------------------------------------------------------------------------- /pkg/objectkinds/job.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | batchV1 "k8s.io/api/batch/v1" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | const ( 10 | // Job represents Kubernetes Job objects. 11 | Job = "Job" 12 | ) 13 | 14 | var ( 15 | jobGVK = batchV1.SchemeGroupVersion.WithKind("Job") 16 | ) 17 | 18 | func init() { 19 | RegisterObjectKind(Job, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 20 | return gvk == jobGVK 21 | })) 22 | } 23 | -------------------------------------------------------------------------------- /tests/checks/wildcard-in-rules.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: role1 6 | namespace: namespace-dev 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["pods"] 10 | verbs: ["get"] 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: Role 14 | metadata: 15 | name: role1 16 | namespace: namespace-dev 17 | rules: 18 | - apiGroups: [""] 19 | resources: ["secrets"] 20 | verbs: ["*"] 21 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/default-service-account.yaml: -------------------------------------------------------------------------------- 1 | name: "default-service-account" 2 | description: "Indicates when pods use the default service account." 3 | remediation: >- 4 | Create a dedicated service account for your pod. 5 | Refer to https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "service-account" 10 | params: 11 | serviceAccount: "^(|default)$" 12 | -------------------------------------------------------------------------------- /tests/testdata/mychart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /tests/testdata/subchart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/host-mounts.yaml: -------------------------------------------------------------------------------- 1 | name: "sensitive-host-mounts" 2 | description: "Alert on deployments with sensitive host system directories mounted in containers" 3 | remediation: "Ensure sensitive host system directories are not mounted in containers by removing those Volumes and VolumeMounts." 4 | scope: 5 | objectKinds: 6 | - DeploymentLike 7 | template: "host-mounts" 8 | params: 9 | dirs: ["^/$", "^/boot$", "^/dev$", "^/etc$", "^/lib$", "^/proc$", "^/sys$", "^/usr$"] 10 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-extensions-v1beta.yaml: -------------------------------------------------------------------------------- 1 | name: "no-extensions-v1beta" 2 | description: "Indicates when objects use deprecated API versions under extensions/v1beta." 3 | remediation: >- 4 | Migrate using the apps/v1 API versions for the objects. 5 | Refer to https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ for details. 6 | scope: 7 | objectKinds: 8 | - Any 9 | template: "disallowed-api-obj" 10 | params: 11 | group: "extensions" 12 | version: "v1beta.+" 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/usenamespace.yaml: -------------------------------------------------------------------------------- 1 | name: "use-namespace" 2 | description: >- 3 | Indicates when a resource is deployed to the default namespace. 4 | CIS Benchmark 5.7.1: Create administrative boundaries between resources using namespaces. 5 | CIS Benchmark 5.7.4: The default namespace should not be used. 6 | remediation: "Create namespaces for objects in your deployment." 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | - Service 11 | template: "use-namespace" 12 | -------------------------------------------------------------------------------- /pkg/templates/disallowedgvk/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The disallowed object group. 7 | // +example=apps 8 | Group string `json:"group"` 9 | 10 | // The disallowed object API version. 11 | // +example=v1 12 | // +example=v1beta1 13 | Version string 14 | 15 | // The disallowed kind. 16 | // +example=Deployment 17 | // +example=DaemonSet 18 | Kind string 19 | } 20 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/configmap-merge-unsorted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | base-config: &base 4 | timeout: "30s" 5 | retries: "3" 6 | 7 | # When merged, these keys will be unsorted 8 | service-a-config: 9 | <<: *base 10 | endpoint: "http://service-a" 11 | auth: "bearer-token" 12 | 13 | service-b-config: 14 | <<: *base 15 | zz-custom: "value" 16 | aa-priority: "high" 17 | kind: ConfigMap 18 | metadata: 19 | name: config-with-anchors -------------------------------------------------------------------------------- /pkg/objectkinds/service.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Service represents Kubernetes Service objects. 10 | Service = "Service" 11 | ) 12 | 13 | var ( 14 | serviceGVK = v1.SchemeGroupVersion.WithKind("Service") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(Service, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == serviceGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/objectkinds/types.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime/schema" 5 | ) 6 | 7 | // A Matcher selects a certain subset of GVKs. 8 | type Matcher interface { 9 | Matches(gvk schema.GroupVersionKind) bool 10 | } 11 | 12 | // MatcherFunc takes in a GVK and decides if it matches an object kind 13 | type MatcherFunc func(gvk schema.GroupVersionKind) bool 14 | 15 | func (f MatcherFunc) Matches(gvk schema.GroupVersionKind) bool { 16 | return f(gvk) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/templates/envvarvaluefrom/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // ignored list => these resources already exist in the cluster. 6 | 7 | // list of regular expressions specifying pattern(s) for secrets that will be ignored. 8 | IgnoredSecrets []string 9 | 10 | // list of regular expressions specifying pattern(s) for secrets that will be ignored. 11 | IgnoredConfigMaps []string 12 | } 13 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-deployment 5 | labels: 6 | app: test-app 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: test-app 12 | template: 13 | metadata: 14 | labels: 15 | app: test-app 16 | spec: 17 | containers: 18 | - name: test-container 19 | image: nginx:1.14.2 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-node-affinity.yaml: -------------------------------------------------------------------------------- 1 | name: "no-node-affinity" 2 | description: "Alert on deployments that have no node affinity defined" 3 | remediation: >- 4 | Specify node-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on specified nodes. 5 | Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "no-node-affinity" 10 | -------------------------------------------------------------------------------- /pkg/command/version/command.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "golang.stackrox.io/kube-linter/internal/version" 8 | ) 9 | 10 | // Command defines the version command 11 | func Command() *cobra.Command { 12 | c := &cobra.Command{ 13 | Use: "version", 14 | Short: "Print version and exit", 15 | Args: cobra.NoArgs, 16 | Run: func(cmd *cobra.Command, _ []string) { 17 | fmt.Println(version.Get()) 18 | }, 19 | } 20 | return c 21 | } 22 | -------------------------------------------------------------------------------- /tests/testdata/mychart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "mychart.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "mychart.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /pkg/extract/scc_spec.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | ocpSecV1 "github.com/openshift/api/security/v1" 5 | "golang.stackrox.io/kube-linter/pkg/k8sutil" 6 | ) 7 | 8 | // SCCallowPrivilegedContainer extracts allowPrivilegedContainer from the given object, if available. 9 | func SCCallowPrivilegedContainer(obj k8sutil.Object) (bool, bool) { 10 | if scc, ok := obj.(*ocpSecV1.SecurityContextConstraints); ok { 11 | return scc.AllowPrivilegedContainer, true 12 | } 13 | return false, false 14 | } 15 | -------------------------------------------------------------------------------- /tests/testdata/mykustomize-deprecated/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-deployment 5 | labels: 6 | app: test-app 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: test-app 12 | template: 13 | metadata: 14 | labels: 15 | app: test-app 16 | spec: 17 | containers: 18 | - name: test-container 19 | image: nginx:1.14.2 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/pdb-unhealthy-pod-eviction-policy.yaml: -------------------------------------------------------------------------------- 1 | name: "pdb-unhealthy-pod-eviction-policy" 2 | description: "Indicates when a PodDisruptionBudget does not explicitly set the unhealthyPodEvictionPolicy field." 3 | remediation: "Set unhealthyPodEvictionPolicy to AlwaysAllow. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/#unhealthy-pod-eviction-policy for more information." 4 | scope: 5 | objectKinds: 6 | - PodDisruptionBudget 7 | template: "pdb-unhealthy-pod-eviction-policy" 8 | -------------------------------------------------------------------------------- /pkg/command/root/command_test.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | 8 | "github.com/fatih/color" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestCommand(t *testing.T) { 14 | c := Command() 15 | c.SetOut(io.Discard) 16 | c.SetArgs([]string{ 17 | "version", 18 | fmt.Sprintf("--%s", colorFlag), 19 | }) 20 | err := c.Execute() 21 | require.NoError(t, err) 22 | assert.False(t, color.NoColor) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/objectkinds/cronjob.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | batchV1 "k8s.io/api/batch/v1" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | const ( 10 | // CronJob represents Kubernetes CronJob objects. 11 | CronJob = "CronJob" 12 | ) 13 | 14 | var ( 15 | cronJobGVK = batchV1.SchemeGroupVersion.WithKind("CronJob") 16 | ) 17 | 18 | func init() { 19 | RegisterObjectKind(CronJob, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 20 | return gvk == cronJobGVK 21 | })) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/complex-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | service.beta.kubernetes.io/aws-load-balancer-type: nlb 6 | labels: 7 | app: web 8 | name: complex-service 9 | spec: 10 | ports: 11 | - port: 443 12 | targetPort: 8443 13 | protocol: TCP 14 | name: https 15 | - name: http 16 | port: 80 17 | protocol: TCP 18 | targetPort: 8080 19 | selector: 20 | app: web 21 | type: LoadBalancer 22 | -------------------------------------------------------------------------------- /internal/stringutils/default.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | // OrDefault returns the string if it's not empty, or the default. 4 | func OrDefault(s, defaultValue string) string { 5 | if s != "" { 6 | return s 7 | } 8 | return defaultValue 9 | } 10 | 11 | // PointerOrDefault returns the string if it's not nil nor empty, or the default. 12 | func PointerOrDefault(s *string, defaultValue string) string { 13 | if s == nil { 14 | return defaultValue 15 | } 16 | 17 | return OrDefault(*s, defaultValue) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/objectkinds/daemonset.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | appsV1 "k8s.io/api/apps/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // DaemonSet represents Kubernetes DaemonSet objects. 10 | DaemonSet = "DaemonSet" 11 | ) 12 | 13 | var ( 14 | daemonSetGVK = appsV1.SchemeGroupVersion.WithKind("DaemonSet") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(DaemonSet, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == daemonSetGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /tests/checks/unset-memory-requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | limit: 13 | cpu: 1 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: app2 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | limit: 26 | cpu: 1 -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dnsconfig-options.yaml: -------------------------------------------------------------------------------- 1 | name: "dnsconfig-options" 2 | description: "Alert on deployments that have no specified dnsConfig options" 3 | remediation: >- 4 | Specify dnsconfig options in your Pod specification to ensure the expected DNS setting on the Pod. 5 | Refer to https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "dnsconfig-options" 10 | params: 11 | Key: ndots 12 | Value: "2" 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/env-var-secret.yaml: -------------------------------------------------------------------------------- 1 | name: "env-var-secret" 2 | description: "Indicates when objects use a secret in an environment variable." 3 | remediation: >- 4 | Do not use raw secrets in environment variables. Instead, either mount the secret as a file or use a secretKeyRef. 5 | Refer to https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "env-var" 10 | params: 11 | name: "(?i).*secret.*" 12 | value: ".+" 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/pdbs-max-unavailable.yaml: -------------------------------------------------------------------------------- 1 | name: "pdb-max-unavailable" 2 | description: "Indicates when a PodDisruptionBudget has a maxUnavailable value that will always prevent disruptions of pods created by related deployment-like objects." 3 | remediation: "Change the PodDisruptionBudget to have maxUnavailable set to a value greater than 0. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information." 4 | scope: 5 | objectKinds: 6 | - PodDisruptionBudget 7 | template: "pdb-max-unavailable" 8 | -------------------------------------------------------------------------------- /pkg/objectkinds/deployment.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | appsV1 "k8s.io/api/apps/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Deployment represents Kubernetes Deployment objects. 10 | Deployment = "Deployment" 11 | ) 12 | 13 | var ( 14 | deploymentGVK = appsV1.SchemeGroupVersion.WithKind("Deployment") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(Deployment, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == deploymentGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/objectkinds/replicaset.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | appsV1 "k8s.io/api/apps/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // ReplicaSet represents Kubernetes ReplicaSet objects. 10 | ReplicaSet = "ReplicaSet" 11 | ) 12 | 13 | var ( 14 | replicaSetGVK = appsV1.SchemeGroupVersion.WithKind("ReplicaSet") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(ReplicaSet, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == replicaSetGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /tests/checks/unset-cpu-requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | requests: 13 | memory: 1Gi 14 | 15 | --- 16 | apiVersion: apps.openshift.io/v1 17 | kind: DeploymentConfig 18 | metadata: 19 | name: app2 20 | spec: 21 | replicas: 3 22 | template: 23 | spec: 24 | containers: 25 | - name: app 26 | requests: 27 | memory: 1Gi -------------------------------------------------------------------------------- /pkg/command/lint/testdata/valid-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: homebrew-demo 5 | spec: 6 | securityContext: 7 | runAsUser: 1000 8 | runAsGroup: 3000 9 | fsGroup: 2000 10 | containers: 11 | - name: homebrew-test 12 | image: busybox:stable 13 | resources: 14 | limits: 15 | memory: "128Mi" 16 | cpu: "500m" 17 | requests: 18 | memory: "64Mi" 19 | cpu: "250m" 20 | securityContext: 21 | readOnlyRootFilesystem: true -------------------------------------------------------------------------------- /pkg/objectkinds/statefulset.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | appsV1 "k8s.io/api/apps/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // StatefulSet represents Kubernetes StatefulSet objects. 10 | StatefulSet = "StatefulSet" 11 | ) 12 | 13 | var ( 14 | statefulSetGVK = appsV1.SchemeGroupVersion.WithKind("StatefulSet") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(StatefulSet, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == statefulSetGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /tests/checks/minimum-three-replicas.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | --- 9 | apiVersion: apps.openshift.io/v1 10 | kind: DeploymentConfig 11 | metadata: 12 | name: dont-fire 13 | spec: 14 | replicas: 3 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: app 20 | spec: 21 | replicas: 2 22 | --- 23 | apiVersion: apps.openshift.io/v1 24 | kind: DeploymentConfig 25 | metadata: 26 | name: app 27 | spec: 28 | replicas: 2 -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/dangling-networkpolicypeer-podselector.yaml: -------------------------------------------------------------------------------- 1 | name: "dangling-networkpolicypeer-podselector" 2 | description: "Indicates when NetworkPolicyPeer in Egress/Ingress rules -in the Spec of NetworkPolicy- do not have any associated deployments. Applied on peer specified with podSelectors only." 3 | remediation: "Confirm that your NetworkPolicy's Ingress/Egress peer's podselector correctly matches the labels on one of your deployments." 4 | scope: 5 | objectKinds: 6 | - NetworkPolicy 7 | template: "dangling-networkpolicypeer-podselector" -------------------------------------------------------------------------------- /pkg/extract/job_spec.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/k8sutil" 5 | batchV1 "k8s.io/api/batch/v1" 6 | ) 7 | 8 | // JobSpec extracts a job template spec from Job or CronJob objects 9 | func JobSpec(obj k8sutil.Object) (batchV1.JobSpec, string, bool) { 10 | switch obj := obj.(type) { 11 | case *batchV1.Job: 12 | return obj.Spec, "Job", true 13 | case *batchV1.CronJob: 14 | return obj.Spec.JobTemplate.Spec, "CronJob", true 15 | default: 16 | return batchV1.JobSpec{}, "", false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /e2etests/bats-support-clone.bash: -------------------------------------------------------------------------------- 1 | if [[ ! -d "e2etests/test_helper/bats-support" ]]; then 2 | # Download bats-support dynamically so it doesnt need to be added into source 3 | git clone https://github.com/ztombol/bats-support e2etests/test_helper/bats-support --depth 1 4 | fi 5 | 6 | if [[ ! -d "e2etests/test_helper/redhatcop-bats-library" ]]; then 7 | # Download redhat-cop/bats-library dynamically so it doesnt need to be added into source 8 | git clone https://github.com/redhat-cop/bats-library e2etests/test_helper/redhatcop-bats-library --depth 1 9 | fi -------------------------------------------------------------------------------- /internal/stringutils/split.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Split2 splits the given string at the given separator, returning the part before and after the separator as two 8 | // separate return values. 9 | // If the string does not contain `sep`, the entire string is returned as the first return value. 10 | func Split2(str, sep string) (string, string) { 11 | splitIdx := strings.Index(str, sep) 12 | if splitIdx == -1 { 13 | return str, "" 14 | } 15 | return str[:splitIdx], str[splitIdx+len(sep):] 16 | } 17 | -------------------------------------------------------------------------------- /pkg/objectkinds/serviceaccount.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // ServiceAccount represents Kubernetes ServiceAccount objects. 10 | ServiceAccount = "ServiceAccount" 11 | ) 12 | 13 | var ( 14 | serviceAccountGVK = v1.SchemeGroupVersion.WithKind("ServiceAccount") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(ServiceAccount, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == serviceAccountGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/templates/util/json.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "golang.stackrox.io/kube-linter/pkg/check" 8 | ) 9 | 10 | // MustParseParameterDesc unmarshals the given JSON into a templates.ParameterDesc. 11 | func MustParseParameterDesc(asJSON string) check.ParameterDesc { 12 | var out check.ParameterDesc 13 | 14 | decoder := json.NewDecoder(strings.NewReader(asJSON)) 15 | decoder.DisallowUnknownFields() 16 | if err := decoder.Decode(&out); err != nil { 17 | panic(err) 18 | } 19 | return out 20 | } 21 | -------------------------------------------------------------------------------- /tests/checks/unsafe-proc-mount.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | securityContext: 13 | procMount: Unmasked 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: app2 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | securityContext: 26 | procMount: Unmasked -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/drop-net-raw-capability.yaml: -------------------------------------------------------------------------------- 1 | name: "drop-net-raw-capability" 2 | description: "Indicates when containers do not drop NET_RAW capability" 3 | remediation: >- 4 | NET_RAW makes it so that an application within the container is able to craft raw packets, 5 | use raw sockets, and bind to any address. Remove this capability in the containers under 6 | containers security contexts. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "verify-container-capabilities" 11 | params: 12 | forbiddenCapabilities: ["NET_RAW"] 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/unset-cpu-requirements.yaml: -------------------------------------------------------------------------------- 1 | name: "unset-cpu-requirements" 2 | description: "Indicates when containers do not have CPU requests and limits set." 3 | scope: 4 | objectKinds: 5 | - DeploymentLike 6 | remediation: >- 7 | Set CPU requests for your container based on its requirements. 8 | Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details. 9 | template: "cpu-requirements" 10 | params: 11 | requirementsType: "request" 12 | lowerBoundMillis: 0 13 | upperBoundMillis: 0 14 | -------------------------------------------------------------------------------- /pkg/templates/util/map_structure.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/mitchellh/mapstructure" 5 | ) 6 | 7 | // DecodeMapStructure decodes the given map[string]interface{} into the given out variable, typically 8 | // a pointer to a struct. 9 | func DecodeMapStructure(m map[string]interface{}, out interface{}) error { 10 | dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 11 | ErrorUnused: true, 12 | TagName: "json", 13 | Result: out, 14 | }) 15 | if err != nil { 16 | return err 17 | } 18 | return dec.Decode(m) 19 | } 20 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: "✔️ New checks" 4 | labels: 5 | - "new-check" 6 | - title: "🚀 Features" 7 | labels: 8 | - "feature" 9 | - "enhancement" 10 | - title: "🐛 Bug Fixes" 11 | labels: 12 | - "fix" 13 | - "bugfix" 14 | - "bug" 15 | - title: "🧰 Maintenance" 16 | labels: 17 | - "chore" 18 | - title: Other Changes 19 | labels: 20 | - '*' 21 | - title: '⬆️ Dependencies' 22 | labels: 23 | - 'dependencies' 24 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-rolling-update-strategy.yaml: -------------------------------------------------------------------------------- 1 | name: "no-rolling-update-strategy" 2 | description: "Indicates when a deployment doesn't use a rolling update strategy" 3 | remediation: >- 4 | Use a rolling update strategy to avoid service disruption during an update. 5 | A rolling update strategy allows for pods to be systematicaly replaced in a 6 | controlled fashion to ensure no service disruption. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "update-configuration" 11 | params: 12 | strategyTypeRegex: "^(RollingUpdate|Rolling)$" 13 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/unset-memory-requirements.yaml: -------------------------------------------------------------------------------- 1 | name: "unset-memory-requirements" 2 | description: "Indicates when containers do not have memory requests and limits set." 3 | remediation: >- 4 | Set memory limits for your container based on its requirements. 5 | Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details. 6 | scope: 7 | objectKinds: 8 | - DeploymentLike 9 | template: "memory-requirements" 10 | params: 11 | requirementsType: "limit" 12 | lowerBoundMB: 0 13 | upperBoundMB: 0 14 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/pdbs-min-available.yaml: -------------------------------------------------------------------------------- 1 | name: "pdb-min-available" 2 | description: "Indicates when a PodDisruptionBudget sets a minAvailable value that will always prevent disruptions of pods created by related deployment-like objects." 3 | remediation: "Change the PodDisruptionBudget to have minAvailable set to a number lower than the number of replicas in the related deployment-like objects. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information." 4 | scope: 5 | objectKinds: 6 | - PodDisruptionBudget 7 | template: "pdb-min-available" 8 | -------------------------------------------------------------------------------- /pkg/diagnostic/diagnostic.go: -------------------------------------------------------------------------------- 1 | package diagnostic 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/lintcontext" 5 | ) 6 | 7 | // A Diagnostic represents one specific problem diagnosed by a check. 8 | type Diagnostic struct { 9 | Message string 10 | 11 | // TODO: add line number/col number 12 | } 13 | 14 | // WithContext puts a diagnostic in the context of which check emitted it, 15 | // and which object it applied to. 16 | type WithContext struct { 17 | Diagnostic Diagnostic 18 | Check string 19 | Remediation string 20 | Object lintcontext.Object 21 | } 22 | -------------------------------------------------------------------------------- /tests/checks/unsafe-sysctls.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | securityContext: 13 | sysctls: 14 | - name: kernel.sem 15 | --- 16 | apiVersion: apps.openshift.io/v1 17 | kind: DeploymentConfig 18 | metadata: 19 | name: app2 20 | spec: 21 | replicas: 3 22 | template: 23 | spec: 24 | containers: 25 | - name: app 26 | securityContext: 27 | sysctls: 28 | - name: kernel.sem -------------------------------------------------------------------------------- /pkg/objectkinds/deploymentconfig.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | ocsAppsV1 "github.com/openshift/api/apps/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // DeploymentConfig represents OpenShift DeploymentConfig objects. 10 | DeploymentConfig = "DeploymentConfig" 11 | ) 12 | 13 | var ( 14 | deploymentConfigGVK = ocsAppsV1.SchemeGroupVersion.WithKind("DeploymentConfig") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(DeploymentConfig, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == deploymentConfigGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/privilege-escalation.yaml: -------------------------------------------------------------------------------- 1 | name: "privilege-escalation-container" 2 | description: "Alert on containers of allowing privilege escalation that could gain more privileges than its parent process." 3 | remediation: >- 4 | Ensure containers do not allow privilege escalation by setting 5 | allowPrivilegeEscalation=false, privileged=false and removing CAP_SYS_ADMIN capability. 6 | See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "privilege-escalation-container" 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/unsafe-proc-mount.yaml: -------------------------------------------------------------------------------- 1 | name: "unsafe-proc-mount" 2 | description: "Alert on deployments with unsafe /proc mount (procMount=Unmasked) that will bypass the default masking behavior of the container runtime" 3 | remediation: >- 4 | Ensure container does not unsafely exposes parts of /proc by setting procMount=Default. 5 | Unmasked ProcMount bypasses the default masking behavior of the container runtime. 6 | See https://kubernetes.io/docs/concepts/security/pod-security-standards/ for more details. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "unsafe-proc-mount" 11 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/read-secret-from-env-var.yaml: -------------------------------------------------------------------------------- 1 | name: "read-secret-from-env-var" 2 | description: >- 3 | Indicates when a deployment reads secret from environment variables. 4 | CIS Benchmark 5.4.1: "Prefer using secrets as files over secrets as environment variables. " 5 | remediation: >- 6 | If possible, rewrite application code to read secrets from mounted secret files, rather than from environment variables. 7 | Refer to https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets for details. 8 | scope: 9 | objectKinds: 10 | - DeploymentLike 11 | template: "read-secret-from-env-var" 12 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/wildcard-use-in-rules.yaml: -------------------------------------------------------------------------------- 1 | name: "wildcard-in-rules" 2 | description: >- 3 | Indicate when a wildcard is used in Role or ClusterRole rules. 4 | CIS Benchmark 5.1.3 Use of wildcards is not optimal from a security perspective as it may allow for inadvertent access to be granted when new resources are added to the Kubernetes API either as CRDs or in later versions of the product. 5 | remediation: "Where possible replace any use of wildcards in clusterroles and roles with specific objects or actions." 6 | scope: 7 | objectKinds: 8 | - ClusterRole 9 | - Role 10 | template: "wildcard-in-rules" 11 | -------------------------------------------------------------------------------- /pkg/objectkinds/replicationcontroller.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | coreV1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // ReplicationController represents Kubernetes ReplicationController objects. 10 | ReplicationController = "ReplicationController" 11 | ) 12 | 13 | var ( 14 | replicationControllerGVK = coreV1.SchemeGroupVersion.WithKind("ReplicationController") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(ReplicationController, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == replicationControllerGVK 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/templates/containercapabilities/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // List of capabilities that needs to be removed from containers. 7 | // +noregex 8 | // +notnegatable 9 | ForbiddenCapabilities []string `json:"forbiddenCapabilities"` 10 | 11 | // List of capabilities that are exceptions to the above list. This should only be filled 12 | // when the above contains "all", and is used to forgive capabilities in ADD list. 13 | // +noregex 14 | // +notnegatable 15 | Exceptions []string `json:"exceptions"` 16 | } 17 | -------------------------------------------------------------------------------- /tests/checks/required-label-owner.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | labels: 7 | owner: kubelinter@stackrox.com 8 | --- 9 | apiVersion: apps.openshift.io/v1 10 | kind: DeploymentConfig 11 | metadata: 12 | name: dont-fire 13 | labels: 14 | owner: kubelinter@stackrox.com 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: app1 20 | labels: 21 | dev: kubelinter@stackrox.com 22 | --- 23 | apiVersion: apps.openshift.io/v1 24 | kind: DeploymentConfig 25 | metadata: 26 | name: app2 27 | labels: 28 | dev: kubelinter@stackrox.com -------------------------------------------------------------------------------- /tests/checks/non-existent-service-account.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dont-fire 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: dont-fire 11 | spec: 12 | template: 13 | spec: 14 | serviceAccount: dont-fire 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: app 20 | spec: 21 | template: 22 | spec: 23 | serviceAccount: missing 24 | --- 25 | apiVersion: apps.openshift.io/v1 26 | kind: DeploymentConfig 27 | metadata: 28 | name: app 29 | spec: 30 | template: 31 | spec: 32 | serviceAccount: missing -------------------------------------------------------------------------------- /pkg/builtinchecks/built_in_checks_test.go: -------------------------------------------------------------------------------- 1 | package builtinchecks 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestBuiltInChecksWellFormed(t *testing.T) { 12 | checks, err := List() 13 | require.NoError(t, err) 14 | for _, check := range checks { 15 | t.Run(check.Name, func(t *testing.T) { 16 | assert.NotEmpty(t, check.Remediation, "Please add remediation") 17 | assert.True(t, strings.HasSuffix(check.Remediation, "."), "Please end your remediation texts with a period (got %q)", check.Remediation) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/objectkinds/pvc.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | PersistentVolumeClaim = "PersistentVolumeClaim" 10 | ) 11 | 12 | var ( 13 | persistentvolumeclaimGVK = v1.SchemeGroupVersion.WithKind("PersistentVolumeClaim") 14 | ) 15 | 16 | func init() { 17 | RegisterObjectKind(PersistentVolumeClaim, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 18 | return gvk == persistentvolumeclaimGVK 19 | })) 20 | } 21 | 22 | func GetPersistentVolumeClaimAPIVersion() string { 23 | return persistentvolumeclaimGVK.GroupVersion().String() 24 | } 25 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/docker-sock.yaml: -------------------------------------------------------------------------------- 1 | name: "docker-sock" 2 | description: "Alert on deployments with docker.sock mounted in containers. " 3 | remediation: >- 4 | Ensure the Docker socket is not mounted inside any containers by removing the associated 5 | Volume and VolumeMount in deployment yaml specification. 6 | If the Docker socket is mounted inside a container it could allow processes running within 7 | the container to execute Docker commands which would effectively allow for full control of the host. 8 | 9 | scope: 10 | objectKinds: 11 | - DeploymentLike 12 | template: "host-mounts" 13 | params: 14 | dirs: ["docker.sock$"] 15 | -------------------------------------------------------------------------------- /pkg/objectkinds/ingress.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | v1 "k8s.io/api/networking/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Ingress represents Kubernetes Ingress objects. 10 | Ingress = "Ingress" 11 | ) 12 | 13 | var ( 14 | ingressGVK = v1.SchemeGroupVersion.WithKind(Ingress) 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(Ingress, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == ingressGVK 20 | })) 21 | } 22 | 23 | // GetIngressAPIVersion returns Ingress's apiversion 24 | func GetIngressAPIVersion() string { 25 | return ingressGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /tests/checks/hpa-minimum-three-replicas.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | minReplicas: 3 8 | maxReplicas: 100 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: testing 13 | targetCPUUtilizationPercentage: 85 14 | --- 15 | apiVersion: autoscaling/v2beta1 16 | kind: HorizontalPodAutoscaler 17 | metadata: 18 | name: app 19 | spec: 20 | minReplicas: 2 21 | maxReplicas: 100 22 | scaleTargetRef: 23 | apiVersion: apps/v1 24 | kind: Deployment 25 | name: testing 26 | targetCPUUtilizationPercentage: 85 27 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/access-to-secrets.yaml: -------------------------------------------------------------------------------- 1 | name: "access-to-secrets" 2 | description: >- 3 | Indicates when a subject (Group/User/ServiceAccount) has access to Secrets. 4 | CIS Benchmark 5.1.2: Access to secrets should be restricted to the smallest possible group of users to reduce the risk of privilege escalation. 5 | remediation: "Where possible, remove get, list and watch access to secret objects in the cluster." 6 | scope: 7 | objectKinds: 8 | - ClusterRoleBinding 9 | - RoleBinding 10 | template: "access-to-resources" 11 | params: 12 | resources: ["^secrets$"] 13 | verbs: ["^get$", "^list$", "^delete$", "^create$", "^watch$", "^*$"] 14 | -------------------------------------------------------------------------------- /tests/checks/required-annotation-email.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | annotations: 7 | email: kubelinter@stackrox.com 8 | --- 9 | apiVersion: apps.openshift.io/v1 10 | kind: DeploymentConfig 11 | metadata: 12 | name: dont-fire 13 | annotations: 14 | email: kubelinter@stackrox.com 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: app1 20 | annotations: 21 | whois: kubelinter@stackrox.com 22 | --- 23 | apiVersion: apps.openshift.io/v1 24 | kind: DeploymentConfig 25 | metadata: 26 | name: app2 27 | annotations: 28 | whois: kubelinter@stackrox.com -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE_REQUEST]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description of the problem/feature request** 11 | A clear and concise description of the problem, or the proposed new feature. 12 | 13 | **Description of the existing behavior vs. expected behavior** 14 | If applicable, please paste in the existing KubeLinter output along with the input used, and point out which part should be modified (expected output). 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /pkg/config/check.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // A Check represents a single check. It is serializable. 4 | type Check struct { 5 | Name string `json:"name"` 6 | Description string `json:"description"` 7 | Remediation string `json:"remediation"` 8 | Scope *ObjectKindsDesc `json:"scope"` 9 | Template string `json:"template"` 10 | Params map[string]interface{} `json:"params,omitempty"` 11 | } 12 | 13 | // ObjectKindsDesc describes a list of supported object kinds for a check template. 14 | type ObjectKindsDesc struct { 15 | ObjectKinds []string `json:"objectKinds"` 16 | } 17 | -------------------------------------------------------------------------------- /pkg/templates/accesstoresources/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // Set to true to flag the roles that are referenced in bindings but not found in the context 6 | FlagRolesNotFound bool `json:"flagRolesNotFound"` 7 | // An array of regular expressions specifying resources. e.g. ^secrets$ for secrets and ^*$ for any resources 8 | // +notnegatable 9 | Resources []string `json:"resources"` 10 | // An array of regular expressions specifying verbs. e.g. ^create$ for create and ^*$ for any k8s verbs 11 | // +notnegatable 12 | Verbs []string `json:"verbs"` 13 | } 14 | -------------------------------------------------------------------------------- /e2etests/check-bats-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | run() { 4 | local tmp_write_dir=/tmp/kubelinter/$(date +'%d-%m-%Y-%H-%M') 5 | mkdir -p "${tmp_write_dir}" 6 | 7 | grep "@test" e2etests/bats-tests.sh | grep -v 'flag-' | grep -v 'template-' | cut -d'"' -f2 > ${tmp_write_dir}/batstests.log 8 | ${KUBE_LINTER_BIN:-kube-linter} checks list --format json | jq -r '.[].name' > ${tmp_write_dir}/kubelinterchecks.log 9 | diff -c ${tmp_write_dir}/kubelinterchecks.log ${tmp_write_dir}/batstests.log || { echo >&2 "ERROR: The output of '${KUBE_LINTER_BIN} checks list' differs from the tests in 'e2etests/bats-tests.sh'. See above diff."; exit 1; } 10 | } 11 | 12 | run 13 | -------------------------------------------------------------------------------- /tests/checks/host-ipc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | hostIPC: false 10 | --- 11 | apiVersion: apps.openshift.io/v1 12 | kind: DeploymentConfig 13 | metadata: 14 | name: dont-fire 15 | spec: 16 | template: 17 | spec: 18 | hostIPC: false 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: app 24 | spec: 25 | template: 26 | spec: 27 | hostIPC: true 28 | --- 29 | apiVersion: apps.openshift.io/v1 30 | kind: DeploymentConfig 31 | metadata: 32 | name: app 33 | spec: 34 | template: 35 | spec: 36 | hostIPC: true -------------------------------------------------------------------------------- /tests/checks/host-pid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | hostPID: false 10 | --- 11 | apiVersion: apps.openshift.io/v1 12 | kind: DeploymentConfig 13 | metadata: 14 | name: dont-fire 15 | spec: 16 | template: 17 | spec: 18 | hostPID: false 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: app 24 | spec: 25 | template: 26 | spec: 27 | hostPID: true 28 | --- 29 | apiVersion: apps.openshift.io/v1 30 | kind: DeploymentConfig 31 | metadata: 32 | name: app 33 | spec: 34 | template: 35 | spec: 36 | hostPID: true -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/scc-deny-privileged-container.yaml: -------------------------------------------------------------------------------- 1 | name: "scc-deny-privileged-container" 2 | description: "Indicates when allowPrivilegedContainer SecurityContextConstraints set to true" 3 | remediation: >- 4 | SecurityContextConstraints has AllowPrivilegedContainer set to "true". Using this option is dangerous, please consider using allowedCapabilities instead. Refer to https://docs.openshift.com/container-platform/4.12/authentication/managing-security-context-constraints.html#scc-settings_configuring-internal-oauth for details. 5 | scope: 6 | objectKinds: 7 | - SecurityContextConstraints 8 | template: "scc-deny-privileged-container" 9 | params: 10 | AllowPrivilegedContainer: true -------------------------------------------------------------------------------- /tests/checks/host-network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | hostNetwork: false 10 | --- 11 | apiVersion: apps.openshift.io/v1 12 | kind: DeploymentConfig 13 | metadata: 14 | name: dont-fire 15 | spec: 16 | template: 17 | spec: 18 | hostNetwork: false 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: app 24 | spec: 25 | template: 26 | spec: 27 | hostNetwork: true 28 | --- 29 | apiVersion: apps.openshift.io/v1 30 | kind: DeploymentConfig 31 | metadata: 32 | name: app 33 | spec: 34 | template: 35 | spec: 36 | hostNetwork: true -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/access-to-create-pods.yaml: -------------------------------------------------------------------------------- 1 | name: "access-to-create-pods" 2 | description: >- 3 | Indicates when a subject (Group/User/ServiceAccount) has create access to Pods. 4 | CIS Benchmark 5.1.4: The ability to create pods in a cluster opens up possibilities for privilege escalation and should be restricted, where possible. 5 | remediation: "Where possible, remove create access to pod objects in the cluster." 6 | scope: 7 | objectKinds: 8 | - ClusterRoleBinding 9 | - RoleBinding 10 | template: "access-to-resources" 11 | params: 12 | resources: ["^pods$", "^deployments$", "^statefulsets$", "^replicasets$", "^cronjob$", "^jobs$","^daemonsets$"] 13 | verbs: ["^create$"] 14 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/invalid-target-ports.yaml: -------------------------------------------------------------------------------- 1 | name: "invalid-target-ports" 2 | description: "Indicates when deployments or services are using port names that are violating specifications." 3 | remediation: >- 4 | Ensure that port naming is in conjunction with the specification. For more information, 5 | please look at the Kubernetes Service specification on this page: 6 | https://kubernetes.io/docs/reference/_print/#ServiceSpec. And additional information 7 | about IANA Service naming can be found on the following page: 8 | https://www.rfc-editor.org/rfc/rfc6335.html#section-5.1. 9 | scope: 10 | objectKinds: 11 | - DeploymentLike 12 | - Service 13 | template: "target-port" 14 | -------------------------------------------------------------------------------- /pkg/objectkinds/role.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | rbacV1 "k8s.io/api/rbac/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Role represents Kubernetes Role objects. Case sensitive. 10 | Role = "Role" 11 | ) 12 | 13 | var ( 14 | // roleGVK represents Kubernetes Role objects. Case sensitive. 15 | roleGVK = rbacV1.SchemeGroupVersion.WithKind("Role") 16 | ) 17 | 18 | func init() { 19 | RegisterObjectKind(Role, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 20 | return gvk == roleGVK 21 | })) 22 | } 23 | 24 | // GetRoleAPIVersion returns Role's APIVersion 25 | func GetRoleAPIVersion() string { 26 | return roleGVK.GroupVersion().String() 27 | } 28 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/container.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | appsV1 "k8s.io/api/apps/v1" 8 | v1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | // AddContainerToDeployment adds a mock container to the specified pod under context 12 | func (l *MockLintContext) AddContainerToDeployment(t *testing.T, deploymentName string, container v1.Container) { 13 | deployment, ok := l.objects[deploymentName].(*appsV1.Deployment) 14 | require.True(t, ok, "deployment with name %s not found", deploymentName) 15 | // TODO: keep supporting other fields 16 | deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, container) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/templates/memoryrequirements/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The type of requirement. Use any to apply to both requests and limits. 7 | // +enum=request 8 | // +enum=limit 9 | // +enum=any 10 | // +required 11 | RequirementsType string 12 | 13 | // The lower bound of the requirement (inclusive), specified as 14 | // a number of MB. 15 | LowerBoundMB int `json:"lowerBoundMB"` 16 | 17 | // The upper bound of the requirement (inclusive), specified as 18 | // a number of MB. 19 | // If not specified, it is treated as "no upper bound". 20 | UpperBoundMB *int `json:"upperBoundMB"` 21 | } 22 | -------------------------------------------------------------------------------- /internal/defaultchecks/default_test.go: -------------------------------------------------------------------------------- 1 | package defaultchecks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "golang.stackrox.io/kube-linter/internal/set" 9 | "golang.stackrox.io/kube-linter/pkg/builtinchecks" 10 | ) 11 | 12 | func TestListReferencesOnlyValidChecks(t *testing.T) { 13 | allChecks, err := builtinchecks.List() 14 | require.NoError(t, err) 15 | allCheckNames := set.NewStringSet() 16 | for _, check := range allChecks { 17 | allCheckNames.Add(check.Name) 18 | } 19 | for _, defaultCheck := range List.AsSlice() { 20 | assert.True(t, allCheckNames.Contains(defaultCheck), "default check %s invalid", defaultCheck) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/objectkinds/rolebinding.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | rbacV1 "k8s.io/api/rbac/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // RoleBinding represents Kubernetes RoleBinding objects. Case sensitive. 10 | RoleBinding = "RoleBinding" 11 | ) 12 | 13 | var ( 14 | roleBindingGVK = rbacV1.SchemeGroupVersion.WithKind("RoleBinding") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(RoleBinding, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == roleBindingGVK 20 | })) 21 | } 22 | 23 | // GetRoleBindingAPIVersion returns RoleBinding's APIVersion 24 | func GetRoleBindingAPIVersion() string { 25 | return roleBindingGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/sysctls.yaml: -------------------------------------------------------------------------------- 1 | name: "unsafe-sysctls" 2 | description: "Alert on deployments specifying unsafe sysctls that may lead to severe problems like wrong behavior of containers" 3 | remediation: >- 4 | Ensure container does not allow unsafe allocation of system resources by removing unsafe sysctls configurations. 5 | For more details see https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ 6 | https://docs.docker.com/engine/reference/commandline/run/#configure-namespaced-kernel-parameters-sysctls-at-runtime. 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "unsafe-sysctls" 11 | params: 12 | unsafeSysCtls: ["kernel.msg", "kernel.sem", "kernel.shm", "fs.mqueue.", "net."] 13 | -------------------------------------------------------------------------------- /tests/checks/pdb-unhealthy-pod-eviction-policy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: fire 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: app1 10 | maxUnavailable: 1 11 | --- 12 | apiVersion: policy/v1 13 | kind: PodDisruptionBudget 14 | metadata: 15 | name: dont-fire-1 16 | spec: 17 | selector: 18 | matchLabels: 19 | app: app2 20 | maxUnavailable: 1 21 | unhealthyPodEvictionPolicy: AlwaysAllow 22 | --- 23 | apiVersion: policy/v1 24 | kind: PodDisruptionBudget 25 | metadata: 26 | name: dont-fire-2 27 | spec: 28 | selector: 29 | matchLabels: 30 | app: app2 31 | maxUnavailable: 1 32 | unhealthyPodEvictionPolicy: IfHealthyBudget 33 | -------------------------------------------------------------------------------- /pkg/objectkinds/networkpolicy.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | v1 "k8s.io/api/networking/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // NetworkPolicy represents Kubernetes NetworkPolicy objects. 10 | NetworkPolicy = "NetworkPolicy" 11 | ) 12 | 13 | var ( 14 | networkpolicyGVK = v1.SchemeGroupVersion.WithKind("NetworkPolicy") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(NetworkPolicy, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == networkpolicyGVK 20 | })) 21 | } 22 | 23 | // GetNetworkPolicyAPIVersion returns networkpolicy's apiversion 24 | func GetNetworkPolicyAPIVersion() string { 25 | return networkpolicyGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/multi-container.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: multi-container 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app: multi 10 | spec: 11 | containers: 12 | - name: nginx 13 | image: nginx:latest 14 | ports: 15 | - protocol: TCP 16 | containerPort: 80 17 | name: http 18 | - name: sidecar 19 | image: sidecar:latest 20 | volumeMounts: 21 | - name: data 22 | mountPath: /data 23 | - image: logger:latest 24 | name: logger 25 | volumes: 26 | - name: data 27 | emptyDir: {} 28 | -------------------------------------------------------------------------------- /tests/checks/default-service-account.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | serviceAccountName: app 10 | --- 11 | apiVersion: apps.openshift.io/v1 12 | kind: DeploymentConfig 13 | metadata: 14 | name: dont-fire 15 | spec: 16 | template: 17 | spec: 18 | serviceAccountName: app 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: app 24 | spec: 25 | template: 26 | spec: 27 | serviceAccountName: default 28 | --- 29 | apiVersion: apps.openshift.io/v1 30 | kind: DeploymentConfig 31 | metadata: 32 | name: app 33 | spec: 34 | template: 35 | spec: 36 | serviceAccountName: default -------------------------------------------------------------------------------- /pkg/objectkinds/securitycontext.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | ocpSecV1 "github.com/openshift/api/security/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // Service represents Kubernetes Service objects. 10 | SecurityContextConstraints = "SecurityContextConstraints" 11 | ) 12 | 13 | var ( 14 | sccGVK = ocpSecV1.SchemeGroupVersion.WithKind("SecurityContextConstraints") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(SecurityContextConstraints, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == sccGVK 20 | })) 21 | } 22 | 23 | // GetSCCAPIVersion returns SCC's apiversion 24 | func GetSCCAPIVersion() string { 25 | return sccGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/job.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | batchV1 "k8s.io/api/batch/v1" 8 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | // AddMockJob adds a mock Job to LintContext 12 | func (l *MockLintContext) AddMockJob(t *testing.T, name string) { 13 | require.NotEmpty(t, name) 14 | l.objects[name] = &batchV1.Job{ 15 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 16 | } 17 | } 18 | 19 | // ModifyJob modifies a given Job in the context via the passed function 20 | func (l *MockLintContext) ModifyJob(t *testing.T, name string, f func(job *batchV1.Job)) { 21 | dep, ok := l.objects[name].(*batchV1.Job) 22 | require.True(t, ok) 23 | f(dep) 24 | } 25 | -------------------------------------------------------------------------------- /tests/checks/deprecated-service-account-field.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | serviceAccountName: default 10 | --- 11 | apiVersion: apps.openshift.io/v1 12 | kind: DeploymentConfig 13 | metadata: 14 | name: dont-fire 15 | spec: 16 | template: 17 | spec: 18 | serviceAccountName: default 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: app 24 | spec: 25 | template: 26 | spec: 27 | serviceAccount: default 28 | --- 29 | apiVersion: apps.openshift.io/v1 30 | kind: DeploymentConfig 31 | metadata: 32 | name: app 33 | spec: 34 | template: 35 | spec: 36 | serviceAccount: default -------------------------------------------------------------------------------- /pkg/objectkinds/poddisruptionbudget.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | policyv1 "k8s.io/api/policy/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // PodDisruptionBudget represents Kubernetes PodDisruptionBudget objects. 10 | PodDisruptionBudget = "PodDisruptionBudget" 11 | ) 12 | 13 | var ( 14 | pdbGVK = policyv1.SchemeGroupVersion.WithKind("PodDisruptionBudget") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(PodDisruptionBudget, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == pdbGVK 20 | })) 21 | } 22 | 23 | // GetPodDisruptionBudgetAPIVersion returns pdb's apiversion 24 | func GetPodDisruptionBudgetAPIVersion() string { 25 | return pdbGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /tests/checks/cluster-admin-role-binding.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: dont-fire 6 | namespace: namespace-dev 7 | subjects: 8 | - kind: ServiceAccount 9 | name: account1 10 | namespace: namespace-dev 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: admin 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: role-binding1 20 | namespace: namespace-dev 21 | subjects: 22 | - kind: ServiceAccount 23 | name: account1 24 | namespace: namespace-dev 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: cluster-admin 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **System info:** 11 | - OS: [e.g. Linux? MaxOS? Windows?] 12 | 13 | **Describe the bug** 14 | A clear and concise description of the bug. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 19 | **Sample YAML input** 20 | If applicable, sample YAML input which reproduces the issue. 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /tests/checks/read-secret-from-env-var.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | env: 13 | - name: TOKEN 14 | valueFrom: 15 | secretKeyRef: 16 | name: my-secret 17 | key: token 18 | --- 19 | apiVersion: apps.openshift.io/v1 20 | kind: DeploymentConfig 21 | metadata: 22 | name: app2 23 | spec: 24 | replicas: 3 25 | template: 26 | spec: 27 | containers: 28 | - name: app 29 | env: 30 | - name: TOKEN 31 | valueFrom: 32 | secretKeyRef: 33 | name: my-secret 34 | key: token -------------------------------------------------------------------------------- /pkg/objectkinds/clusterrole.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "fmt" 5 | 6 | rbacV1 "k8s.io/api/rbac/v1" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | ) 9 | 10 | const ( 11 | // ClusterRole represents Kubernetes ClusterRole objects. Case sensitive. 12 | ClusterRole = "ClusterRole" 13 | ) 14 | 15 | var ( 16 | clusterRoleGVK = rbacV1.SchemeGroupVersion.WithKind("ClusterRole") 17 | ) 18 | 19 | func init() { 20 | RegisterObjectKind(ClusterRole, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 21 | return gvk == clusterRoleGVK 22 | })) 23 | } 24 | 25 | // GetClusterRoleAPIVersion returns ClusterRole's APIVersion 26 | func GetClusterRoleAPIVersion() string { 27 | return fmt.Sprintf("%s/%s", clusterRoleGVK.Group, clusterRoleGVK.Version) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/pathutil/path.go: -------------------------------------------------------------------------------- 1 | package pathutil 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/mitchellh/go-homedir" 8 | ) 9 | 10 | // GetAbsolutPath returns the absolute representation of given path. 11 | func GetAbsolutPath(path string) (string, error) { 12 | switch { 13 | case path[0] == '~': 14 | expandedPath, err := homedir.Expand(path) 15 | if err != nil { 16 | return "", fmt.Errorf("could not expand path: %q: %w", expandedPath, err) 17 | } 18 | return expandedPath, nil 19 | case !filepath.IsAbs(path): 20 | absPath, err := filepath.Abs(path) 21 | if err != nil { 22 | return "", fmt.Errorf("could not expand non-absolute path: %q: %w", absPath, err) 23 | } 24 | return absPath, nil 25 | default: 26 | return path, nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/service.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | coreV1 "k8s.io/api/core/v1" 8 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | // AddMockService adds a mock Service to LintContext 12 | func (l *MockLintContext) AddMockService(t *testing.T, name string) { 13 | require.NotEmpty(t, name) 14 | l.objects[name] = &coreV1.Service{ 15 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 16 | } 17 | } 18 | 19 | // ModifyService modifies a given service in the context via the passed function 20 | func (l *MockLintContext) ModifyService(t *testing.T, name string, f func(service *coreV1.Service)) { 21 | dep, ok := l.objects[name].(*coreV1.Service) 22 | require.True(t, ok) 23 | f(dep) 24 | } 25 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | open-pull-requests-limit: 3 6 | reviewers: 7 | - "janisz" 8 | schedule: 9 | interval: 'weekly' 10 | day: 'wednesday' 11 | - package-ecosystem: 'gomod' 12 | directory: '/' 13 | schedule: 14 | interval: 'weekly' 15 | day: 'wednesday' 16 | open-pull-requests-limit: 3 17 | reviewers: 18 | - "janisz" 19 | groups: 20 | k8s.io: 21 | patterns: 22 | - "k8s.io/*" 23 | - package-ecosystem: 'gomod' 24 | directory: 'tool-imports' 25 | schedule: 26 | interval: 'weekly' 27 | day: 'wednesday' 28 | open-pull-requests-limit: 3 29 | reviewers: 30 | - "janisz" 31 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/cronjob.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | batchV1 "k8s.io/api/batch/v1" 8 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | // AddMockCronJob adds a mock CronJob to LintContext 12 | func (l *MockLintContext) AddMockCronJob(t *testing.T, name string) { 13 | require.NotEmpty(t, name) 14 | l.objects[name] = &batchV1.CronJob{ 15 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 16 | } 17 | } 18 | 19 | // ModifyCronJob modifies a given CronJob in the context via the passed function 20 | func (l *MockLintContext) ModifyCronJob(t *testing.T, name string, f func(cronjob *batchV1.CronJob)) { 21 | dep, ok := l.objects[name].(*batchV1.CronJob) 22 | require.True(t, ok) 23 | f(dep) 24 | } 25 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: kube-linter 2 | name: KubeLinter 3 | description: This hook installs (using Go) and runs the KubeLinter utility to lint Helm charts and Kubernetes YAML files. 4 | entry: kube-linter lint 5 | language: golang 6 | types: [yaml] 7 | 8 | - id: kube-linter-system 9 | name: KubeLinter System 10 | description: This hook runs the KubeLinter utility that exists already on the system to lint Helm charts and Kubernetes YAML files. 11 | entry: kube-linter lint 12 | language: system 13 | types: [yaml] 14 | 15 | - id: kube-linter-docker 16 | name: KubeLinter Docker 17 | description: This hook runs kube-linter using the project's official docker image 18 | language: docker_image 19 | types: [yaml] 20 | entry: stackrox/kube-linter:v0.6.4 lint 21 | -------------------------------------------------------------------------------- /pkg/templates/cpurequirements/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | 6 | // The type of requirement. Use any to apply to both requests and limits. 7 | // +enum=request 8 | // +enum=limit 9 | // +enum=any 10 | // +required 11 | RequirementsType string 12 | 13 | // The lower bound of the requirement (inclusive), specified as 14 | // a number of milli-cores. 15 | // If not specified, it is treated as a lower bound of zero. 16 | LowerBoundMillis int `json:"lowerBoundMillis"` 17 | 18 | // The upper bound of the requirement (inclusive), specified as 19 | // a number of milli-cores. 20 | // If not specified, it is treated as "no upper bound". 21 | UpperBoundMillis *int `json:"upperBoundMillis"` 22 | } 23 | -------------------------------------------------------------------------------- /pkg/objectkinds/clusterrolebinding.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | rbacV1 "k8s.io/api/rbac/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // ClusterRoleBinding represents Kubernetes ClusterRoleBinding objects. Case sensitive. 10 | ClusterRoleBinding = "ClusterRoleBinding" 11 | ) 12 | 13 | var ( 14 | clusterRoleBindingGVK = rbacV1.SchemeGroupVersion.WithKind("ClusterRoleBinding") 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(ClusterRoleBinding, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == clusterRoleBindingGVK 20 | })) 21 | } 22 | 23 | // GetClusterRoleBindingAPIVersion returns ClusterRoleBinding's APIVersion 24 | func GetClusterRoleBindingAPIVersion() string { 25 | return clusterRoleBindingGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /pkg/templates/all/all_test.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "golang.stackrox.io/kube-linter/pkg/templates" 8 | ) 9 | 10 | func TestTemplatesAreValid(t *testing.T) { 11 | for _, template := range templates.List() { 12 | t.Run(template.HumanName, func(t *testing.T) { 13 | assert.NotEmpty(t, template.HumanName, "human name") 14 | assert.NotEmpty(t, template.Key, "name") 15 | assert.NotEmpty(t, template.Description, "description") 16 | assert.NotNil(t, template.ParseAndValidateParams, "parse and validate params") 17 | assert.NotNil(t, template.Parameters, "params") // We want people to use the generated code and explicitly set it to an empty list. 18 | assert.NotNil(t, template.Instantiate, "instantiate") 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/builtinchecks/yamls/no-anti-affinity.yaml: -------------------------------------------------------------------------------- 1 | name: "no-anti-affinity" 2 | description: "Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity, to ensure that the orchestrator attempts to schedule replicas on different nodes." 3 | remediation: >- 4 | Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. 5 | Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, 6 | and set the topologyKey to kubernetes.io/hostname. 7 | Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details. 8 | scope: 9 | objectKinds: 10 | - DeploymentLike 11 | template: "anti-affinity" 12 | params: 13 | minReplicas: 2 14 | -------------------------------------------------------------------------------- /tests/checks/access-to-secrets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: dont-fire 6 | namespace: namespace-dev 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["pods"] 10 | verbs: ["get"] 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: Role 14 | metadata: 15 | name: role1 16 | namespace: namespace-dev 17 | rules: 18 | - apiGroups: [""] 19 | resources: ["secrets"] 20 | verbs: ["get"] 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: RoleBinding 24 | metadata: 25 | name: role-binding1 26 | namespace: namespace-dev 27 | subjects: 28 | - kind: ServiceAccount 29 | name: account1 30 | namespace: namespace-dev 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: Role 34 | name: role1 35 | -------------------------------------------------------------------------------- /pkg/matcher/string.go: -------------------------------------------------------------------------------- 1 | package matcher 2 | 3 | import ( 4 | "regexp" 5 | 6 | "golang.stackrox.io/kube-linter/internal/stringutils" 7 | ) 8 | 9 | const ( 10 | // NegationPrefix is the prefix used for negations. 11 | NegationPrefix = "!" 12 | ) 13 | 14 | func matchAny(_ string) bool { 15 | return true 16 | } 17 | 18 | // ForString constructs a string matcher for the given value. 19 | func ForString(value string) (func(string) bool, error) { 20 | if value == "" { 21 | return matchAny, nil 22 | } 23 | var negate bool 24 | if stringutils.ConsumePrefix(&value, NegationPrefix) { 25 | negate = true 26 | } 27 | re, err := regexp.Compile(value) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return func(s string) bool { 32 | matched := re.MatchString(s) 33 | return matched != negate 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /tests/checks/access-to-create-pods.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: dont-fire 6 | namespace: namespace-dev 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["pods"] 10 | verbs: ["get"] 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: Role 14 | metadata: 15 | name: role1 16 | namespace: namespace-dev 17 | rules: 18 | - apiGroups: [""] 19 | resources: ["pods"] 20 | verbs: ["create"] 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: RoleBinding 24 | metadata: 25 | name: role-binding1 26 | namespace: namespace-dev 27 | subjects: 28 | - kind: ServiceAccount 29 | name: account1 30 | namespace: namespace-dev 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: Role 34 | name: role1 35 | -------------------------------------------------------------------------------- /pkg/objectkinds/serviceMonitor.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | const ( 9 | // ServiceMonitor represents Prometheus Service Monitor objects. 10 | ServiceMonitor = k8sMonitoring.ServiceMonitorsKind 11 | ) 12 | 13 | var ( 14 | serviceMonitorGVK = k8sMonitoring.SchemeGroupVersion.WithKind(ServiceMonitor) 15 | ) 16 | 17 | func init() { 18 | RegisterObjectKind(ServiceMonitor, MatcherFunc(func(gvk schema.GroupVersionKind) bool { 19 | return gvk == serviceMonitorGVK 20 | })) 21 | } 22 | 23 | // GetServiceMonitorAPIVersion returns servicemonitor's apiversion 24 | func GetServiceMonitorAPIVersion() string { 25 | return serviceMonitorGVK.GroupVersion().String() 26 | } 27 | -------------------------------------------------------------------------------- /pkg/ignore/ignore.go: -------------------------------------------------------------------------------- 1 | package ignore 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/internal/stringutils" 5 | ) 6 | 7 | const ( 8 | // AnnotationKeyPrefix is the prefix for annotations for kube-linter check ignores. 9 | AnnotationKeyPrefix = "ignore-check.kube-linter.io/" 10 | 11 | // AllAnnotationKey is used to ignore all checks for a given object. 12 | AllAnnotationKey = "kube-linter.io/ignore-all" 13 | ) 14 | 15 | // ObjectForCheck returns whether to ignore the given object for the passed check name. 16 | func ObjectForCheck(annotations map[string]string, checkName string) bool { 17 | for k := range annotations { 18 | if k == AllAnnotationKey { 19 | return true 20 | } 21 | key := k 22 | if stringutils.ConsumePrefix(&key, AnnotationKeyPrefix) && key == checkName { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/scc.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | ocpSecV1 "github.com/openshift/api/security/v1" 7 | "github.com/stretchr/testify/require" 8 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockSecurityContextConstraints adds a mock SecurityContextConstraints to LintContext 13 | func (l *MockLintContext) AddMockSecurityContextConstraints(t *testing.T, name string, allowFlag bool) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &ocpSecV1.SecurityContextConstraints{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.SecurityContextConstraints, 18 | APIVersion: objectkinds.GetSCCAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 21 | AllowPrivilegedContainer: allowFlag, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/checks/forbidden-annotation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | annotations: 7 | reloader.stakater.com/auto: "true" 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: dont-fire 13 | annotations: 14 | reloader.stakater.com/auto: "false" 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: dont-fire 20 | annotations: 21 | reloader.stakater.com/auto: T 22 | --- 23 | apiVersion: v1 24 | kind: ServiceAccount 25 | metadata: 26 | name: bad-irsa-role 27 | annotations: 28 | eks.amazonaws.com/role-arn: this-is-not-a-valid-iam-role-arn 29 | --- 30 | apiVersion: v1 31 | kind: ServiceAccount 32 | metadata: 33 | name: good-irsa-role 34 | annotations: 35 | eks.amazonaws.com/role-arn: arn:aws:iam::121212121212:role/role-name-goes-here 36 | -------------------------------------------------------------------------------- /internal/stringutils/consume.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import "strings" 4 | 5 | // ConsumePrefix checks if *s has the given prefix, and if yes, modifies it 6 | // to remove the prefix. The return value indicates whether the original string 7 | // had the given prefix. 8 | func ConsumePrefix(s *string, prefix string) bool { 9 | orig := *s 10 | if !strings.HasPrefix(orig, prefix) { 11 | return false 12 | } 13 | *s = orig[len(prefix):] 14 | return true 15 | } 16 | 17 | // ConsumeSuffix checks if *s has the given suffix, and if yes, modifies it 18 | // to remove the suffix. The return value indicates whether the original string 19 | // had the given suffix. 20 | func ConsumeSuffix(s *string, suffix string) bool { 21 | orig := *s 22 | if !strings.HasSuffix(orig, suffix) { 23 | return false 24 | } 25 | *s = orig[:len(orig)-len(suffix)] 26 | return true 27 | } 28 | -------------------------------------------------------------------------------- /pkg/objectkinds/scaledobject.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "fmt" 5 | 6 | kedaV1Alpha1 "golang.stackrox.io/kube-linter/pkg/crds/keda/v1alpha1" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | ) 9 | 10 | const ( 11 | // ScaledObject represents Kubernetes ScaledObject objects. Case sensitive. 12 | ScaledObject = "ScaledObject" 13 | ) 14 | 15 | var ( 16 | ScaledObjectV1Alpha1 = kedaV1Alpha1.SchemeGroupVersion.WithKind(ScaledObject) 17 | ) 18 | 19 | func isScaledObject(gvk schema.GroupVersionKind) bool { 20 | return gvk == ScaledObjectV1Alpha1 21 | } 22 | 23 | func init() { 24 | RegisterObjectKind(ScaledObject, MatcherFunc(isScaledObject)) 25 | } 26 | 27 | // GetScaledObjectAPIVersion returns ScaledObject's APIVersion 28 | func GetScaledObjectAPIVersion(version string) string { 29 | return fmt.Sprintf("%s/%s", ScaledObjectV1Alpha1.Group, version) 30 | } 31 | -------------------------------------------------------------------------------- /tests/checks/latest-tag.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: app 11 | image: app:v1 12 | --- 13 | apiVersion: apps.openshift.io/v1 14 | kind: DeploymentConfig 15 | metadata: 16 | name: dont-fire 17 | spec: 18 | template: 19 | spec: 20 | containers: 21 | - name: app 22 | image: app:v1 23 | --- 24 | apiVersion: apps/v1 25 | kind: Deployment 26 | metadata: 27 | name: app 28 | spec: 29 | template: 30 | spec: 31 | containers: 32 | - name: app 33 | image: app:latest 34 | --- 35 | apiVersion: apps.openshift.io/v1 36 | kind: DeploymentConfig 37 | metadata: 38 | name: app 39 | spec: 40 | template: 41 | spec: 42 | containers: 43 | - name: app 44 | image: app:latest -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/deeply-nested-unsorted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | kubernetes.io/change-cause: "updated image" 6 | app.kubernetes.io/version: "1.0.0" 7 | labels: 8 | app: myapp 9 | name: deeply-nested 10 | spec: 11 | selector: 12 | matchLabels: 13 | app: myapp 14 | template: 15 | metadata: 16 | labels: 17 | app: myapp 18 | spec: 19 | containers: 20 | - env: 21 | - name: LOG_LEVEL 22 | value: debug 23 | - name: APP_MODE 24 | value: production 25 | image: myapp:latest 26 | name: main 27 | resources: 28 | requests: 29 | memory: "64Mi" 30 | cpu: "100m" 31 | limits: 32 | memory: "128Mi" 33 | cpu: "500m" -------------------------------------------------------------------------------- /e2etests/testdata/forbidden-annotation-config.yaml: -------------------------------------------------------------------------------- 1 | checks: 2 | addAllBuiltIn: false 3 | customChecks: 4 | - name: "forbid-annotation-reloader-stakater-auto" 5 | description: "" 6 | remediation: "Remove reloader.stakater.com/auto annotation" 7 | scope: 8 | objectKinds: 9 | - DeploymentLike 10 | template: "forbidden-annotation" 11 | params: 12 | key: "reloader.stakater.com/auto" 13 | value: 'true' 14 | - name: invalid-irsa-role 15 | description: "IRSA annotations must have a valid IAM Role ARN value" 16 | remediation: "Validate the format of the annotation's value to ensure it is a valid IAM Role ARN" 17 | scope: 18 | objectKinds: 19 | - ServiceAccount 20 | template: "forbidden-annotation" 21 | params: 22 | key: "eks.amazonaws.com/role-arn" 23 | value: | 24 | !arn:aws:iam::\d{12}:role\/[\w+=,.@-]{1,64}$ -------------------------------------------------------------------------------- /tests/checks/sensitive-host-mounts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: app1 10 | template: 11 | spec: 12 | containers: 13 | - name: app 14 | volumeMounts: 15 | - name: config 16 | mountPath: /etc 17 | volumes: 18 | - name: config 19 | hostPath: 20 | path: /etc 21 | --- 22 | apiVersion: apps.openshift.io/v1 23 | kind: DeploymentConfig 24 | metadata: 25 | name: app2 26 | spec: 27 | selector: 28 | app.kubernetes.io/name: app2 29 | template: 30 | spec: 31 | containers: 32 | - name: app2 33 | runAsUser: 0 34 | volumeMounts: 35 | - name: config 36 | mountPath: /etc 37 | volumes: 38 | - name: config 39 | hostPath: 40 | path: /etc -------------------------------------------------------------------------------- /tests/checks/writable-host-mount.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: app1 6 | spec: 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: app1 10 | template: 11 | spec: 12 | containers: 13 | - name: app 14 | volumeMounts: 15 | - name: config 16 | mountPath: /config 17 | volumes: 18 | - name: config 19 | hostPath: 20 | path: /config 21 | --- 22 | apiVersion: apps.openshift.io/v1 23 | kind: DeploymentConfig 24 | metadata: 25 | name: app2 26 | spec: 27 | selector: 28 | app.kubernetes.io/name: app2 29 | template: 30 | spec: 31 | containers: 32 | - name: app2 33 | runAsUser: 0 34 | volumeMounts: 35 | - name: config 36 | mountPath: /config 37 | volumes: 38 | - name: config 39 | hostPath: 40 | path: /config -------------------------------------------------------------------------------- /tests/checks/duplicate-env-var.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | spec: 4 | template: 5 | spec: 6 | containers: 7 | - name: dont-fire-deployment 8 | env: 9 | - name: PORT 10 | value: "8080" 11 | - name: HOST 12 | value: "localhost" 13 | - name: fire-deployment 14 | env: 15 | - name: PORT 16 | value: "8080" 17 | - name: PORT 18 | value: "9090" 19 | --- 20 | apiVersion: apps/v1 21 | kind: StatefulSet 22 | spec: 23 | template: 24 | spec: 25 | containers: 26 | - name: dont-fire-stateful 27 | env: 28 | - name: PORT 29 | value: "8080" 30 | - name: HOST 31 | value: "localhost" 32 | - name: fire-stateful 33 | env: 34 | - name: PORT 35 | value: "8080" 36 | - name: PORT 37 | value: "9090" 38 | -------------------------------------------------------------------------------- /pkg/extract/sts_spec.go: -------------------------------------------------------------------------------- 1 | package extract 2 | 3 | import ( 4 | "reflect" 5 | 6 | "golang.stackrox.io/kube-linter/pkg/k8sutil" 7 | appsV1 "k8s.io/api/apps/v1" 8 | ) 9 | 10 | func StatefulSetSpec(obj k8sutil.Object) (appsV1.StatefulSetSpec, bool) { 11 | if obj == nil { 12 | return appsV1.StatefulSetSpec{}, false 13 | } 14 | 15 | switch obj := obj.(type) { 16 | case *appsV1.StatefulSet: 17 | return obj.Spec, true 18 | default: 19 | kind := obj.GetObjectKind().GroupVersionKind().Kind 20 | if kind != "StatefulSet" { 21 | return appsV1.StatefulSetSpec{}, false 22 | } 23 | 24 | objValue := reflect.Indirect(reflect.ValueOf(obj)) 25 | spec := objValue.FieldByName("Spec") 26 | if !spec.IsValid() { 27 | return appsV1.StatefulSetSpec{}, false 28 | } 29 | statefulSetSpec, ok := spec.Interface().(appsV1.StatefulSetSpec) 30 | if ok { 31 | return statefulSetSpec, true 32 | } 33 | return appsV1.StatefulSetSpec{}, false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/crds/keda/v1alpha1/gvkr_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The KEDA Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | // GroupVersionKindResource provides unified structure for schema.GroupVersionKind and Resource 20 | type GroupVersionKindResource struct { 21 | Group string `json:"group"` 22 | Version string `json:"version"` 23 | Kind string `json:"kind"` 24 | Resource string `json:"resource"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/complex-pod-spec.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: complex-pod-spec 5 | spec: 6 | template: 7 | metadata: 8 | labels: 9 | app: complex 10 | spec: 11 | containers: 12 | - image: app:latest 13 | name: app 14 | volumeMounts: 15 | - mountPath: /data 16 | name: data-volume 17 | securityContext: 18 | runAsNonRoot: true 19 | allowPrivilegeEscalation: false 20 | initContainers: 21 | - command: 22 | - sh 23 | - -c 24 | - echo init 25 | image: busybox:latest 26 | name: init 27 | serviceAccountName: app-sa 28 | securityContext: 29 | fsGroup: 1000 30 | runAsUser: 1000 31 | runAsGroup: 1000 32 | volumes: 33 | - name: data-volume 34 | persistentVolumeClaim: 35 | claimName: data-pvc -------------------------------------------------------------------------------- /config.yaml.example: -------------------------------------------------------------------------------- 1 | # customChecks defines custom checks. 2 | customChecks: 3 | - name: "required-label-app" 4 | template: "required-label" 5 | params: 6 | key: "app" 7 | checks: 8 | # if doNotAutoAddDefaults is true, default checks are not automatically added. 9 | doNotAutoAddDefaults: false 10 | 11 | # addAllBuiltIn, if set, adds all built-in checks. This allows users to 12 | # explicitly opt-out of checks that are not relevant using Exclude. 13 | # Takes precedence over doNotAutoAddDefaults, if both are set. 14 | addAllBuiltIn: false 15 | 16 | # include explicitly adds checks, by name. You can reference any of the built-in checks. 17 | # Note that customChecks defined above are included automatically. 18 | include: 19 | - "required-label-owner" 20 | # exclude explicitly excludes checks, by name. exclude has the highest priority: if a check is 21 | # in exclude, then it is not considered, even if it is in include as well. 22 | exclude: 23 | - "privileged" 24 | -------------------------------------------------------------------------------- /pkg/crds/keda/v1alpha1/identifier.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The KEDA Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | // GenerateIdentifier returns identifier for the object in form "kind.namespace.name" (lowercase) 25 | func GenerateIdentifier(kind, namespace, name string) string { 26 | return strings.ToLower(fmt.Sprintf("%s.%s.%s", kind, namespace, name)) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/ingress.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | networkingV1 "k8s.io/api/networking/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | func (l *MockLintContext) AddMockIngress(t *testing.T, name string) { 13 | require.NotEmpty(t, name) 14 | l.objects[name] = &networkingV1.Ingress{ 15 | TypeMeta: metaV1.TypeMeta{ 16 | Kind: objectkinds.Ingress, 17 | APIVersion: objectkinds.GetIngressAPIVersion(), 18 | }, 19 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 20 | Spec: networkingV1.IngressSpec{}, 21 | } 22 | } 23 | 24 | // ModifyIngress modifies a given networkpolicy in the context via the passed function. 25 | func (l *MockLintContext) ModifyIngress(t *testing.T, name string, f func(ingress *networkingV1.Ingress)) { 26 | r, ok := l.objects[name].(*networkingV1.Ingress) 27 | require.True(t, ok) 28 | f(r) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/role.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | rbacV1 "k8s.io/api/rbac/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockRole adds a mock Role to LintContext 13 | func (l *MockLintContext) AddMockRole(t *testing.T, name, namespace string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &rbacV1.Role{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.Role, 18 | APIVersion: objectkinds.GetRoleAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name, Namespace: namespace}, 21 | Rules: []rbacV1.PolicyRule{}, 22 | } 23 | } 24 | 25 | // ModifyRole modifies a given Role in the context via the passed function. 26 | func (l *MockLintContext) ModifyRole(t *testing.T, name string, f func(role *rbacV1.Role)) { 27 | r, ok := l.objects[name].(*rbacV1.Role) 28 | require.True(t, ok) 29 | f(r) 30 | } 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to KubeLinter 2 | 3 | Thank you for your interest in contributing to KubeLinter! 4 | 5 | ### Code Contributions 6 | 7 | We welcome code contributions from the community. Anyone is welcome to create a pull request, 8 | and request a review from any of the project maintainers. 9 | 10 | By default, your pull requests should be linked to an [issue](https://github.com/stackrox/kube-linter/issues). 11 | If you're addressing an existing issue, great! If your change is unrelated to an existing issue, 12 | please file an issue first, and make sure you get buy-in from the maintainers. 13 | 14 | However, if your change is relatively trivial (say, a documentation update, or a simple bugfix), 15 | feel free to directly create a pull request, and explain your changes in the pull request. 16 | 17 | ### Feature Requests and Bug Reports 18 | 19 | If you find a bug, or have a request for a feature, 20 | please [create a GitHub issue](https://github.com/stackrox/kube-linter/issues/new/choose). 21 | -------------------------------------------------------------------------------- /pkg/templates/updateconfig/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params represents the params accepted by this template. 4 | type Params struct { 5 | // A regular expression the defines the type of update 6 | // strategy allowed. 7 | // +required 8 | StrategyTypeRegex string 9 | 10 | // The maximum value that be set in a RollingUpdate 11 | // configuration for the MaxUnavailable. This can be 12 | // an integer or a percent. 13 | MaxPodsUnavailable string 14 | 15 | // The minimum value that be set in a RollingUpdate 16 | // configuration for the MaxUnavailable. This can be 17 | // an integer or a percent. 18 | MinPodsUnavailable string 19 | 20 | // The maximum value that be set in a RollingUpdate 21 | // configuration for the MaxSurge. This can be 22 | // an integer or a percent. 23 | MaxSurge string 24 | 25 | // The minimum value that be set in a RollingUpdate 26 | // configuration for the MaxSurge. This can be 27 | // an integer or a percent. 28 | MinSurge string 29 | } 30 | -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/all-sorted-complex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | app.kubernetes.io/version: "1.0" 6 | kubernetes.io/description: "test app" 7 | labels: 8 | app: test 9 | environment: prod 10 | name: all-sorted-complex 11 | spec: 12 | replicas: 3 13 | selector: 14 | matchLabels: 15 | app: test 16 | template: 17 | metadata: 18 | labels: 19 | app: test 20 | spec: 21 | containers: 22 | - env: 23 | - name: ENV 24 | value: prod 25 | image: test:latest 26 | name: main 27 | ports: 28 | - containerPort: 8080 29 | name: http 30 | protocol: TCP 31 | resources: 32 | limits: 33 | cpu: "1" 34 | memory: 512Mi 35 | requests: 36 | cpu: 100m 37 | memory: 128Mi 38 | volumes: 39 | - emptyDir: {} 40 | name: cache -------------------------------------------------------------------------------- /pkg/templates/sortedkeys/testdata/container-with-merge.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: complex-anchors 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - &app-container 10 | image: myapp:latest 11 | name: app 12 | resources: &resources 13 | limits: 14 | cpu: "1" 15 | memory: 512Mi 16 | requests: 17 | cpu: 100m 18 | memory: 128Mi 19 | env: &common-env 20 | - name: LOG_LEVEL 21 | value: info 22 | - name: APP_MODE 23 | value: production 24 | - <<: *app-container 25 | name: sidecar 26 | image: sidecar:latest 27 | resources: *resources 28 | env: 29 | - name: SIDECAR_MODE 30 | value: enabled 31 | - <<: *common-env 32 | - <<: *app-container 33 | name: worker 34 | env: *common-env 35 | image: worker:latest -------------------------------------------------------------------------------- /pkg/templates/registry.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "golang.stackrox.io/kube-linter/pkg/check" 8 | ) 9 | 10 | var ( 11 | allTemplates = make(map[string]check.Template) 12 | ) 13 | 14 | // Register registers a template with the given name. 15 | // Intended to be called at program init time. 16 | func Register(t check.Template) { 17 | if _, ok := allTemplates[t.Key]; ok { 18 | panic(fmt.Sprintf("duplicate template: %v", t.Key)) 19 | } 20 | allTemplates[t.Key] = t 21 | } 22 | 23 | // Get gets a template by name, returning a boolean indicating whether it was found. 24 | func Get(name string) (check.Template, bool) { 25 | t, ok := allTemplates[name] 26 | return t, ok 27 | } 28 | 29 | // List returns all known templates, sorted by name. 30 | func List() []check.Template { 31 | out := make([]check.Template, 0, len(allTemplates)) 32 | for _, t := range allTemplates { 33 | out = append(out, t) 34 | } 35 | sort.Slice(out, func(i, j int) bool { 36 | return out[i].Key < out[j].Key 37 | }) 38 | return out 39 | } 40 | -------------------------------------------------------------------------------- /pkg/templates/requiredlabel/template.go: -------------------------------------------------------------------------------- 1 | package requiredlabel 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 7 | "golang.stackrox.io/kube-linter/pkg/templates" 8 | "golang.stackrox.io/kube-linter/pkg/templates/requiredlabel/internal/params" 9 | "golang.stackrox.io/kube-linter/pkg/templates/util" 10 | ) 11 | 12 | func init() { 13 | templates.Register(check.Template{ 14 | HumanName: "Required Label", 15 | Key: "required-label", 16 | Description: "Flag objects not carrying at least one label matching the provided patterns", 17 | SupportedObjectKinds: config.ObjectKindsDesc{ 18 | ObjectKinds: []string{objectkinds.Any}, 19 | }, 20 | Parameters: params.ParamDescs, 21 | ParseAndValidateParams: params.ParseAndValidate, 22 | Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) { 23 | return util.ConstructRequiredMapMatcher(p.Key, p.Value, "label") 24 | }), 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /tests/checks/privileged-ports.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | ports: 13 | - containerPort: 8080 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: dont-fire 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | ports: 26 | - containerPort: 8080 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: app1 32 | spec: 33 | replicas: 3 34 | template: 35 | spec: 36 | containers: 37 | - name: app 38 | ports: 39 | - containerPort: 80 40 | --- 41 | apiVersion: apps.openshift.io/v1 42 | kind: DeploymentConfig 43 | metadata: 44 | name: app2 45 | spec: 46 | replicas: 3 47 | template: 48 | spec: 49 | containers: 50 | - name: app 51 | ports: 52 | - containerPort: 80 -------------------------------------------------------------------------------- /tests/testdata/mychart/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "mychart.fullname" . }} 6 | labels: 7 | {{- include "mychart.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "mychart.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /pkg/templates/forbiddenannotation/template.go: -------------------------------------------------------------------------------- 1 | package forbiddenannotation 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 7 | "golang.stackrox.io/kube-linter/pkg/templates" 8 | "golang.stackrox.io/kube-linter/pkg/templates/forbiddenannotation/internal/params" 9 | "golang.stackrox.io/kube-linter/pkg/templates/util" 10 | ) 11 | 12 | func init() { 13 | templates.Register(check.Template{ 14 | HumanName: "Forbidden Annotation", 15 | Key: "forbidden-annotation", 16 | Description: "Flag objects carrying at least one annotation matching the provided patterns", 17 | SupportedObjectKinds: config.ObjectKindsDesc{ 18 | ObjectKinds: []string{objectkinds.Any}, 19 | }, 20 | Parameters: params.ParamDescs, 21 | ParseAndValidateParams: params.ParseAndValidate, 22 | Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) { 23 | return util.ConstructForbiddenMapMatcher(p.Key, p.Value, "annotation") 24 | }), 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/templates/requiredannotation/template.go: -------------------------------------------------------------------------------- 1 | package requiredannotation 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 7 | "golang.stackrox.io/kube-linter/pkg/templates" 8 | "golang.stackrox.io/kube-linter/pkg/templates/requiredannotation/internal/params" 9 | "golang.stackrox.io/kube-linter/pkg/templates/util" 10 | ) 11 | 12 | func init() { 13 | templates.Register(check.Template{ 14 | HumanName: "Required Annotation", 15 | Key: "required-annotation", 16 | Description: "Flag objects not carrying at least one annotation matching the provided patterns", 17 | SupportedObjectKinds: config.ObjectKindsDesc{ 18 | ObjectKinds: []string{objectkinds.Any}, 19 | }, 20 | Parameters: params.ParamDescs, 21 | ParseAndValidateParams: params.ParseAndValidate, 22 | Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) { 23 | return util.ConstructRequiredMapMatcher(p.Key, p.Value, "annotation") 24 | }), 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /tests/checks/privileged-container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | securityContext: 13 | privileged: false 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: dont-fire 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | securityContext: 26 | privileged: false 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: app1 32 | spec: 33 | replicas: 3 34 | template: 35 | spec: 36 | containers: 37 | - name: app 38 | securityContext: 39 | privileged: true 40 | --- 41 | apiVersion: apps.openshift.io/v1 42 | kind: DeploymentConfig 43 | metadata: 44 | name: app2 45 | spec: 46 | replicas: 3 47 | template: 48 | spec: 49 | containers: 50 | - name: app 51 | securityContext: 52 | privileged: true -------------------------------------------------------------------------------- /pkg/command/root/command.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/fatih/color" 8 | "github.com/spf13/cobra" 9 | "golang.stackrox.io/kube-linter/pkg/command/checks" 10 | "golang.stackrox.io/kube-linter/pkg/command/lint" 11 | "golang.stackrox.io/kube-linter/pkg/command/templates" 12 | "golang.stackrox.io/kube-linter/pkg/command/version" 13 | ) 14 | 15 | const ( 16 | colorFlag = "with-color" 17 | ) 18 | 19 | // Command is the root command. 20 | func Command() *cobra.Command { 21 | c := &cobra.Command{ 22 | Use: filepath.Base(os.Args[0]), 23 | SilenceUsage: true, 24 | SilenceErrors: true, 25 | PersistentPreRun: func(cmd *cobra.Command, _ []string) { 26 | // Only forcefully set colorful output if the flag has been set. 27 | if cmd.Flags().Changed(colorFlag) { 28 | color.NoColor = false 29 | } 30 | }, 31 | } 32 | c.AddCommand( 33 | checks.Command(), 34 | lint.Command(), 35 | templates.Command(), 36 | version.Command(), 37 | ) 38 | c.PersistentFlags().Bool(colorFlag, true, "Force color output") 39 | return c 40 | } 41 | -------------------------------------------------------------------------------- /tests/checks/drop-net-raw-capability.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: app 11 | securityContext: 12 | capabilities: 13 | --- 14 | apiVersion: apps.openshift.io/v1 15 | kind: DeploymentConfig 16 | metadata: 17 | name: dont-fire 18 | spec: 19 | template: 20 | spec: 21 | containers: 22 | - name: app 23 | securityContext: 24 | capabilities: 25 | --- 26 | apiVersion: apps/v1 27 | kind: Deployment 28 | metadata: 29 | name: app 30 | spec: 31 | template: 32 | spec: 33 | containers: 34 | - name: app 35 | securityContext: 36 | capabilities: 37 | add: 38 | - NET_RAW 39 | --- 40 | apiVersion: apps.openshift.io/v1 41 | kind: DeploymentConfig 42 | metadata: 43 | name: app 44 | spec: 45 | template: 46 | spec: 47 | containers: 48 | - name: app 49 | securityContext: 50 | capabilities: 51 | add: 52 | - NET_RAW -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/clusterrole.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | rbacV1 "k8s.io/api/rbac/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockClusterRole adds a mock ClusterRole to LintContext 13 | func (l *MockLintContext) AddMockClusterRole(t *testing.T, name string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &rbacV1.ClusterRole{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.ClusterRole, 18 | APIVersion: objectkinds.GetClusterRoleAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 21 | Rules: []rbacV1.PolicyRule{}, 22 | AggregationRule: &rbacV1.AggregationRule{}, 23 | } 24 | } 25 | 26 | // ModifyClusterRole modifies a given clusterrole in the context via the passed function. 27 | func (l *MockLintContext) ModifyClusterRole(t *testing.T, name string, f func(clusterrole *rbacV1.ClusterRole)) { 28 | r, ok := l.objects[name].(*rbacV1.ClusterRole) 29 | require.True(t, ok) 30 | f(r) 31 | } 32 | -------------------------------------------------------------------------------- /tests/checks/env-var-secret.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: app 11 | env: 12 | - name: BLAH 13 | value: secretsquirrels 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: dont-fire 19 | spec: 20 | template: 21 | spec: 22 | containers: 23 | - name: app 24 | env: 25 | - name: BLAH 26 | value: secretsquirrels 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: app 32 | spec: 33 | template: 34 | spec: 35 | containers: 36 | - name: app 37 | env: 38 | - name: SECRET_BLAH 39 | value: secretsquirrels 40 | --- 41 | apiVersion: apps.openshift.io/v1 42 | kind: DeploymentConfig 43 | metadata: 44 | name: app 45 | spec: 46 | template: 47 | spec: 48 | containers: 49 | - name: app 50 | env: 51 | - name: SECRET_BLAH 52 | value: secretsquirrels -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/networkpolicy.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | networkingV1 "k8s.io/api/networking/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockNetworkPolicy adds a mock NetworkPolicy to LintContext 13 | func (l *MockLintContext) AddMockNetworkPolicy(t *testing.T, name string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &networkingV1.NetworkPolicy{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.NetworkPolicy, 18 | APIVersion: objectkinds.GetNetworkPolicyAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 21 | Spec: networkingV1.NetworkPolicySpec{}, 22 | } 23 | } 24 | 25 | // ModifyNetworkPolicy modifies a given networkpolicy in the context via the passed function. 26 | func (l *MockLintContext) ModifyNetworkPolicy(t *testing.T, name string, f func(networkpolicy *networkingV1.NetworkPolicy)) { 27 | r, ok := l.objects[name].(*networkingV1.NetworkPolicy) 28 | require.True(t, ok) 29 | f(r) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/rolebinding.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | rbacV1 "k8s.io/api/rbac/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockRoleBinding adds a mock RoleBinding to LintContext 13 | func (l *MockLintContext) AddMockRoleBinding(t *testing.T, name, namespace string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &rbacV1.RoleBinding{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.RoleBinding, 18 | APIVersion: objectkinds.GetRoleBindingAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name, Namespace: namespace}, 21 | Subjects: []rbacV1.Subject{}, 22 | RoleRef: rbacV1.RoleRef{}, 23 | } 24 | } 25 | 26 | // ModifyRoleBinding modifies a given RoleBinding in the context via the passed function. 27 | func (l *MockLintContext) ModifyRoleBinding(t *testing.T, name string, f func(rolebinding *rbacV1.RoleBinding)) { 28 | rb, ok := l.objects[name].(*rbacV1.RoleBinding) 29 | require.True(t, ok) 30 | f(rb) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/templates/kubeconform/internal/params/params.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | // Params defines the configuration parameters for this template. 4 | type Params struct { 5 | // SchemaLocations contains locations of schemas to use. See: https://github.com/yannh/kubeconform/tree/master?tab=readme-ov-file#overriding-schemas-location 6 | // +noregex 7 | SchemaLocations []string 8 | // Cache specifies the folder to cache schemas downloaded via HTTP. 9 | // +noregex 10 | Cache string 11 | // SkipKinds lists resource kinds to ignore during validation. 12 | // +noregex 13 | SkipKinds []string 14 | // RejectKinds lists resource kinds to reject during validation. 15 | // +noregex 16 | RejectKinds []string 17 | // KubernetesVersion specifies the Kubernetes version - must match one in https://github.com/instrumenta/kubernetes-json-schema 18 | // +noregex 19 | KubernetesVersion string 20 | // Strict enables strict validation that will error if resources contain undocumented fields. 21 | Strict bool 22 | // IgnoreMissingSchemas will skip validation for resources if no schema can be found. 23 | IgnoreMissingSchemas bool 24 | } 25 | -------------------------------------------------------------------------------- /tests/checks/no-read-only-root-fs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | securityContext: 13 | readOnlyRootFilesystem: true 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: dont-fire 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | securityContext: 26 | readOnlyRootFilesystem: true 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: app1 32 | spec: 33 | replicas: 3 34 | template: 35 | spec: 36 | containers: 37 | - name: app 38 | securityContext: 39 | readOnlyRootFilesystem: false 40 | --- 41 | apiVersion: apps.openshift.io/v1 42 | kind: DeploymentConfig 43 | metadata: 44 | name: app2 45 | spec: 46 | replicas: 3 47 | template: 48 | spec: 49 | containers: 50 | - name: app 51 | securityContext: 52 | readOnlyRootFilesystem: false -------------------------------------------------------------------------------- /pkg/command/lint/command_test.go: -------------------------------------------------------------------------------- 1 | package lint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | // Register templates 9 | _ "golang.stackrox.io/kube-linter/pkg/templates/all" 10 | ) 11 | 12 | func TestCommand_InvalidResources(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | cmd *cobra.Command 16 | failure bool 17 | output string 18 | }{ 19 | {name: "InvalidYAML", cmd: createLintCommand("./testdata/invalid.yaml", "--fail-on-invalid-resource"), failure: true}, 20 | {name: "NonexistentFile", cmd: createLintCommand("./testdata/foo-bar.yaml", "--fail-on-invalid-resource"), failure: true}, 21 | {name: "ValidPod", cmd: createLintCommand("./testdata/valid-pod.yaml", "--fail-on-invalid-resource"), failure: false}, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | err := tt.cmd.Execute() 26 | if err == nil && tt.failure || err != nil && !tt.failure { 27 | t.Fail() 28 | } 29 | }) 30 | } 31 | } 32 | 33 | func createLintCommand(args ...string) *cobra.Command { 34 | c := Command() 35 | c.SilenceUsage = true 36 | c.SetArgs(args) 37 | return c 38 | } 39 | -------------------------------------------------------------------------------- /tests/checks/privilege-escalation-container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | securityContext: 13 | allowPrivilegeEscalation: false 14 | --- 15 | apiVersion: apps.openshift.io/v1 16 | kind: DeploymentConfig 17 | metadata: 18 | name: dont-fire 19 | spec: 20 | replicas: 3 21 | template: 22 | spec: 23 | containers: 24 | - name: app 25 | securityContext: 26 | allowPrivilegeEscalation: false 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: app1 32 | spec: 33 | replicas: 3 34 | template: 35 | spec: 36 | containers: 37 | - name: app 38 | securityContext: 39 | allowPrivilegeEscalation: true 40 | --- 41 | apiVersion: apps.openshift.io/v1 42 | kind: DeploymentConfig 43 | metadata: 44 | name: app2 45 | spec: 46 | replicas: 3 47 | template: 48 | spec: 49 | containers: 50 | - name: app 51 | securityContext: 52 | allowPrivilegeEscalation: true -------------------------------------------------------------------------------- /internal/defaultchecks/default_checks.go: -------------------------------------------------------------------------------- 1 | package defaultchecks 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/internal/set" 5 | ) 6 | 7 | var ( 8 | // List is the list of built-in checks that are enabled by default. 9 | List = set.NewFrozenStringSet( 10 | "dangling-service", 11 | "deprecated-service-account-field", 12 | "docker-sock", 13 | "drop-net-raw-capability", 14 | "duplicate-env-var", 15 | "env-var-secret", 16 | "host-ipc", 17 | "host-network", 18 | "host-pid", 19 | "invalid-target-ports", 20 | "job-ttl-seconds-after-finished", 21 | "latest-tag", 22 | "liveness-port", 23 | "mismatching-selector", 24 | "no-anti-affinity", 25 | "no-extensions-v1beta", 26 | "no-read-only-root-fs", 27 | "non-existent-service-account", 28 | "pdb-max-unavailable", 29 | "pdb-min-available", 30 | "privilege-escalation-container", 31 | "privileged-container", 32 | "readiness-port", 33 | "run-as-non-root", 34 | "sensitive-host-mounts", 35 | "ssh-port", 36 | "startup-port", 37 | "unsafe-sysctls", 38 | "unset-cpu-requirements", 39 | "unset-memory-requirements", 40 | "pdb-unhealthy-pod-eviction-policy", 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /tests/testdata/splunk.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: splunk 5 | namespace: splunk-ns 6 | spec: 7 | selector: 8 | app: splunk 9 | ports: 10 | - name: http 11 | protocol: TCP 12 | port: 8000 13 | targetPort: 8000 14 | - name: https 15 | protocol: TCP 16 | port: 8088 17 | targetPort: 8088 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: splunk 23 | namespace: splunk-ns 24 | labels: 25 | apps: splunk 26 | spec: 27 | replicas: 1 28 | selector: 29 | matchLabels: 30 | app: splunk 31 | template: 32 | metadata: 33 | labels: 34 | app: splunk 35 | spec: 36 | containers: 37 | - name: splunk 38 | image: splunk/splunk:8.1.2 39 | securityContext: 40 | privileged: true 41 | ports: 42 | - containerPort: 8000 43 | - containerPort: 8088 44 | env: 45 | - name: SPLUNK_START_ARGS 46 | value: --accept-license 47 | - name: SPLUNK_USER 48 | value: root 49 | - name: SPLUNK_PASSWORD 50 | value: helloworld 51 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/clusterrolebinding.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | rbacV1 "k8s.io/api/rbac/v1" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockClusterRoleBinding adds a mock ClusterRoleBinding to LintContext 13 | func (l *MockLintContext) AddMockClusterRoleBinding(t *testing.T, name string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &rbacV1.ClusterRoleBinding{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.ClusterRoleBinding, 18 | APIVersion: objectkinds.GetClusterRoleBindingAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 21 | Subjects: []rbacV1.Subject{}, 22 | RoleRef: rbacV1.RoleRef{}, 23 | } 24 | } 25 | 26 | // ModifyClusterRoleBinding modifies a given ClusterRoleBinding in the context via the passed function. 27 | func (l *MockLintContext) ModifyClusterRoleBinding(t *testing.T, name string, f func(clusterrolebinding *rbacV1.ClusterRoleBinding)) { 28 | crb, ok := l.objects[name].(*rbacV1.ClusterRoleBinding) 29 | require.True(t, ok) 30 | f(crb) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/servicemonitor.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 7 | "github.com/stretchr/testify/require" 8 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 9 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | // AddMockServiceMonitor adds a mock ServiceMonitor to LintContext 13 | func (l *MockLintContext) AddMockServiceMonitor(t *testing.T, name string) { 14 | require.NotEmpty(t, name) 15 | l.objects[name] = &k8sMonitoring.ServiceMonitor{ 16 | TypeMeta: metaV1.TypeMeta{ 17 | Kind: objectkinds.ServiceMonitor, 18 | APIVersion: objectkinds.GetServiceMonitorAPIVersion(), 19 | }, 20 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 21 | Spec: k8sMonitoring.ServiceMonitorSpec{}, 22 | } 23 | } 24 | 25 | // ModifyServiceMonitor modifies a given servicemonitor in the context via the passed function 26 | func (l *MockLintContext) ModifyServiceMonitor(t *testing.T, name string, f func(servicemonitor *k8sMonitoring.ServiceMonitor)) { 27 | r, ok := l.objects[name].(*k8sMonitoring.ServiceMonitor) 28 | require.True(t, ok) 29 | f(r) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/objectkinds/deployment_like.go: -------------------------------------------------------------------------------- 1 | package objectkinds 2 | 3 | import ( 4 | "fmt" 5 | 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | ) 8 | 9 | var ( 10 | deploymentLikeGroupKinds = func() map[schema.GroupKind]struct{} { 11 | m := make(map[schema.GroupKind]struct{}) 12 | for _, gk := range []schema.GroupKind{ 13 | deploymentGVK.GroupKind(), 14 | daemonSetGVK.GroupKind(), 15 | deploymentConfigGVK.GroupKind(), 16 | statefulSetGVK.GroupKind(), 17 | replicaSetGVK.GroupKind(), 18 | podGVK.GroupKind(), 19 | replicationControllerGVK.GroupKind(), 20 | jobGVK.GroupKind(), 21 | cronJobGVK.GroupKind(), 22 | } { 23 | if _, ok := m[gk]; ok { 24 | panic(fmt.Sprintf("group kind double-registered: %v", gk)) 25 | } 26 | m[gk] = struct{}{} 27 | } 28 | return m 29 | }() 30 | ) 31 | 32 | func IsDeploymentLike(gvk schema.GroupVersionKind) bool { 33 | _, ok := deploymentLikeGroupKinds[gvk.GroupKind()] 34 | return ok 35 | } 36 | 37 | const ( 38 | // DeploymentLike is the name of the DeploymentLike ObjectKind. 39 | DeploymentLike = "DeploymentLike" 40 | ) 41 | 42 | func init() { 43 | RegisterObjectKind(DeploymentLike, MatcherFunc(IsDeploymentLike)) 44 | } 45 | -------------------------------------------------------------------------------- /tests/checks/dangling-hpa.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | app.kubernetes.io/name: dontfire 11 | --- 12 | apiVersion: autoscaling/v2beta1 13 | kind: HorizontalPodAutoscaler 14 | metadata: 15 | name: RELEASE-NAME-mychart 16 | spec: 17 | scaleTargetRef: 18 | apiVersion: apps/v1 19 | kind: Deployment 20 | name: dont-fire 21 | minReplicas: 1 22 | maxReplicas: 100 23 | metrics: 24 | - type: Resource 25 | resource: 26 | name: cpu 27 | targetAverageUtilization: 80 28 | --- 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: app1 33 | spec: 34 | template: 35 | metadata: 36 | labels: 37 | app.kubernetes.io/name: app1 38 | --- 39 | apiVersion: autoscaling/v2beta1 40 | kind: HorizontalPodAutoscaler 41 | metadata: 42 | name: hpa1 43 | spec: 44 | scaleTargetRef: 45 | apiVersion: apps/v1 46 | kind: Deployment 47 | name: app2 48 | minReplicas: 1 49 | maxReplicas: 100 50 | metrics: 51 | - type: Resource 52 | resource: 53 | name: cpu 54 | targetAverageUtilization: 80 -------------------------------------------------------------------------------- /tests/checks/mismatching-selector.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: dont-fire 10 | template: 11 | metadata: 12 | labels: 13 | app.kubernetes.io/name: dont-fire 14 | spec: 15 | containers: 16 | - name: app 17 | --- 18 | apiVersion: apps.openshift.io/v1 19 | kind: DeploymentConfig 20 | metadata: 21 | name: dont-fire 22 | spec: 23 | selector: 24 | app.kubernetes.io/name: dont-fire 25 | template: 26 | metadata: 27 | labels: 28 | app.kubernetes.io/name: dont-fire 29 | spec: 30 | containers: 31 | - name: app2 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: app1 37 | spec: 38 | selector: 39 | matchLabels: 40 | app.kubernetes.io/name: app1 41 | template: 42 | spec: 43 | containers: 44 | - name: app 45 | --- 46 | apiVersion: apps.openshift.io/v1 47 | kind: DeploymentConfig 48 | metadata: 49 | name: app2 50 | spec: 51 | selector: 52 | app.kubernetes.io/name: app2 53 | template: 54 | spec: 55 | containers: 56 | - name: app2 -------------------------------------------------------------------------------- /tests/checks/dangling-service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | app.kubernetes.io/name: dontfire 11 | --- 12 | apiVersion: v1 13 | kind: Service 14 | metadata: 15 | name: dont-fire 16 | spec: 17 | ports: 18 | - name: 8080-tcp 19 | port: 8080 20 | selector: 21 | app.kubernetes.io/name: dontfire 22 | --- 23 | apiVersion: apps/v1 24 | kind: Deployment 25 | metadata: 26 | name: app1 27 | spec: 28 | template: 29 | metadata: 30 | labels: 31 | app.kubernetes.io/name: app1 32 | --- 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: app1 37 | spec: 38 | ports: 39 | - name: 8080-tcp 40 | port: 8080 41 | selector: 42 | app.kubernetes.io/name: app 43 | --- 44 | apiVersion: apps.openshift.io/v1 45 | kind: DeploymentConfig 46 | metadata: 47 | name: app2 48 | spec: 49 | template: 50 | metadata: 51 | labels: 52 | app.kubernetes.io/name: app2 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: app2 58 | spec: 59 | ports: 60 | - name: 8080-tcp 61 | port: 8080 62 | selector: 63 | app.kubernetes.io/name: app -------------------------------------------------------------------------------- /tests/testdata/mychart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "mychart.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: {{ $fullName }} 12 | labels: 13 | {{- include "mychart.labels" . | nindent 4 }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ . }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /tests/checks/run-as-non-root.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: app1 10 | template: 11 | spec: 12 | containers: 13 | - name: app 14 | runAsUser: 1001 15 | securityContext: 16 | runAsNonRoot: true 17 | --- 18 | apiVersion: apps.openshift.io/v1 19 | kind: DeploymentConfig 20 | metadata: 21 | name: dont-fire 22 | spec: 23 | selector: 24 | app.kubernetes.io/name: app2 25 | template: 26 | spec: 27 | containers: 28 | - name: app2 29 | runAsUser: 1001 30 | securityContext: 31 | runAsNonRoot: true 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: app1 37 | spec: 38 | selector: 39 | matchLabels: 40 | app.kubernetes.io/name: app1 41 | template: 42 | spec: 43 | containers: 44 | - name: app 45 | runAsUser: 0 46 | --- 47 | apiVersion: apps.openshift.io/v1 48 | kind: DeploymentConfig 49 | metadata: 50 | name: app2 51 | spec: 52 | selector: 53 | app.kubernetes.io/name: app2 54 | template: 55 | spec: 56 | containers: 57 | - name: app2 58 | runAsUser: 0 -------------------------------------------------------------------------------- /pkg/templates/startupport/template.go: -------------------------------------------------------------------------------- 1 | package startupport 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | "golang.stackrox.io/kube-linter/pkg/templates" 9 | "golang.stackrox.io/kube-linter/pkg/templates/startupport/internal/params" 10 | "golang.stackrox.io/kube-linter/pkg/templates/util" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | const templateKey = "startup-port" 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Startup Port Exposed", 19 | Key: templateKey, 20 | Description: "Flag containers with an Startup probe to not exposed port.", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | ParseAndValidateParams: params.ParseAndValidate, 26 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 27 | return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 28 | return util.CheckProbePort(container, container.StartupProbe) 29 | }), nil 30 | }), 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/templates/livenessport/template.go: -------------------------------------------------------------------------------- 1 | package livenessport 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | "golang.stackrox.io/kube-linter/pkg/templates" 9 | "golang.stackrox.io/kube-linter/pkg/templates/livenessport/internal/params" 10 | "golang.stackrox.io/kube-linter/pkg/templates/util" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | const templateKey = "liveness-port" 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Liveness Port Exposed", 19 | Key: templateKey, 20 | Description: "Flag containers with an liveness probe to not exposed port.", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | ParseAndValidateParams: params.ParseAndValidate, 26 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 27 | return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 28 | return util.CheckProbePort(container, container.LivenessProbe) 29 | }), nil 30 | }), 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/templates/readinessport/template.go: -------------------------------------------------------------------------------- 1 | package readinessport 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 8 | "golang.stackrox.io/kube-linter/pkg/templates" 9 | "golang.stackrox.io/kube-linter/pkg/templates/readinessport/internal/params" 10 | "golang.stackrox.io/kube-linter/pkg/templates/util" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | const templateKey = "readiness-port" 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Readiness Port Not Exposed", 19 | Key: templateKey, 20 | Description: "Flag containers with an Readiness probe to not exposed port.", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | ParseAndValidateParams: params.ParseAndValidate, 26 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 27 | return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 28 | return util.CheckProbePort(container, container.ReadinessProbe) 29 | }), nil 30 | }), 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /tests/checks/statefulset-volumeclaimtemplate-annotation.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: good-sts 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: good 9 | serviceName: good 10 | template: 11 | metadata: 12 | labels: 13 | app: good 14 | spec: 15 | containers: 16 | - name: app 17 | image: busybox 18 | command: ["sleep", "3600"] 19 | volumeClaimTemplates: 20 | - metadata: 21 | name: data 22 | annotations: 23 | required-annotation: "true" 24 | spec: 25 | accessModes: ["ReadWriteOnce"] 26 | resources: 27 | requests: 28 | storage: 1Gi 29 | --- 30 | apiVersion: apps/v1 31 | kind: StatefulSet 32 | metadata: 33 | name: bad-sts 34 | spec: 35 | selector: 36 | matchLabels: 37 | app: bad 38 | serviceName: bad 39 | template: 40 | metadata: 41 | labels: 42 | app: bad 43 | spec: 44 | containers: 45 | - name: app 46 | image: busybox 47 | command: ["sleep", "3600"] 48 | volumeClaimTemplates: 49 | - metadata: 50 | name: data 51 | # required annotation missing 52 | spec: 53 | accessModes: ["ReadWriteOnce"] 54 | resources: 55 | requests: 56 | storage: 1Gi 57 | -------------------------------------------------------------------------------- /pkg/configresolver/config_resolver_test.go: -------------------------------------------------------------------------------- 1 | package configresolver 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/mitchellh/go-homedir" 9 | "github.com/stretchr/testify/assert" 10 | "golang.stackrox.io/kube-linter/pkg/config" 11 | ) 12 | 13 | func TestIgnorePaths(t *testing.T) { 14 | home, homeErr := homedir.Dir() 15 | if homeErr != nil { 16 | t.Fatal(homeErr) 17 | } 18 | wd, wdErr := os.Getwd() 19 | if wdErr != nil { 20 | t.Fatal(wdErr) 21 | } 22 | 23 | parent := filepath.Dir(wd) 24 | c := new(config.Config) 25 | 26 | var tests = []struct { 27 | Paths []string 28 | Expected string 29 | ErrorExpeted bool 30 | }{ 31 | {[]string{"~/test"}, home + "/test", false}, 32 | {[]string{"~/*.yaml"}, home + "/*.yaml", false}, 33 | {[]string{"~~/test"}, "", true}, 34 | {[]string{"../test"}, parent + "/test", false}, 35 | {[]string{"../*.yaml"}, parent + "/*.yaml", false}, 36 | {[]string{"~/test", "~/test"}, home + "/test", false}, 37 | } 38 | 39 | for _, e := range tests { 40 | c.Checks.IgnorePaths = e.Paths 41 | paths, err := GetIgnorePaths(c) 42 | 43 | if e.ErrorExpeted { 44 | assert.Error(t, err) 45 | } else { 46 | for _, path := range paths { 47 | assert.NoError(t, err) 48 | assert.Equal(t, e.Expected, path) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/lintcontext/mocks/scaledobject.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | kedaV1Alpha1 "golang.stackrox.io/kube-linter/pkg/crds/keda/v1alpha1" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | 11 | metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | // AddMockScaledObject adds a mock ScaledObject to LintContext 15 | func (l *MockLintContext) AddMockScaledObject(t *testing.T, name, version string) { 16 | require.NotEmpty(t, name) 17 | switch version { 18 | case "v1alpha1": 19 | l.objects[name] = &kedaV1Alpha1.ScaledObject{ 20 | TypeMeta: metaV1.TypeMeta{ 21 | Kind: objectkinds.ScaledObject, 22 | APIVersion: objectkinds.GetScaledObjectAPIVersion(version), 23 | }, 24 | ObjectMeta: metaV1.ObjectMeta{Name: name}, 25 | Spec: kedaV1Alpha1.ScaledObjectSpec{}, 26 | } 27 | default: 28 | require.FailNow(t, fmt.Sprintf("Unknown scaled object version %s", version)) 29 | } 30 | } 31 | 32 | // ModifyScaledObjectV1Alpha1 modifies a given ScaledObject in the context via the passed function. 33 | func (l *MockLintContext) ModifyScaledObjectV1Alpha1(t *testing.T, name string, f func(hpa *kedaV1Alpha1.ScaledObject)) { 34 | r, ok := l.objects[name].(*kedaV1Alpha1.ScaledObject) 35 | require.True(t, ok) 36 | f(r) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/checkregistry/check_registry.go: -------------------------------------------------------------------------------- 1 | package checkregistry 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.stackrox.io/kube-linter/pkg/config" 7 | "golang.stackrox.io/kube-linter/pkg/instantiatedcheck" 8 | ) 9 | 10 | // A CheckRegistry is a registry of checks. 11 | // It is not thread-safe. It is anticipated that checks will all be registered ahead of time 12 | // before calls to Load. 13 | type CheckRegistry interface { 14 | Register(checks ...*config.Check) error 15 | Load(name string) *instantiatedcheck.InstantiatedCheck 16 | } 17 | 18 | type checkRegistry map[string]*instantiatedcheck.InstantiatedCheck 19 | 20 | func (cr checkRegistry) Register(checks ...*config.Check) error { 21 | for _, c := range checks { 22 | instantiated, err := instantiatedcheck.ValidateAndInstantiate(c) 23 | if err != nil { 24 | return fmt.Errorf("invalid check %s: %w", c.Name, err) 25 | } 26 | if _, ok := cr[instantiated.Spec.Name]; ok { 27 | return fmt.Errorf("duplicate check name: %s", instantiated.Spec.Name) 28 | } 29 | cr[instantiated.Spec.Name] = instantiated 30 | } 31 | return nil 32 | } 33 | 34 | func (cr checkRegistry) Load(name string) *instantiatedcheck.InstantiatedCheck { 35 | return cr[name] 36 | } 37 | 38 | // New returns a ready-to-use, empty CheckRegistry. 39 | func New() CheckRegistry { 40 | return make(checkRegistry) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/templates/livenessprobe/template.go: -------------------------------------------------------------------------------- 1 | package livenessprobe 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.stackrox.io/kube-linter/pkg/check" 7 | "golang.stackrox.io/kube-linter/pkg/config" 8 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/livenessprobe/internal/params" 12 | "golang.stackrox.io/kube-linter/pkg/templates/util" 13 | v1 "k8s.io/api/core/v1" 14 | ) 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Liveness Probe Not Specified", 19 | Key: "liveness-probe", 20 | Description: "Flag containers that don't specify a liveness probe", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | 26 | ParseAndValidateParams: params.ParseAndValidate, 27 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 28 | return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 29 | if container.LivenessProbe == nil { 30 | return []diagnostic.Diagnostic{{Message: fmt.Sprintf("container %q does not specify a liveness probe", container.Name)}} 31 | } 32 | return nil 33 | }), nil 34 | }), 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/templates/readinessprobe/template.go: -------------------------------------------------------------------------------- 1 | package readinessprobe 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.stackrox.io/kube-linter/pkg/check" 7 | "golang.stackrox.io/kube-linter/pkg/config" 8 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/readinessprobe/internal/params" 12 | "golang.stackrox.io/kube-linter/pkg/templates/util" 13 | v1 "k8s.io/api/core/v1" 14 | ) 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Readiness Probe Not Specified", 19 | Key: "readiness-probe", 20 | Description: "Flag containers that don't specify a readiness probe", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | ParseAndValidateParams: params.ParseAndValidate, 26 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 27 | return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 28 | if container.ReadinessProbe == nil { 29 | return []diagnostic.Diagnostic{{Message: fmt.Sprintf("container %q does not specify a readiness probe", container.Name)}} 30 | } 31 | return nil 32 | }), nil 33 | }), 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/templates/hostipc/template.go: -------------------------------------------------------------------------------- 1 | package hostipc 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/extract" 8 | "golang.stackrox.io/kube-linter/pkg/lintcontext" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/hostipc/internal/params" 12 | ) 13 | 14 | func init() { 15 | templates.Register(check.Template{ 16 | HumanName: "Host IPC", 17 | Key: "host-ipc", 18 | Description: "Flag Pod sharing host's IPC namespace", 19 | SupportedObjectKinds: config.ObjectKindsDesc{ 20 | ObjectKinds: []string{objectkinds.DeploymentLike}, 21 | }, 22 | Parameters: params.ParamDescs, 23 | ParseAndValidateParams: params.ParseAndValidate, 24 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 25 | return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic { 26 | podSpec, found := extract.PodSpec(object.K8sObject) 27 | if !found { 28 | return nil 29 | } 30 | if podSpec.HostIPC { 31 | return []diagnostic.Diagnostic{{Message: "resource shares host's IPC namespace (via hostIPC=true)."}} 32 | } 33 | return nil 34 | }, nil 35 | }), 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/templates/hostpid/template.go: -------------------------------------------------------------------------------- 1 | package hostpid 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/extract" 8 | "golang.stackrox.io/kube-linter/pkg/lintcontext" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/hostpid/internal/params" 12 | ) 13 | 14 | func init() { 15 | templates.Register(check.Template{ 16 | HumanName: "Host PID", 17 | Key: "host-pid", 18 | Description: "Flag Pod sharing host's process namespace", 19 | SupportedObjectKinds: config.ObjectKindsDesc{ 20 | ObjectKinds: []string{objectkinds.DeploymentLike}, 21 | }, 22 | Parameters: params.ParamDescs, 23 | ParseAndValidateParams: params.ParseAndValidate, 24 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 25 | return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic { 26 | podSpec, found := extract.PodSpec(object.K8sObject) 27 | if !found { 28 | return nil 29 | } 30 | if podSpec.HostPID { 31 | return []diagnostic.Diagnostic{{Message: "object shares the host's process namespace (via hostPID=true)."}} 32 | } 33 | return nil 34 | }, nil 35 | }), 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/templates/hostnetwork/template.go: -------------------------------------------------------------------------------- 1 | package hostnetwork 2 | 3 | import ( 4 | "golang.stackrox.io/kube-linter/pkg/check" 5 | "golang.stackrox.io/kube-linter/pkg/config" 6 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 7 | "golang.stackrox.io/kube-linter/pkg/extract" 8 | "golang.stackrox.io/kube-linter/pkg/lintcontext" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/hostnetwork/internal/params" 12 | ) 13 | 14 | func init() { 15 | templates.Register(check.Template{ 16 | HumanName: "Host Network", 17 | Key: "host-network", 18 | Description: "Flag Pod sharing host's network namespace", 19 | SupportedObjectKinds: config.ObjectKindsDesc{ 20 | ObjectKinds: []string{objectkinds.DeploymentLike}, 21 | }, 22 | Parameters: params.ParamDescs, 23 | ParseAndValidateParams: params.ParseAndValidate, 24 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 25 | return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic { 26 | podSpec, found := extract.PodSpec(object.K8sObject) 27 | if !found { 28 | return nil 29 | } 30 | if podSpec.HostNetwork { 31 | return []diagnostic.Diagnostic{{Message: "resource shares host's network namespace (via hostNetwork=true)."}} 32 | } 33 | return nil 34 | }, nil 35 | }), 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /tests/checks/ssh-port.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dont-fire 6 | spec: 7 | replicas: 3 8 | template: 9 | spec: 10 | containers: 11 | - name: app 12 | ports: 13 | - containerPort: 2222 14 | protocol: TCP 15 | --- 16 | apiVersion: apps.openshift.io/v1 17 | kind: DeploymentConfig 18 | metadata: 19 | name: dont-fire 20 | spec: 21 | replicas: 3 22 | template: 23 | spec: 24 | containers: 25 | - name: app 26 | ports: 27 | - containerPort: 2222 28 | protocol: TCP 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: app1 34 | spec: 35 | replicas: 3 36 | template: 37 | spec: 38 | containers: 39 | - name: app 40 | ports: 41 | - containerPort: 22 42 | protocol: TCP 43 | --- 44 | apiVersion: apps.openshift.io/v1 45 | kind: DeploymentConfig 46 | metadata: 47 | name: app2 48 | spec: 49 | replicas: 3 50 | template: 51 | spec: 52 | containers: 53 | - name: app 54 | ports: 55 | - containerPort: 22 56 | protocol: TCP 57 | --- 58 | apiVersion: apps.openshift.io/v1 59 | kind: DeploymentConfig 60 | metadata: 61 | name: app3 62 | spec: 63 | replicas: 3 64 | template: 65 | spec: 66 | containers: 67 | - name: app-no-protocol 68 | ports: 69 | - containerPort: 22 -------------------------------------------------------------------------------- /pkg/templates/privileged/template.go: -------------------------------------------------------------------------------- 1 | package privileged 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.stackrox.io/kube-linter/pkg/check" 7 | "golang.stackrox.io/kube-linter/pkg/config" 8 | "golang.stackrox.io/kube-linter/pkg/diagnostic" 9 | "golang.stackrox.io/kube-linter/pkg/objectkinds" 10 | "golang.stackrox.io/kube-linter/pkg/templates" 11 | "golang.stackrox.io/kube-linter/pkg/templates/privileged/internal/params" 12 | "golang.stackrox.io/kube-linter/pkg/templates/util" 13 | v1 "k8s.io/api/core/v1" 14 | ) 15 | 16 | func init() { 17 | templates.Register(check.Template{ 18 | HumanName: "Privileged Containers", 19 | Key: "privileged", 20 | Description: "Flag privileged containers", 21 | SupportedObjectKinds: config.ObjectKindsDesc{ 22 | ObjectKinds: []string{objectkinds.DeploymentLike}, 23 | }, 24 | Parameters: params.ParamDescs, 25 | ParseAndValidateParams: params.ParseAndValidate, 26 | Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { 27 | return util.PerContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { 28 | if securityContext := container.SecurityContext; securityContext != nil { 29 | if securityContext.Privileged != nil && *securityContext.Privileged { 30 | return []diagnostic.Diagnostic{{Message: fmt.Sprintf("container %q is privileged", container.Name)}} 31 | } 32 | } 33 | return nil 34 | }), nil 35 | }), 36 | }) 37 | } 38 | --------------------------------------------------------------------------------