From 905430f6448213e56c335613fb96ed7bb9fd4510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Mon, 14 Oct 2024 23:31:30 +0100 Subject: [PATCH 1/2] [feat] - add prom analyzer --- cmd/analyze.go | 3 + internal/analyzers/operator.go | 16 +- internal/analyzers/prometheus.go | 261 +++++++++++++ internal/analyzers/prometheus_test.go | 531 ++++++++++++++++++++++++++ internal/k8sutil/k8sutil.go | 14 + 5 files changed, 810 insertions(+), 15 deletions(-) create mode 100644 internal/analyzers/prometheus.go create mode 100644 internal/analyzers/prometheus_test.go diff --git a/cmd/analyze.go b/cmd/analyze.go index fcdaac0..7f850ca 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -30,6 +30,7 @@ type AnalyzeKind string const ( ServiceMonitor AnalyzeKind = "servicemonitor" Operator AnalyzeKind = "operator" + Prometheus AnalyzeKind = "prometheus" ) type AnalyzeFlags struct { @@ -78,6 +79,8 @@ func run(cmd *cobra.Command, _ []string) error { return analyzers.RunServiceMonitorAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) case Operator: return analyzers.RunOperatorAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) + case Prometheus: + return analyzers.RunPrometheusAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) default: return fmt.Errorf("kind %s not supported", analyzerFlags.Kind) } diff --git a/internal/analyzers/operator.go b/internal/analyzers/operator.go index fd42f79..28fd90e 100644 --- a/internal/analyzers/operator.go +++ b/internal/analyzers/operator.go @@ -38,8 +38,7 @@ func RunOperatorAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, na return fmt.Errorf("failed to list RoleBindings: %w", err) } - // Check if the ServiceAccount is bound to any ClusterRoleBindings - if !isServiceAccountBoundToRoleBindingList(cRb, op.Spec.Template.Spec.ServiceAccountName) { + if !k8sutil.IsServiceAccountBoundToRoleBindingList(cRb, op.Spec.Template.Spec.ServiceAccountName) { return fmt.Errorf("ServiceAccount %s is not bound to any RoleBindings", op.Spec.Template.Spec.ServiceAccountName) } @@ -101,16 +100,3 @@ func analyzeCRDRules(ctx context.Context, clientSets *k8sutil.ClientSets, crb v1 } return nil } - -func isServiceAccountBoundToRoleBindingList(clusterRoleBindings *v1.ClusterRoleBindingList, serviceAccountName string) bool { - for _, roleBinding := range clusterRoleBindings.Items { - if roleBinding.Subjects != nil { - for _, subject := range roleBinding.Subjects { - if subject.Kind == "ServiceAccount" && subject.Name == serviceAccountName { - return true - } - } - } - } - return false -} diff --git a/internal/analyzers/prometheus.go b/internal/analyzers/prometheus.go new file mode 100644 index 0000000..674568f --- /dev/null +++ b/internal/analyzers/prometheus.go @@ -0,0 +1,261 @@ +// Copyright 2024 The prometheus-operator Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "github.com/prometheus-operator/poctl/internal/k8sutil" + v1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +const ( + ServiceMonitor = "ServiceMonitor" + PodMonitor = "PodMonitor" + Probe = "Probe" + ScrapeConfig = "ScrapeConfig" + PrometheusRule = "PrometheusRule" +) + +func RunPrometheusAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, name, namespace string) error { + prometheus, err := clientSets.MClient.MonitoringV1().Prometheuses(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return fmt.Errorf("prometheus %s not found in namespace %s", name, namespace) + } + return fmt.Errorf("error while getting Prometheus: %v", err) + } + + cRb, err := clientSets.KClient.RbacV1().ClusterRoleBindings().List(ctx, metav1.ListOptions{ + LabelSelector: "prometheus=prometheus", + }) + if err != nil { + return fmt.Errorf("failed to list RoleBindings: %w", err) + } + + if !k8sutil.IsServiceAccountBoundToRoleBindingList(cRb, prometheus.Spec.ServiceAccountName) { + return fmt.Errorf("serviceAccount %s is not bound to any RoleBindings", prometheus.Spec.ServiceAccountName) + } + + for _, crb := range cRb.Items { + cr, err := clientSets.KClient.RbacV1().ClusterRoles().Get(ctx, crb.RoleRef.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get ClusterRole %s", crb.RoleRef.Name) + } + + err = checkClusterRoleRules(crb, cr) + if err != nil { + return err + } + } + + if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.PodMonitorNamespaceSelector); err != nil { + return fmt.Errorf("podMonitorNamespaceSelector is not properly defined: %s", err) + } + + if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ProbeNamespaceSelector); err != nil { + return fmt.Errorf("probeNamespaceSelector is not properly defined: %s", err) + } + + if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorNamespaceSelector); err != nil { + return fmt.Errorf("serviceMonitorNamespaceSelector is not properly defined: %s", err) + } + + if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigNamespaceSelector); err != nil { + return fmt.Errorf("scrapeConfigNamespaceSelector is not properly defined: %s", err) + } + + if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.RuleNamespaceSelector); err != nil { + return fmt.Errorf("ruleNamespaceSelector is not properly defined: %s", err) + } + + if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorSelector, ServiceMonitor, namespace); err != nil { + return fmt.Errorf("serviceMonitor is not properly defined: %s", err) + } + + if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.PodMonitorSelector, PodMonitor, namespace); err != nil { + return fmt.Errorf("podMonitor is not properly defined: %s", err) + } + + if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ProbeSelector, Probe, namespace); err != nil { + return fmt.Errorf("probe is not properly defined: %s", err) + } + + if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigSelector, ScrapeConfig, namespace); err != nil { + return fmt.Errorf("scrapeConfig is not properly defined: %s", err) + } + + if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.RuleSelector, PrometheusRule, namespace); err != nil { + return fmt.Errorf("prometheusRule is not properly defined: %s", err) + } + + slog.Info("Prometheus is compliant, no issues found", "name", name, "namespace", namespace) + return nil +} + +func checkClusterRoleRules(crb v1.ClusterRoleBinding, cr *v1.ClusterRole) error { + var errs []string + verbsToCheck := []string{"get", "list", "watch"} + missingVerbs := []string{} + + for _, rule := range cr.Rules { + for _, resource := range rule.Resources { + found := false + if resource == "configmaps" { + for _, verb := range rule.Verbs { + if verb == "get" { + found = true + break + } + } + if !found { + errs = append(errs, fmt.Sprintf("ClusterRole %s does not include 'configmaps' with 'get' in its verbs", crb.RoleRef.Name)) + } + continue + } + for range rule.APIGroups { + for _, requiredVerb := range verbsToCheck { + found := false + for _, verb := range rule.Verbs { + if verb == requiredVerb { + found = true + break + } + } + if !found { + missingVerbs = append(missingVerbs, requiredVerb) + } + } + if len(missingVerbs) > 0 { + errs = append(errs, fmt.Sprintf("ClusterRole %s is missing necessary verbs for APIGroups: %v", crb.RoleRef.Name, missingVerbs)) + } + } + } + for _, nonResource := range rule.NonResourceURLs { + if nonResource == "/metrics" { + hasGet := false + for _, verb := range rule.Verbs { + if verb == "get" { + hasGet = true + break + } + } + if !hasGet { + errs = append(errs, fmt.Sprintf("ClusterRole %s does not include 'get' verb for NonResourceURL '/metrics'", crb.RoleRef.Name)) + } + } + } + } + + if len(errs) > 0 { + return fmt.Errorf("multiple errors found:\n%s", strings.Join(errs, "\n")) + } + return nil +} + +func checkPrometheusNamespaceSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector) error { + if labelSelector == nil { + return nil + } + + if len(labelSelector.MatchLabels) == 0 && len(labelSelector.MatchExpressions) == 0 { + return nil + } + + labelMap, err := metav1.LabelSelectorAsMap(labelSelector) + if err != nil { + return fmt.Errorf("invalid label selector format in %s: %v", labelSelector, err) + } + + namespaces, err := clientSets.KClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + + if err != nil { + return fmt.Errorf("failed to list Namespaces in %s: %v", labelSelector, err) + } + + if len(namespaces.Items) == 0 { + return fmt.Errorf("no namespaces match the selector %s", labelSelector) + } + + return nil +} + +func checkPrometheusServiceSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector, resourceName, namespace string) error { + if labelSelector == nil { + return fmt.Errorf("%s selector is not defined", resourceName) + } + + if len(labelSelector.MatchLabels) == 0 && len(labelSelector.MatchExpressions) == 0 { + return nil + } + + labelMap, err := metav1.LabelSelectorAsMap(labelSelector) + if err != nil { + return fmt.Errorf("invalid label selector format in %s: %v", resourceName, err) + } + + switch resourceName { + case ServiceMonitor: + serviceMonitors, err := clientSets.MClient.MonitoringV1().ServiceMonitors(namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + if err != nil { + return fmt.Errorf("failed to list ServiceMonitors in %s: %v", namespace, err) + } + if len(serviceMonitors.Items) == 0 { + return fmt.Errorf("no ServiceMonitors match the provided selector in Prometheus %s", namespace) + } + case PodMonitor: + podMonitors, err := clientSets.MClient.MonitoringV1().PodMonitors(namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + if err != nil { + return fmt.Errorf("failed to list PodMonitor in %s: %v", namespace, err) + } + if len(podMonitors.Items) == 0 { + return fmt.Errorf("no PodMonitors match the provided selector in Prometheus %s", namespace) + } + case Probe: + probes, err := clientSets.MClient.MonitoringV1().Probes(namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + if err != nil { + return fmt.Errorf("failed to list Probes in %s: %v", namespace, err) + } + if len(probes.Items) == 0 { + return fmt.Errorf("no Probes match the provided selector in Prometheus %s", namespace) + } + case ScrapeConfig: + scrapeConfigs, err := clientSets.MClient.MonitoringV1alpha1().ScrapeConfigs(namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + if err != nil { + return fmt.Errorf("failed to list ScrapeConfigs in %s: %v", namespace, err) + } + if len(scrapeConfigs.Items) == 0 { + return fmt.Errorf("no ScrapeConfigs match the provided selector in Prometheus %s", namespace) + } + case PrometheusRule: + promRules, err := clientSets.MClient.MonitoringV1().PrometheusRules(namespace).List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()}) + if err != nil { + return fmt.Errorf("failed to list Probes in %s: %v", namespace, err) + } + if len(promRules.Items) == 0 { + return fmt.Errorf("no PrometheusRules match the provided selector in Prometheus %s", namespace) + } + default: + return fmt.Errorf("unknown selector type: %s", resourceName) + } + + return nil +} diff --git a/internal/analyzers/prometheus_test.go b/internal/analyzers/prometheus_test.go new file mode 100644 index 0000000..cfa374b --- /dev/null +++ b/internal/analyzers/prometheus_test.go @@ -0,0 +1,531 @@ +// Copyright 2024 The prometheus-operator Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "context" + "testing" + + "github.com/prometheus-operator/poctl/internal/k8sutil" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + monitoringv1alpha1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1" + monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" +) + +func getPrometheusClusterRoleBinding(namespace string) []rbacv1.ClusterRoleBinding { + return []rbacv1.ClusterRoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "prometheus", + Labels: map[string]string{ + "prometheus": "prometheus", + }, + }, + RoleRef: rbacv1.RoleRef{ + Name: "prometheus", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "prometheus", + Namespace: namespace, + }, + }, + }, + } +} + +func TestPrometheusAnalyzer(t *testing.T) { + type testCase struct { + name string + namespace string + getMockedClientSets func(tc testCase) k8sutil.ClientSets + shouldFail bool + } + + tests := []testCase{ + { + name: "PrometheusRoleBindingListError", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&rbacv1.ClusterRoleBindingList{}) + kClient.PrependReactor("list", "clusterrolebindings", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.NewInternalError(nil) + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "PrometheusServiceAccountNotFound", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&rbacv1.ClusterRoleBindingList{}) + kClient.PrependReactor("list", "clusterrolebindings", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRoleBindingList{ + Items: getPrometheusClusterRoleBinding(tc.namespace), + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "ConfigMapsVerbsNotFoundInClusterRole", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&rbacv1.ClusterRoleBindingList{}) + kClient.PrependReactor("list", "clusterrolebindings", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRoleBindingList{ + Items: getPrometheusClusterRoleBinding(tc.namespace), + }, nil + }) + + kClient.PrependReactor("get", "clusterroles", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "prometheus", + }, + Rules: []rbacv1.PolicyRule{ + { + Resources: []string{"configmaps"}, + Verbs: []string{"list", "watch"}, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "RequiredVerbsNotFoundInClusterRole", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&rbacv1.ClusterRoleBindingList{}) + kClient.PrependReactor("list", "clusterrolebindings", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRoleBindingList{ + Items: getPrometheusClusterRoleBinding(tc.namespace), + }, nil + }) + + kClient.PrependReactor("get", "clusterroles", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "prometheus", + }, + Rules: []rbacv1.PolicyRule{ + { + Resources: []string{"nodes", "pods"}, + Verbs: []string{"list", "watch"}, + APIGroups: []string{""}, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "NonResourceURLsNotFoundInClusterRole", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&rbacv1.ClusterRoleBindingList{}) + kClient.PrependReactor("list", "clusterrolebindings", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRoleBindingList{ + Items: getPrometheusClusterRoleBinding(tc.namespace), + }, nil + }) + + kClient.PrependReactor("get", "clusterroles", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "prometheus", + }, + Rules: []rbacv1.PolicyRule{ + { + NonResourceURLs: []string{"/metrics"}, + Verbs: []string{"post"}, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "NamespaceSelectorNull", + namespace: "test", + shouldFail: false, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ScrapeConfigNamespaceSelector: nil, + }, + }, + }, nil + }) + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "NamespaceSelectorEmpty", + namespace: "test", + shouldFail: false, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + PodMonitorNamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{}, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, nil + }) + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "NamespaceSelectorWithoutMatchLabels", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ServiceMonitorNamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"environment": "test"}, + }, + }, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&corev1.Namespace{}) + kClient.PrependReactor("list", "namespaces", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &corev1.NamespaceList{ + Items: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + Labels: map[string]string{"another": "label"}, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "NamespaceSelectorWithtMatchLabels", + namespace: "test", + shouldFail: false, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ProbeNamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"environment": "test"}, + }, + }, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&corev1.Namespace{}) + kClient.PrependReactor("list", "namespaces", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &corev1.NamespaceList{ + Items: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + Labels: map[string]string{"environment": "test"}, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "ServiceSelectorsEmpty", + namespace: "test", + shouldFail: false, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + PodMonitorSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{}, + MatchExpressions: []metav1.LabelSelectorRequirement{}, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "ServiceSelectorsNull", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ServiceMonitorSelector: nil, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "ServiceSelectorsWithoutMatchLabels", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ScrapeConfigSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "label"}, + }, + }, + }, + }, nil + }) + + mClient.PrependReactor("list", "scrapeconfigs", func(_ clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, &monitoringv1alpha1.ScrapeConfigList{ + Items: []*monitoringv1alpha1.ScrapeConfig{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "scrapeconfig-crd", + Namespace: tc.namespace, + Labels: map[string]string{"service": "notest"}, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "ServiceSelectorsWithMatchLabels", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PrometheusList{}) + mClient.PrependReactor("get", "prometheuses", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: monitoringv1.PrometheusSpec{ + CommonPrometheusFields: monitoringv1.CommonPrometheusFields{ + ProbeSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "label"}, + }, + }, + }, + }, nil + }) + + mClient.PrependReactor("list", "probes", func(_ clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, &monitoringv1.ProbeList{ + Items: []*monitoringv1.Probe{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "probes-crd", + Namespace: tc.namespace, + Labels: map[string]string{"app": "label"}, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + clientSets := tc.getMockedClientSets(tc) + err := RunPrometheusAnalyzer(context.Background(), &clientSets, tc.name, tc.namespace) + if tc.shouldFail { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/internal/k8sutil/k8sutil.go b/internal/k8sutil/k8sutil.go index 7a3102d..3dd6a56 100644 --- a/internal/k8sutil/k8sutil.go +++ b/internal/k8sutil/k8sutil.go @@ -37,6 +37,7 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + v1 "k8s.io/api/rbac/v1" ) var ApplyOption = metav1.ApplyOptions{ @@ -144,3 +145,16 @@ func GetClientSets(kubeconfig string) (*ClientSets, error) { APIExtensionsClient: apiExtensions, }, nil } + +func IsServiceAccountBoundToRoleBindingList(clusterRoleBindings *v1.ClusterRoleBindingList, serviceAccountName string) bool { + for _, roleBinding := range clusterRoleBindings.Items { + if roleBinding.Subjects != nil { + for _, subject := range roleBinding.Subjects { + if subject.Kind == "ServiceAccount" && subject.Name == serviceAccountName { + return true + } + } + } + } + return false +} From d07ad3ec8873d48240d2f4d4220a8f62ec603870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Mon, 4 Nov 2024 10:55:15 +0000 Subject: [PATCH 2/2] [fix] - rename func --- internal/analyzers/prometheus.go | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/analyzers/prometheus.go b/internal/analyzers/prometheus.go index 674568f..9919a2a 100644 --- a/internal/analyzers/prometheus.go +++ b/internal/analyzers/prometheus.go @@ -67,44 +67,44 @@ func RunPrometheusAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, } } - if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.PodMonitorNamespaceSelector); err != nil { + if err := checkResourceNamespaceSelectors(ctx, clientSets, prometheus.Spec.PodMonitorNamespaceSelector); err != nil { return fmt.Errorf("podMonitorNamespaceSelector is not properly defined: %s", err) } - if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ProbeNamespaceSelector); err != nil { + if err := checkResourceNamespaceSelectors(ctx, clientSets, prometheus.Spec.ProbeNamespaceSelector); err != nil { return fmt.Errorf("probeNamespaceSelector is not properly defined: %s", err) } - if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorNamespaceSelector); err != nil { + if err := checkResourceNamespaceSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorNamespaceSelector); err != nil { return fmt.Errorf("serviceMonitorNamespaceSelector is not properly defined: %s", err) } - if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigNamespaceSelector); err != nil { + if err := checkResourceNamespaceSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigNamespaceSelector); err != nil { return fmt.Errorf("scrapeConfigNamespaceSelector is not properly defined: %s", err) } - if err := checkPrometheusNamespaceSelectors(ctx, clientSets, prometheus.Spec.RuleNamespaceSelector); err != nil { + if err := checkResourceNamespaceSelectors(ctx, clientSets, prometheus.Spec.RuleNamespaceSelector); err != nil { return fmt.Errorf("ruleNamespaceSelector is not properly defined: %s", err) } - if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorSelector, ServiceMonitor, namespace); err != nil { - return fmt.Errorf("serviceMonitor is not properly defined: %s", err) + if err := checkResourceLabelSelectors(ctx, clientSets, prometheus.Spec.ServiceMonitorSelector, ServiceMonitor, namespace); err != nil { + return fmt.Errorf("serviceMonitorSelector is not properly defined: %s", err) } - if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.PodMonitorSelector, PodMonitor, namespace); err != nil { - return fmt.Errorf("podMonitor is not properly defined: %s", err) + if err := checkResourceLabelSelectors(ctx, clientSets, prometheus.Spec.PodMonitorSelector, PodMonitor, namespace); err != nil { + return fmt.Errorf("podMonitorSelector is not properly defined: %s", err) } - if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ProbeSelector, Probe, namespace); err != nil { - return fmt.Errorf("probe is not properly defined: %s", err) + if err := checkResourceLabelSelectors(ctx, clientSets, prometheus.Spec.ProbeSelector, Probe, namespace); err != nil { + return fmt.Errorf("probeSelector is not properly defined: %s", err) } - if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigSelector, ScrapeConfig, namespace); err != nil { - return fmt.Errorf("scrapeConfig is not properly defined: %s", err) + if err := checkResourceLabelSelectors(ctx, clientSets, prometheus.Spec.ScrapeConfigSelector, ScrapeConfig, namespace); err != nil { + return fmt.Errorf("scrapeConfigSelector is not properly defined: %s", err) } - if err := checkPrometheusServiceSelectors(ctx, clientSets, prometheus.Spec.RuleSelector, PrometheusRule, namespace); err != nil { - return fmt.Errorf("prometheusRule is not properly defined: %s", err) + if err := checkResourceLabelSelectors(ctx, clientSets, prometheus.Spec.RuleSelector, PrometheusRule, namespace); err != nil { + return fmt.Errorf("ruleSelector is not properly defined: %s", err) } slog.Info("Prometheus is compliant, no issues found", "name", name, "namespace", namespace) @@ -171,7 +171,7 @@ func checkClusterRoleRules(crb v1.ClusterRoleBinding, cr *v1.ClusterRole) error return nil } -func checkPrometheusNamespaceSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector) error { +func checkResourceNamespaceSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector) error { if labelSelector == nil { return nil } @@ -198,7 +198,7 @@ func checkPrometheusNamespaceSelectors(ctx context.Context, clientSets *k8sutil. return nil } -func checkPrometheusServiceSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector, resourceName, namespace string) error { +func checkResourceLabelSelectors(ctx context.Context, clientSets *k8sutil.ClientSets, labelSelector *metav1.LabelSelector, resourceName, namespace string) error { if labelSelector == nil { return fmt.Errorf("%s selector is not defined", resourceName) }