grafana/pkg/models/datasource_cache.go
Sergey Kostrukov c1963024ec
Infra: Azure authentication in HttpClientProvider (#36932)
* Azure middleware in HttpClientProxy

* Azure authentication under feature flag

* Minor fixes

* Add prefixes to not clash with JsonData

* Return error if JsonData cannot be parsed

* Return original string if URL invalid

* Tests for datasource_cache
2021-07-22 21:43:10 +01:00

292 lines
7.6 KiB
Go

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))
}
}