Skip to content

Commit

Permalink
Allow access to Prometheus in OpenShift via SA token
Browse files Browse the repository at this point in the history
Fixes: #1064

Signed-off-by: Wallace Wadge <wwadge@gmail.com>
  • Loading branch information
wwadge authored and Wallace Wadge committed Jan 17, 2023
1 parent 58270dd commit 1b17b5d
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 13 deletions.
16 changes: 14 additions & 2 deletions docs/gitbook/usage/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -204,7 +216,7 @@ spec:
type: prometheus
address: http://prometheus.monitoring:9090
secretRef:
name: prom-basic-auth
name: prom-auth
```

## Datadog
Expand Down
28 changes: 18 additions & 10 deletions pkg/metrics/providers/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type PrometheusProvider struct {
url url.URL
username string
password string
token string
client *http.Client
}

Expand All @@ -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)
Expand All @@ -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)
}
}
}

Expand All @@ -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)
}

Expand Down
85 changes: 84 additions & 1 deletion pkg/metrics/providers/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -170,6 +182,77 @@ 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)
})

noResultTests := []struct {
name string
queryResult string
}{
{name: "no values result", queryResult: `{"status":"success","data":{"resultType":"vector","result":[]}}`},
{name: "NaN result", queryResult: `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1643023250.379,"NaN"]}]}}`},
}

for _, tt := range noResultTests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := tt.queryResult
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", metav1.GetOptions{})
require.NoError(t, err)

prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
require.NoError(t, err)

_, err = prom.RunQuery(template.Spec.Query)
require.True(t, errors.Is(err, ErrNoValuesFound))
})
}

}

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) {
Expand Down

0 comments on commit 1b17b5d

Please sign in to comment.