From a50d7de86dac747daf63a14caba34881b83618e9 Mon Sep 17 00:00:00 2001 From: Wallace Wadge Date: Fri, 6 Jan 2023 14:01:05 +0100 Subject: [PATCH] Allow access to Prometheus in OpenShift via SA token Fixes: https://github.com/fluxcd/flagger/issues/1064 Signed-off-by: Wallace Wadge --- docs/gitbook/usage/metrics.md | 16 +++++++- pkg/metrics/providers/prometheus.go | 28 ++++++++----- pkg/metrics/providers/prometheus_test.go | 50 +++++++++++++++++++++++- 3 files changed, 81 insertions(+), 13 deletions(-) diff --git a/docs/gitbook/usage/metrics.md b/docs/gitbook/usage/metrics.md index 854a8e90a..18f899b72 100644 --- a/docs/gitbook/usage/metrics.md +++ b/docs/gitbook/usage/metrics.md @@ -184,13 +184,25 @@ as the `MetricTemplate` with the basic-auth credentials: apiVersion: v1 kind: Secret metadata: - name: prom-basic-auth + name: prom-auth namespace: flagger data: username: your-user password: your-password ``` +or if you require bearer token authentication (via a SA token): + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: prom-auth + namespace: flagger +data: + token: ey1234... +``` + Then reference the secret in the `MetricTemplate`: ```yaml @@ -204,7 +216,7 @@ spec: type: prometheus address: http://prometheus.monitoring:9090 secretRef: - name: prom-basic-auth + name: prom-auth ``` ## Datadog diff --git a/pkg/metrics/providers/prometheus.go b/pkg/metrics/providers/prometheus.go index bead94a80..73653eea6 100644 --- a/pkg/metrics/providers/prometheus.go +++ b/pkg/metrics/providers/prometheus.go @@ -41,6 +41,7 @@ type PrometheusProvider struct { url url.URL username string password string + token string client *http.Client } @@ -56,7 +57,7 @@ type prometheusResponse struct { } // NewPrometheusProvider takes a provider spec and the credentials map, -// validates the address, extracts the username and password values if provided and +// validates the address, extracts the bearer token or username and password values if provided and // returns a Prometheus client ready to execute queries against the API func NewPrometheusProvider(provider flaggerv1.MetricTemplateProvider, credentials map[string][]byte) (*PrometheusProvider, error) { promURL, err := url.Parse(provider.Address) @@ -77,16 +78,21 @@ func NewPrometheusProvider(provider flaggerv1.MetricTemplateProvider, credential } if provider.SecretRef != nil { - if username, ok := credentials["username"]; ok { - prom.username = string(username) + if token, ok := credentials["token"]; ok { + prom.token = string(token) } else { - return nil, fmt.Errorf("%s credentials does not contain a username", provider.Type) - } - if password, ok := credentials["password"]; ok { - prom.password = string(password) - } else { - return nil, fmt.Errorf("%s credentials does not contain a password", provider.Type) + if username, ok := credentials["username"]; ok { + prom.username = string(username) + } else { + return nil, fmt.Errorf("%s credentials does not contain a username", provider.Type) + } + + if password, ok := credentials["password"]; ok { + prom.password = string(password) + } else { + return nil, fmt.Errorf("%s credentials does not contain a password", provider.Type) + } } } @@ -109,7 +115,9 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) { return 0, fmt.Errorf("http.NewRequest failed: %w", err) } - if p.username != "" && p.password != "" { + if p.token != "" { + req.Header.Add("Authorization", "Bearer "+p.token) + } else if p.username != "" && p.password != "" { req.SetBasicAuth(p.username, p.password) } diff --git a/pkg/metrics/providers/prometheus_test.go b/pkg/metrics/providers/prometheus_test.go index 84ee62c07..484fc16df 100644 --- a/pkg/metrics/providers/prometheus_test.go +++ b/pkg/metrics/providers/prometheus_test.go @@ -75,7 +75,19 @@ func prometheusFake() fakeClients { }, } - kubeClient := fake.NewSimpleClientset(secret) + bearerTokenSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "prometheus-bearer", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "token": []byte("bearer_token"), + }, + } + + kubeClient := fake.NewSimpleClientset(secret, bearerTokenSecret) return fakeClients{ kubeClient: kubeClient, @@ -170,6 +182,42 @@ func TestPrometheusProvider_RunQueryWithBasicAuth(t *testing.T) { } +func TestPrometheusProvider_RunQueryWithBearerAuth(t *testing.T) { + t.Run("ok", func(t *testing.T) { + expected := `sum(envoy_cluster_upstream_rq)` + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + promql := r.URL.Query()["query"][0] + assert.Equal(t, expected, promql) + + header, ok := r.Header["Authorization"] + if assert.True(t, ok, "Authorization header not found") { + assert.True(t, strings.Contains(header[0], "Bearer"), "Bearer authorization header not found") + } + + json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}` + w.Write([]byte(json)) + })) + defer ts.Close() + + clients := prometheusFake() + + template, err := clients.flaggerClient.FlaggerV1beta1().MetricTemplates("default").Get(context.TODO(), "prometheus", metav1.GetOptions{}) + require.NoError(t, err) + template.Spec.Provider.Address = ts.URL + + secret, err := clients.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "prometheus-bearer", metav1.GetOptions{}) + require.NoError(t, err) + + prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data) + require.NoError(t, err) + + val, err := prom.RunQuery(template.Spec.Query) + require.NoError(t, err) + + assert.Equal(t, float64(100), val) + }) +} + func TestPrometheusProvider_IsOnline(t *testing.T) { t.Run("fail", func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {