Skip to content

Commit

Permalink
add dangling service monitor check (#547)
Browse files Browse the repository at this point in the history
Co-authored-by: abrad3 <[email protected]>
  • Loading branch information
abrad3 and abrad3 authored Oct 9, 2023
1 parent a36b6ee commit 4f5840b
Show file tree
Hide file tree
Showing 15 changed files with 541 additions and 1 deletion.
9 changes: 9 additions & 0 deletions docs/generated/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ verbs:
**Remediation**: Confirm that your service's selector correctly matches the labels on one of your deployments.
**Template**: [dangling-service](templates.md#dangling-services)
## dangling-servicemonitor
**Enabled by default**: No
**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).
**Remediation**: Check selectors and your services.
**Template**: [dangling-servicemonitor](templates.md#dangling-service-monitor)
## default-service-account
**Enabled by default**: No
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ KubeLinter supports the following templates:
type: array
```

## Dangling Service Monitor

**Key**: `dangling-servicemonitor`

**Description**: Flag service monitors which do not match any service

**Supported Objects**: ServiceMonitor


## Deprecated Service Account Field

**Key**: `deprecated-service-account-field`
Expand Down
22 changes: 22 additions & 0 deletions e2etests/bats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,28 @@ get_value_from() {
[[ "${count}" == "2" ]]
}

@test "dangling-servicemonitor" {
tmp="tests/checks/dangling-servicemonitor.yml"
cmd="${KUBE_LINTER_BIN} lint --include dangling-servicemonitor --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')
message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message')
message3=$(get_value_from "${lines[0]}" '.Reports[2].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[2].Diagnostic.Message')
message4=$(get_value_from "${lines[0]}" '.Reports[3].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[3].Diagnostic.Message')

count=$(get_value_from "${lines[0]}" '.Reports | length')

[[ "${message1}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app) and namespace selector ([])" ]]
[[ "${message2}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app) and namespace selector ([])" ]]
[[ "${message3}" == "ServiceMonitor: no services found matching the service monitor's label selector () and namespace selector ([test2])" ]]
[[ "${message4}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app1) and namespace selector ([test2])" ]]
[[ "${count}" == "4" ]]
}

@test "default-service-account" {
tmp="tests/checks/default-service-account.yml"
cmd="${KUBE_LINTER_BIN} lint --include default-service-account --do-not-auto-add-defaults --format json ${tmp}"
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/openshift/api v0.0.0-20230406152840-ce21e3fe5da2
github.com/owenrumney/go-sarif/v2 v2.2.2
github.com/pkg/errors v0.9.1
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.16.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1 h1:9bqwpJD3v8eLW5szVHLAY7oASwo0sOBb5IoJEqN+Jxk=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1/go.mod h1:xcfWyzl4BpEe5jnVkw7D1yCHU7GHjfjCERJsEfGbpSU=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.4.3 h1:P6NALOLV8BrWhm6PsqOraUK05E5h8IZnpXYJ+CIg+0U=
Expand Down
7 changes: 7 additions & 0 deletions pkg/builtinchecks/yamls/dangling-servicemonitor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: "dangling-servicemonitor"
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)."
remediation: "Check selectors and your services."
scope:
objectKinds:
- ServiceMonitor
template: "dangling-servicemonitor"
30 changes: 30 additions & 0 deletions pkg/lintcontext/mocks/servicemonitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mocks

import (
"testing"

k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/stretchr/testify/require"
"golang.stackrox.io/kube-linter/pkg/objectkinds"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// AddMockServiceMonitor adds a mock ServiceMonitor to LintContext
func (l *MockLintContext) AddMockServiceMonitor(t *testing.T, name string) {
require.NotEmpty(t, name)
l.objects[name] = &k8sMonitoring.ServiceMonitor{
TypeMeta: metaV1.TypeMeta{
Kind: objectkinds.ServiceMonitor,
APIVersion: objectkinds.GetServiceMonitorAPIVersion(),
},
ObjectMeta: metaV1.ObjectMeta{Name: name},
Spec: k8sMonitoring.ServiceMonitorSpec{},
}
}

// ModifyServiceMonitor modifies a given servicemonitor in the context via the passed function
func (l *MockLintContext) ModifyServiceMonitor(t *testing.T, name string, f func(servicemonitor *k8sMonitoring.ServiceMonitor)) {
r, ok := l.objects[name].(*k8sMonitoring.ServiceMonitor)
require.True(t, ok)
f(r)
}
3 changes: 2 additions & 1 deletion pkg/lintcontext/parse_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
y "github.com/ghodss/yaml"
ocsAppsV1 "github.com/openshift/api/apps/v1"
"github.com/pkg/errors"
k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"golang.stackrox.io/kube-linter/pkg/k8sutil"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
Expand Down Expand Up @@ -41,7 +42,7 @@ func init() {
clientScheme := scheme.Scheme

// Add OpenShift and Autoscaling schema
schemeBuilder := runtime.NewSchemeBuilder(ocsAppsV1.AddToScheme, autoscalingV2Beta1.AddToScheme)
schemeBuilder := runtime.NewSchemeBuilder(ocsAppsV1.AddToScheme, autoscalingV2Beta1.AddToScheme, k8sMonitoring.AddToScheme)
if err := schemeBuilder.AddToScheme(clientScheme); err != nil {
panic(fmt.Sprintf("Can not add OpenShift schema %v", err))
}
Expand Down
26 changes: 26 additions & 0 deletions pkg/objectkinds/serviceMonitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package objectkinds

import (
k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
// ServiceMonitor represents Prometheus Service Monitor objects.
ServiceMonitor = k8sMonitoring.ServiceMonitorsKind
)

var (
serviceMonitorGVK = k8sMonitoring.SchemeGroupVersion.WithKind(ServiceMonitor)
)

func init() {
RegisterObjectKind(ServiceMonitor, MatcherFunc(func(gvk schema.GroupVersionKind) bool {
return gvk == serviceMonitorGVK
}))
}

// GetServiceMonitorAPIVersion returns servicemonitor's apiversion
func GetServiceMonitorAPIVersion() string {
return serviceMonitorGVK.GroupVersion().String()
}
1 change: 1 addition & 0 deletions pkg/templates/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
_ "golang.stackrox.io/kube-linter/pkg/templates/danglingnetworkpolicy"
_ "golang.stackrox.io/kube-linter/pkg/templates/danglingnetworkpolicypeer"
_ "golang.stackrox.io/kube-linter/pkg/templates/danglingservice"
_ "golang.stackrox.io/kube-linter/pkg/templates/danglingservicemonitor"
_ "golang.stackrox.io/kube-linter/pkg/templates/deprecatedserviceaccount"
_ "golang.stackrox.io/kube-linter/pkg/templates/disallowedgvk"
_ "golang.stackrox.io/kube-linter/pkg/templates/dnsconfigoptions"
Expand Down
52 changes: 52 additions & 0 deletions pkg/templates/danglingservicemonitor/internal/params/gen-params.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package params

// Params represents the params accepted by this template.
type Params struct {
}
88 changes: 88 additions & 0 deletions pkg/templates/danglingservicemonitor/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package danglingservicemonitor

import (
"fmt"

k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"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/lintcontext"
"golang.stackrox.io/kube-linter/pkg/objectkinds"
"golang.stackrox.io/kube-linter/pkg/templates"
"golang.stackrox.io/kube-linter/pkg/templates/danglingservicemonitor/internal/params"
v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)

func init() {
templates.Register(check.Template{
HumanName: "Dangling Service Monitor",
Key: "dangling-servicemonitor",
Description: "Flag service monitors which do not match any service",
SupportedObjectKinds: config.ObjectKindsDesc{
ObjectKinds: []string{objectkinds.ServiceMonitor},
},
Parameters: params.ParamDescs,
ParseAndValidateParams: params.ParseAndValidate,
Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) {
return func(lintCtx lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
serviceMonitor, ok := object.K8sObject.(*k8sMonitoring.ServiceMonitor)
if !ok {
return nil
}
nsSelector := serviceMonitor.Spec.NamespaceSelector
nsSelectorSet := len(nsSelector.MatchNames) != 0 || nsSelector.Any

labelSelectors := serviceMonitor.Spec.Selector.MatchLabels
labelSelectorSet := len(labelSelectors) != 0
if !labelSelectorSet && !nsSelectorSet {
return []diagnostic.Diagnostic{{
Message: "service monitor has no selector specified",
}}
}
labelSelector, err := metaV1.LabelSelectorAsSelector(&metaV1.LabelSelector{MatchLabels: serviceMonitor.Spec.Selector.MatchLabels})
if err != nil {
return []diagnostic.Diagnostic{{
Message: fmt.Sprintf("service monitor has invalid label selector: %v", err),
}}
}
for _, obj := range lintCtx.Objects() {
services, found := obj.K8sObject.(*v1.Service)
if !found {
continue
}
if checkNamespaceSelector(nsSelector, services) {
if !labelSelectorSet {
return nil
}
if labelSelectorSet && labelSelector.Matches(labels.Set(services.Labels)) {
return nil
} else {
continue
}
}
if labelSelector.Matches(labels.Set(services.Labels)) && labelSelectorSet && !nsSelectorSet {
// Found!
return nil
}

}
return []diagnostic.Diagnostic{{Message: fmt.Sprintf("no services found matching the service monitor's label selector (%s) and namespace selector (%s)", labelSelector, nsSelector.MatchNames)}}
}, nil
}),
})
}

func checkNamespaceSelector(namespaceSelector k8sMonitoring.NamespaceSelector, service *v1.Service) bool {
if namespaceSelector.Any {
return true
}
for _, ns := range namespaceSelector.MatchNames {
if ns == service.Namespace {
return true
}
}
return false
}
Loading

0 comments on commit 4f5840b

Please sign in to comment.