package models import ( "crypto/tls" "fmt" "net/http" "strconv" "sync" "time" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" ) func (ds *DataSource) getTimeout() time.Duration { timeout := 0 if ds.JsonData != nil { timeout = ds.JsonData.Get("timeout").MustInt() if timeout <= 0 { if timeoutStr := ds.JsonData.Get("timeout").MustString(); timeoutStr != "" { if t, err := strconv.Atoi(timeoutStr); err == nil { timeout = t } } } } if timeout <= 0 { return sdkhttpclient.DefaultTimeoutOptions.Timeout } return time.Duration(timeout) * time.Second } type proxyTransportCache struct { cache map[int64]cachedRoundTripper sync.Mutex } type cachedRoundTripper struct { updated time.Time roundTripper http.RoundTripper } var ptc = proxyTransportCache{ cache: make(map[int64]cachedRoundTripper), } func (ds *DataSource) GetHTTPClient(provider httpclient.Provider) (*http.Client, error) { transport, err := ds.GetHTTPTransport(provider) if err != nil { return nil, err } return &http.Client{ Timeout: ds.getTimeout(), Transport: transport, }, nil } func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) { ptc.Lock() defer ptc.Unlock() if t, present := ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) { return t.roundTripper, nil } opts, err := ds.HTTPClientOptions() if err != nil { return nil, err } opts.Middlewares = customMiddlewares rt, err := provider.GetTransport(*opts) if err != nil { return nil, err } ptc.cache[ds.Id] = cachedRoundTripper{ roundTripper: rt, updated: ds.Updated, } return rt, nil } func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { tlsOptions := ds.TLSOptions() timeouts := &sdkhttpclient.TimeoutOptions{ Timeout: ds.getTimeout(), DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout, KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive, TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout, ExpectContinueTimeout: sdkhttpclient.DefaultTimeoutOptions.ExpectContinueTimeout, MaxConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxConnsPerHost, MaxIdleConns: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConns, MaxIdleConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConnsPerHost, IdleConnTimeout: sdkhttpclient.DefaultTimeoutOptions.IdleConnTimeout, } opts := &sdkhttpclient.Options{ Timeouts: timeouts, Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()), Labels: map[string]string{ "datasource_name": ds.Name, "datasource_uid": ds.Uid, }, TLS: &tlsOptions, } if ds.JsonData != nil { opts.CustomOptions = ds.JsonData.MustMap() } if ds.BasicAuth { opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ User: ds.BasicAuthUser, Password: ds.DecryptedBasicAuthPassword(), } } else if ds.User != "" { opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ User: ds.User, Password: ds.DecryptedPassword(), } } if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() { credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), ds.DecryptedValues()) if err != nil { err = fmt.Errorf("invalid Azure credentials: %s", err) return nil, err } opts.CustomOptions["_azureAuth"] = true if credentials != nil { opts.CustomOptions["_azureCredentials"] = credentials } } if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool(false) { opts.SigV4 = &sdkhttpclient.SigV4Config{ Service: awsServiceNamespace(ds.Type), Region: ds.JsonData.Get("sigV4Region").MustString(), AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(), AuthType: ds.JsonData.Get("sigV4AuthType").MustString(), ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(), Profile: ds.JsonData.Get("sigV4Profile").MustString(), } if val, exists := ds.DecryptedValue("sigV4AccessKey"); exists { opts.SigV4.AccessKey = val } if val, exists := ds.DecryptedValue("sigV4SecretKey"); exists { opts.SigV4.SecretKey = val } } return opts, nil } func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions { var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool var serverName string if ds.JsonData != nil { tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false) tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false) serverName = ds.JsonData.Get("serverName").MustString() } opts := sdkhttpclient.TLSOptions{ InsecureSkipVerify: tlsSkipVerify, ServerName: serverName, } if tlsClientAuth || tlsAuthWithCACert { if tlsAuthWithCACert { if val, exists := ds.DecryptedValue("tlsCACert"); exists && len(val) > 0 { opts.CACertificate = val } } if tlsClientAuth { if val, exists := ds.DecryptedValue("tlsClientCert"); exists && len(val) > 0 { opts.ClientCertificate = val } if val, exists := ds.DecryptedValue("tlsClientKey"); exists && len(val) > 0 { opts.ClientKey = val } } } return opts } func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) { opts, err := ds.HTTPClientOptions() if err != nil { return nil, err } return httpClientProvider.GetTLSConfig(*opts) } // getCustomHeaders returns a map with all the to be set headers // The map key represents the HeaderName and the value represents this header's value func getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]string) map[string]string { headers := make(map[string]string) if jsonData == nil { return headers } index := 1 for { headerNameSuffix := fmt.Sprintf("httpHeaderName%d", index) headerValueSuffix := fmt.Sprintf("httpHeaderValue%d", index) key := jsonData.Get(headerNameSuffix).MustString() if key == "" { // No (more) header values are available break } if val, ok := decryptedValues[headerValueSuffix]; ok { headers[key] = val } index++ } return headers } type cachedDecryptedJSON struct { updated time.Time json map[string]string } type secureJSONDecryptionCache struct { cache map[int64]cachedDecryptedJSON sync.Mutex } var dsDecryptionCache = secureJSONDecryptionCache{ cache: make(map[int64]cachedDecryptedJSON), } // DecryptedValues returns cached decrypted values from secureJsonData. func (ds *DataSource) DecryptedValues() map[string]string { dsDecryptionCache.Lock() defer dsDecryptionCache.Unlock() if item, present := dsDecryptionCache.cache[ds.Id]; present && ds.Updated.Equal(item.updated) { return item.json } json := ds.SecureJsonData.Decrypt() dsDecryptionCache.cache[ds.Id] = cachedDecryptedJSON{ updated: ds.Updated, json: json, } return json } // DecryptedValue returns cached decrypted value from cached secureJsonData. func (ds *DataSource) DecryptedValue(key string) (string, bool) { value, exists := ds.DecryptedValues()[key] return value, exists } // ClearDSDecryptionCache clears the datasource decryption cache. func ClearDSDecryptionCache() { dsDecryptionCache.Lock() defer dsDecryptionCache.Unlock() dsDecryptionCache.cache = make(map[int64]cachedDecryptedJSON) } func awsServiceNamespace(dsType string) string { switch dsType { case DS_ES, DS_ES_OPEN_DISTRO: return "es" case DS_PROMETHEUS: return "aps" default: panic(fmt.Sprintf("Unsupported datasource %q", dsType)) } }