From 99c7ff877d2163290d0ce55f01e19997782aadde Mon Sep 17 00:00:00 2001 From: Cyril Jouve Date: Sat, 26 Nov 2022 08:25:46 +0100 Subject: [PATCH] Check that string probe port match container port --- docs/generated/checks.md | 9 ++ docs/generated/templates.md | 9 ++ e2etests/bats-tests.sh | 15 +++ internal/defaultchecks/default_checks.go | 1 + pkg/builtinchecks/yamls/probeport.yaml | 7 + pkg/templates/all/all.go | 1 + .../probeport/internal/params/gen-params.go | 52 ++++++++ .../probeport/internal/params/params.go | 5 + pkg/templates/probeport/template.go | 59 +++++++++ tests/checks/probe-port.yml | 122 ++++++++++++++++++ 10 files changed, 280 insertions(+) create mode 100644 pkg/builtinchecks/yamls/probeport.yaml create mode 100644 pkg/templates/probeport/internal/params/gen-params.go create mode 100644 pkg/templates/probeport/internal/params/params.go create mode 100644 pkg/templates/probeport/template.go create mode 100644 tests/checks/probe-port.yml diff --git a/docs/generated/checks.md b/docs/generated/checks.md index e98b0506a..b6aa48e2e 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -428,6 +428,15 @@ strategyTypeRegex: ^(RollingUpdate|Rolling)$ **Remediation**: Ensure privileged ports [0, 1024] are not mapped within containers. **Template**: [privileged-ports](templates.md#privileged-ports) +## probe-port + +**Enabled by default**: Yes + +**Description**: Alert on probe port that does not match a port defined in container ports + +**Remediation**: Ensure probe port matches a port defined in container ports. + +**Template**: [probe-port](templates.md#probe-port) ## read-secret-from-env-var **Enabled by default**: No diff --git a/docs/generated/templates.md b/docs/generated/templates.md index e2c38efba..09e5a952f 100644 --- a/docs/generated/templates.md +++ b/docs/generated/templates.md @@ -557,6 +557,15 @@ KubeLinter supports the following templates: **Supported Objects**: DeploymentLike +## Probe Port + +**Key**: `probe-port` + +**Description**: Flag unknown probe port + +**Supported Objects**: DeploymentLike + + ## Read-only Root Filesystems **Key**: `read-only-root-fs` diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index e78a9c412..e9c54f13b 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -608,6 +608,21 @@ get_value_from() { [[ "${count}" == "2" ]] } +@test "probe-port" { + tmp="tests/checks/probe-port.yml" + cmd="${KUBE_LINTER_BIN} lint --include probe-port --do-not-auto-add-defaults --format json ${tmp}" + run ${cmd} + + print_info "${status}" "${output}" "${cmd}" "${tmp}" + [ "$status" -eq 1 ] + + message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') + count=$(get_value_from "${lines[0]}" '.Reports | length') + + [[ "${message1}" == "Deployment: probe port \"bar\" does not match a port in container \"myapp\"." ]] + [[ "${count}" == "1" ]] +} + @test "read-secret-from-env-var" { tmp="tests/checks/read-secret-from-env-var.yml" cmd="${KUBE_LINTER_BIN} lint --include read-secret-from-env-var --do-not-auto-add-defaults --format json ${tmp}" diff --git a/internal/defaultchecks/default_checks.go b/internal/defaultchecks/default_checks.go index 9cd2497f1..8525f413c 100644 --- a/internal/defaultchecks/default_checks.go +++ b/internal/defaultchecks/default_checks.go @@ -26,6 +26,7 @@ var ( "ssh-port", "privilege-escalation-container", "privileged-container", + "probe-port", "run-as-non-root", "unsafe-sysctls", "unset-cpu-requirements", diff --git a/pkg/builtinchecks/yamls/probeport.yaml b/pkg/builtinchecks/yamls/probeport.yaml new file mode 100644 index 000000000..7f22a2aef --- /dev/null +++ b/pkg/builtinchecks/yamls/probeport.yaml @@ -0,0 +1,7 @@ +name: "probe-port" +description: "Alert on probe port that does not match a port defined in container ports" +remediation: "Ensure probe port matches a port defined in container ports." +scope: + objectKinds: + - DeploymentLike +template: "probe-port" diff --git a/pkg/templates/all/all.go b/pkg/templates/all/all.go index b9b024179..4814d302c 100644 --- a/pkg/templates/all/all.go +++ b/pkg/templates/all/all.go @@ -35,6 +35,7 @@ import ( _ "golang.stackrox.io/kube-linter/pkg/templates/privileged" _ "golang.stackrox.io/kube-linter/pkg/templates/privilegedports" _ "golang.stackrox.io/kube-linter/pkg/templates/privilegeescalation" + _ "golang.stackrox.io/kube-linter/pkg/templates/probeport" _ "golang.stackrox.io/kube-linter/pkg/templates/readinessprobe" _ "golang.stackrox.io/kube-linter/pkg/templates/readonlyrootfs" _ "golang.stackrox.io/kube-linter/pkg/templates/readsecret" diff --git a/pkg/templates/probeport/internal/params/gen-params.go b/pkg/templates/probeport/internal/params/gen-params.go new file mode 100644 index 000000000..21bb3aa5c --- /dev/null +++ b/pkg/templates/probeport/internal/params/gen-params.go @@ -0,0 +1,52 @@ +// Code generated by kube-linter template codegen. DO NOT EDIT. +// +build !templatecodegen + +package params + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "golang.stackrox.io/kube-linter/pkg/check" + "golang.stackrox.io/kube-linter/pkg/templates/util" +) + +var ( + // Use some imports in case they don't get used otherwise. + _ = util.MustParseParameterDesc + _ = fmt.Sprintf + + ParamDescs = []check.ParameterDesc{ + } +) + +func (p *Params) Validate() error { + var validationErrors []string + if len(validationErrors) > 0 { + return errors.Errorf("invalid parameters: %s", strings.Join(validationErrors, ", ")) + } + return nil +} + +// ParseAndValidate instantiates a Params object out of the passed map[string]interface{}, +// validates it, and returns it. +// The return type is interface{} to satisfy the type in the Template struct. +func ParseAndValidate(m map[string]interface{}) (interface{}, error) { + var p Params + if err := util.DecodeMapStructure(m, &p); err != nil { + return nil, err + } + if err := p.Validate(); err != nil { + return nil, err + } + return p, nil +} + +// WrapInstantiateFunc is a convenience wrapper that wraps an untyped instantiate function +// into a typed one. +func WrapInstantiateFunc(f func(p Params) (check.Func, error)) func (interface{}) (check.Func, error) { + return func(paramsInt interface{}) (check.Func, error) { + return f(paramsInt.(Params)) + } +} diff --git a/pkg/templates/probeport/internal/params/params.go b/pkg/templates/probeport/internal/params/params.go new file mode 100644 index 000000000..578cc3aa8 --- /dev/null +++ b/pkg/templates/probeport/internal/params/params.go @@ -0,0 +1,5 @@ +package params + +// Params represents the params accepted by this template. +type Params struct { +} diff --git a/pkg/templates/probeport/template.go b/pkg/templates/probeport/template.go new file mode 100644 index 000000000..fa3ed7315 --- /dev/null +++ b/pkg/templates/probeport/template.go @@ -0,0 +1,59 @@ +package probeport + +import ( + "fmt" + + "golang.stackrox.io/kube-linter/internal/set" + "golang.stackrox.io/kube-linter/pkg/check" + "golang.stackrox.io/kube-linter/pkg/config" + "golang.stackrox.io/kube-linter/pkg/diagnostic" + "golang.stackrox.io/kube-linter/pkg/objectkinds" + "golang.stackrox.io/kube-linter/pkg/templates" + "golang.stackrox.io/kube-linter/pkg/templates/probeport/internal/params" + "golang.stackrox.io/kube-linter/pkg/templates/util" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func init() { + templates.Register(check.Template{ + HumanName: "Probe Port", + Key: "probe-port", + Description: "Flag unknown probe port", + SupportedObjectKinds: config.ObjectKindsDesc{ + ObjectKinds: []string{objectkinds.DeploymentLike}, + }, + Parameters: params.ParamDescs, + ParseAndValidateParams: params.ParseAndValidate, + Instantiate: params.WrapInstantiateFunc(func(_ params.Params) (check.Func, error) { + return util.PerNonInitContainerCheck(func(container *v1.Container) []diagnostic.Diagnostic { + var portNames set.StringSet + for _, port := range container.Ports { + if name := port.Name; len(name) > 0 { + portNames.Add(name) + } + } + var results []diagnostic.Diagnostic + for _, probe := range []*v1.Probe{container.LivenessProbe, container.ReadinessProbe, container.StartupProbe} { + if probe == nil { + continue + } + var port intstr.IntOrString + if httpGet := probe.HTTPGet; httpGet != nil { + port = httpGet.Port + } else if tcpSocket := probe.TCPSocket; tcpSocket != nil { + port = tcpSocket.Port + } else { + continue + } + if port.Type == intstr.String && !portNames.Contains(port.StrVal) && port.IntValue() == 0 { + results = append(results, diagnostic.Diagnostic{ + Message: fmt.Sprintf("probe port %q does not match a port in container %q.", port.StrVal, container.Name), + }) + } + } + return results + }), nil + }), + }) +} diff --git a/tests/checks/probe-port.yml b/tests/checks/probe-port.yml new file mode 100644 index 000000000..122f50731 --- /dev/null +++ b/tests/checks/probe-port.yml @@ -0,0 +1,122 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: no-probe +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myimage + resources: + limits: + memory: "128Mi" + cpu: "500m" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: livenessProbe-int +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myimage + resources: + limits: + memory: "128Mi" + cpu: "500m" + livenessProbe: + httpGet: + port: 1234 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: readinessProbe-int-str +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myimage + resources: + limits: + memory: "128Mi" + cpu: "500m" + readinessProbe: + tcpCheck: + port: "1234" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: startupProbe-str-container-port-ok +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myimage + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 1234 + name: foo + startupProbe: + httpGet: + port: foo +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: startupProbe-str-container-port-ko +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myimage + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 1234 + name: foo + startupProbe: + httpGet: + port: bar