Skip to content

Commit 5c9ea12

Browse files
committed
Ensure ServiceMonitor's endpoints are protected
1 parent 848143e commit 5c9ea12

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

test/extended/prometheus/prometheus.go

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8+
"os"
89
"regexp"
910
"strings"
1011
"time"
@@ -14,6 +15,8 @@ import (
1415

1516
g "github.com/onsi/ginkgo/v2"
1617
o "github.com/onsi/gomega"
18+
prometheusoperatorv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
19+
prometheusoperatorv1client "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
1720
promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
1821
dto "github.com/prometheus/client_model/go"
1922
"github.com/prometheus/common/expfmt"
@@ -49,6 +52,97 @@ type TelemeterClientConfig struct {
4952
Enabled *bool `json:"enabled"`
5053
}
5154

55+
var componentNamespace = os.Getenv("COMPONENT_NAMESPACE")
56+
57+
var _ = g.Describe("[sig-instrumentation][Late] OpenShift service monitors [apigroup:image.openshift.io]", func() {
58+
defer g.GinkgoRecover()
59+
var (
60+
oc = exutil.NewCLIWithoutNamespace("prometheus")
61+
prometheusURL, bearerToken string
62+
)
63+
64+
g.BeforeEach(func(ctx g.SpecContext) {
65+
var err error
66+
prometheusURL, err = helper.PrometheusRouteURL(ctx, oc)
67+
o.Expect(err).NotTo(o.HaveOccurred(), "Get public url of prometheus")
68+
bearerToken, err = helper.RequestPrometheusServiceAccountAPIToken(ctx, oc)
69+
o.Expect(err).NotTo(o.HaveOccurred(), "Request prometheus service account API token")
70+
})
71+
72+
g.It("should not be accessible without authorization", func() {
73+
var errs []error
74+
75+
g.By("verifying all targets returns 401 or 403 without authorization")
76+
contents, err := helper.GetURLWithToken(helper.MustJoinUrlPath(prometheusURL, "api/v1/targets"), bearerToken)
77+
o.Expect(err).NotTo(o.HaveOccurred())
78+
79+
targets := &prometheusTargets{}
80+
err = json.Unmarshal([]byte(contents), targets)
81+
o.Expect(err).NotTo(o.HaveOccurred())
82+
83+
for _, target := range targets.Data.ActiveTargets {
84+
ns := target.Labels["namespace"]
85+
if !strings.HasPrefix(ns, "openshift-") {
86+
continue
87+
}
88+
if componentNamespace != "" && ns != componentNamespace {
89+
continue
90+
}
91+
// Error out only when the status code is obtained
92+
// Error on executing the cmd is skipped here because the endpoint does not expose any information.
93+
// Promethus may have troubles to scrape it but that is out of the scape of this test
94+
err = helper.ExpectURLStatusCodeExecViaPod("openshift-monitoring", "prometheus-k8s-0", target.ScrapeUrl, true, 401, 403)
95+
if err != nil {
96+
errs = append(errs, fmt.Errorf("the scaple url %s for namespace %s is accessible without authorization: %w", target.ScrapeUrl, ns, err))
97+
} else {
98+
e2e.Logf("the scaple url %s for namespace %s is not accessible without authorization", target.ScrapeUrl, ns)
99+
}
100+
}
101+
102+
g.By("verifying all service monitors are configured with authorization")
103+
client, err := prometheusoperatorv1client.NewForConfig(oc.AdminConfig())
104+
o.Expect(err).NotTo(o.HaveOccurred(), "Create monitoring client")
105+
106+
serviceMonitorList, err := client.ServiceMonitors("").List(context.Background(), metav1.ListOptions{})
107+
o.Expect(err).NotTo(o.HaveOccurred(), "List service monitors")
108+
109+
for _, sm := range serviceMonitorList.Items {
110+
// we do not check service monitors for user-workload-monitoring
111+
if !strings.HasPrefix(sm.Namespace, "openshift-") {
112+
continue
113+
}
114+
if componentNamespace != "" && sm.Namespace != componentNamespace {
115+
continue
116+
}
117+
if !authorizationConfigured(sm) {
118+
errs = append(errs, fmt.Errorf("service monitor %s/%s has no authorization", sm.Namespace, sm.Name))
119+
} else {
120+
e2e.Logf("service monitor %s/%s has authorization", sm.Namespace, sm.Name)
121+
}
122+
}
123+
124+
o.Expect(errs).To(o.BeEmpty())
125+
})
126+
127+
})
128+
129+
// authorizationConfigured returns true if the service monitor is configured with authorization
130+
func authorizationConfigured(sm *prometheusoperatorv1.ServiceMonitor) bool {
131+
if sm == nil {
132+
return false
133+
}
134+
for _, e := range sm.Spec.Endpoints {
135+
if e.BasicAuth != nil ||
136+
e.OAuth2 != nil ||
137+
e.Authorization != nil ||
138+
e.BearerTokenFile != "" ||
139+
e.BearerTokenSecret != nil {
140+
return true
141+
}
142+
}
143+
return false
144+
}
145+
52146
var _ = g.Describe("[sig-instrumentation][Late] OpenShift alerting rules [apigroup:image.openshift.io]", func() {
53147
defer g.GinkgoRecover()
54148

@@ -549,7 +643,7 @@ var _ = g.Describe("[sig-instrumentation] Prometheus [apigroup:image.openshift.i
549643
})).NotTo(o.HaveOccurred(), fmt.Sprintf("Did not find tsdb_samples_appended_total, tsdb_head_samples_appended_total, or prometheus_tsdb_head_samples_appended_total"))
550644

551645
g.By("verifying the Thanos querier service requires authentication")
552-
err := helper.ExpectURLStatusCodeExecViaPod(ns, execPod.Name, querySvcURL, 401, 403)
646+
err := helper.ExpectURLStatusCodeExecViaPod(ns, execPod.Name, querySvcURL, false, 401, 403)
553647
o.Expect(err).NotTo(o.HaveOccurred())
554648

555649
g.By("verifying a service account token is able to authenticate")

test/extended/util/prometheus/helpers.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,19 @@ func ExpectHTTPStatusCode(url, bearerToken string, statusCodes ...int) error {
283283
}
284284

285285
// ExpectURLStatusCodeExecViaPod attempts connection to url via exec pod and returns an error
286-
// upon failure or if status return code is not equal to any of the statusCodes.
287-
func ExpectURLStatusCodeExecViaPod(ns, execPodName, url string, statusCodes ...int) error {
286+
// if status return code is not equal to any of the statusCodes, or
287+
// it is not tolerant to failures, i.e., allowFailure=false and some failure popped up when executing the command.
288+
// When allowFailure=false, it logs the error and the output and return nil.
289+
func ExpectURLStatusCodeExecViaPod(ns, execPodName, url string, allowFailure bool, statusCodes ...int) error {
288290
cmd := fmt.Sprintf("curl -k -s -o /dev/null -w '%%{http_code}' %q", url)
289291
output, err := e2eoutput.RunHostCmd(ns, execPodName, cmd)
290292
if err != nil {
291-
return fmt.Errorf("host command failed: %v\n%s", err, output)
293+
if !allowFailure {
294+
return fmt.Errorf("host command failed: %v\n%s", err, output)
295+
} else {
296+
framework.Logf("host command failed: %v\n%s", err, output)
297+
return nil
298+
}
292299
}
293300
for _, statusCode := range statusCodes {
294301
if output == strconv.Itoa(statusCode) {

0 commit comments

Comments
 (0)