From 2b516126cf2d8b977a7451869ef8bb19224da6d0 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:27:57 +0100 Subject: [PATCH] Prometheus: Fix Azure authentication support (#44407) (#44861) Re-adding back Azure authentication support to Prometheus datasource after the datasource query logic was rewritten from plugin.json routes to Go backend. Ref #35857 (cherry picked from commit 85ea1a5d6449b0e68b2683a19cd4514a98883c2e) Co-authored-by: Sergey Kostrukov --- pkg/tsdb/prometheus/promclient/provider.go | 22 +++--- .../prometheus/promclient/provider_azure.go | 32 ++++++++ .../prometheus/promclient/provider_test.go | 2 +- pkg/tsdb/prometheus/prometheus.go | 10 ++- pkg/util/maputil/maputil.go | 73 +++++++++++++++++++ 5 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 pkg/tsdb/prometheus/promclient/provider_azure.go create mode 100644 pkg/util/maputil/maputil.go diff --git a/pkg/tsdb/prometheus/promclient/provider.go b/pkg/tsdb/prometheus/promclient/provider.go index c84a9651773..c33d6a14c11 100644 --- a/pkg/tsdb/prometheus/promclient/provider.go +++ b/pkg/tsdb/prometheus/promclient/provider.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/tsdb/prometheus/middleware" + "github.com/grafana/grafana/pkg/util/maputil" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient" @@ -16,30 +16,28 @@ import ( type Provider struct { settings backend.DataSourceInstanceSettings - jsonData JsonData + jsonData map[string]interface{} + httpMethod string clientProvider httpclient.Provider log log.Logger } func NewProvider( settings backend.DataSourceInstanceSettings, - jsonData JsonData, + jsonData map[string]interface{}, clientProvider httpclient.Provider, log log.Logger, ) *Provider { + httpMethod, _ := maputil.GetStringOptional(jsonData, "httpMethod") return &Provider{ settings: settings, jsonData: jsonData, + httpMethod: httpMethod, clientProvider: clientProvider, log: log, } } -type JsonData struct { - Method string `json:"httpMethod"` - TimeInterval string `json:"timeInterval"` -} - func (p *Provider) GetClient(headers map[string]string) (apiv1.API, error) { opts, err := p.settings.HTTPClientOptions() if err != nil { @@ -54,6 +52,12 @@ func (p *Provider) GetClient(headers map[string]string) (apiv1.API, error) { opts.SigV4.Service = "aps" } + // Azure authentication + err = p.configureAzureAuthentication(opts) + if err != nil { + return nil, err + } + roundTripper, err := p.clientProvider.GetTransport(opts) if err != nil { return nil, err @@ -77,7 +81,7 @@ func (p *Provider) middlewares() []sdkhttpclient.Middleware { middleware.CustomQueryParameters(p.log), sdkhttpclient.CustomHeadersMiddleware(), } - if strings.ToLower(p.jsonData.Method) == "get" { + if strings.ToLower(p.httpMethod) == "get" { middlewares = append(middlewares, middleware.ForceHttpGet(p.log)) } diff --git a/pkg/tsdb/prometheus/promclient/provider_azure.go b/pkg/tsdb/prometheus/promclient/provider_azure.go new file mode 100644 index 00000000000..920f253a75d --- /dev/null +++ b/pkg/tsdb/prometheus/promclient/provider_azure.go @@ -0,0 +1,32 @@ +package promclient + +import ( + "fmt" + + sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" + "github.com/grafana/grafana/pkg/util/maputil" +) + +func (p *Provider) configureAzureAuthentication(opts sdkhttpclient.Options) error { + credentials, err := azcredentials.FromDatasourceData(p.jsonData, p.settings.DecryptedSecureJSONData) + if err != nil { + err = fmt.Errorf("invalid Azure credentials: %s", err) + return err + } + + if credentials != nil { + opts.CustomOptions["_azureCredentials"] = credentials + + resourceId, err := maputil.GetStringOptional(p.jsonData, "azureEndpointResourceId") + if err != nil { + return err + } + + if resourceId != "" { + opts.CustomOptions["azureEndpointResourceId"] = resourceId + } + } + + return nil +} diff --git a/pkg/tsdb/prometheus/promclient/provider_test.go b/pkg/tsdb/prometheus/promclient/provider_test.go index 935b3d07c5a..43acc14b362 100644 --- a/pkg/tsdb/prometheus/promclient/provider_test.go +++ b/pkg/tsdb/prometheus/promclient/provider_test.go @@ -135,7 +135,7 @@ func setup(jsonData ...string) *testContext { rawData = []byte(jsonData[0]) } - var jd promclient.JsonData + var jd map[string]interface{} _ = json.Unmarshal(rawData, &jd) settings := backend.DataSourceInstanceSettings{URL: "test-url", JSONData: rawData} diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index e16597c50fb..9ba855411ad 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/tsdb/intervalv2" + "github.com/grafana/grafana/pkg/util/maputil" apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" ) @@ -42,7 +43,7 @@ func ProvideService(httpClientProvider httpclient.Provider, tracer tracing.Trace func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc { return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { - var jsonData promclient.JsonData + var jsonData map[string]interface{} err := json.Unmarshal(settings.JSONData, &jsonData) if err != nil { return nil, fmt.Errorf("error reading settings: %w", err) @@ -54,10 +55,15 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst return nil, err } + timeInterval, err := maputil.GetStringOptional(jsonData, "timeInterval") + if err != nil { + return nil, err + } + mdl := DatasourceInfo{ ID: settings.ID, URL: settings.URL, - TimeInterval: jsonData.TimeInterval, + TimeInterval: timeInterval, getClient: pc.GetClient, } diff --git a/pkg/util/maputil/maputil.go b/pkg/util/maputil/maputil.go new file mode 100644 index 00000000000..becd6d81e0c --- /dev/null +++ b/pkg/util/maputil/maputil.go @@ -0,0 +1,73 @@ +package maputil + +import "fmt" + +func GetMap(obj map[string]interface{}, key string) (map[string]interface{}, error) { + if untypedValue, ok := obj[key]; ok { + if value, ok := untypedValue.(map[string]interface{}); ok { + return value, nil + } else { + err := fmt.Errorf("the field '%s' should be an object", key) + return nil, err + } + } else { + err := fmt.Errorf("the field '%s' should be set", key) + return nil, err + } +} + +func GetBool(obj map[string]interface{}, key string) (bool, error) { + if untypedValue, ok := obj[key]; ok { + if value, ok := untypedValue.(bool); ok { + return value, nil + } else { + err := fmt.Errorf("the field '%s' should be a bool", key) + return false, err + } + } else { + err := fmt.Errorf("the field '%s' should be set", key) + return false, err + } +} + +func GetBoolOptional(obj map[string]interface{}, key string) (bool, error) { + if untypedValue, ok := obj[key]; ok { + if value, ok := untypedValue.(bool); ok { + return value, nil + } else { + err := fmt.Errorf("the field '%s' should be a bool", key) + return false, err + } + } else { + // Value optional, not error + return false, nil + } +} + +func GetString(obj map[string]interface{}, key string) (string, error) { + if untypedValue, ok := obj[key]; ok { + if value, ok := untypedValue.(string); ok { + return value, nil + } else { + err := fmt.Errorf("the field '%s' should be a string", key) + return "", err + } + } else { + err := fmt.Errorf("the field '%s' should be set", key) + return "", err + } +} + +func GetStringOptional(obj map[string]interface{}, key string) (string, error) { + if untypedValue, ok := obj[key]; ok { + if value, ok := untypedValue.(string); ok { + return value, nil + } else { + err := fmt.Errorf("the field '%s' should be a string", key) + return "", err + } + } else { + // Value optional, not error + return "", nil + } +}