The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/models/datasource_cache.go

256 lines
6.6 KiB

package models
import (
"crypto/tls"
"fmt"
"net/http"
"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/setting"
)
func (ds *DataSource) getTimeout() time.Duration {
timeout := 0
if ds.JsonData != nil {
timeout = ds.JsonData.Get("timeout").MustInt()
}
if timeout == 0 {
timeout = setting.DataProxyTimeout
}
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) (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
}
rt, err := provider.GetTransport(ds.HTTPClientOptions())
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 {
tlsOptions := ds.TLSOptions()
opts := sdkhttpclient.Options{
Timeouts: &sdkhttpclient.TimeoutOptions{
Timeout: ds.getTimeout(),
KeepAlive: time.Duration(setting.DataProxyKeepAlive) * time.Second,
TLSHandshakeTimeout: time.Duration(setting.DataProxyTLSHandshakeTimeout) * time.Second,
ExpectContinueTimeout: time.Duration(setting.DataProxyExpectContinueTimeout) * time.Second,
MaxIdleConns: setting.DataProxyMaxIdleConns,
MaxIdleConnsPerHost: setting.DataProxyMaxIdleConnsPerHost,
IdleConnTimeout: time.Duration(setting.DataProxyIdleConnTimeout) * time.Second,
},
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("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
}
func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
DataSourceSettings: Add servername field to DataSource TLS config (#29279) * DataSourceSettings: Add servername field to DataSource TLS config A DNS lookup URL can be provided in the DataSource URL field in order to dynamically load balance between multiple instances of a DataSource. When using mutual TLS, Golang's TLS config implementation checks that the certificate's common name (< 1.15) or subject alternative name (>= 1.15) has the same value as the domain being accessed. If the DNS entry is dynamically generated for a specific environment, the certificate cannot be generated with a name matching the dynamic DNS URL. As such, Golang offers a servername field that can be set to overwrite what value is used when checking against the certificate's common name (or subject alternative name). Without this change, Skip TLS Verify must be set to true in order for the DataSource to work, removing some of the benefits gained by using mutual TLS. This commit adds the ability to set Grafana's internal Golang TLS config servername field from the UI or a provisioned DataSource. The servername field is optional and the existing behavior is retained if the field is not set. Co-authored-by: Dana Pruitt <dpruitt@vmware.com> Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> * Update docs with PR review changes Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> Co-authored-by: Dana Pruitt <dpruitt@vmware.com> * Update with additional PR requested changes * Minor updates based on PR change requests Co-authored-by: Dana Pruitt <dpruitt@vmware.com>
5 years ago
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)
DataSourceSettings: Add servername field to DataSource TLS config (#29279) * DataSourceSettings: Add servername field to DataSource TLS config A DNS lookup URL can be provided in the DataSource URL field in order to dynamically load balance between multiple instances of a DataSource. When using mutual TLS, Golang's TLS config implementation checks that the certificate's common name (< 1.15) or subject alternative name (>= 1.15) has the same value as the domain being accessed. If the DNS entry is dynamically generated for a specific environment, the certificate cannot be generated with a name matching the dynamic DNS URL. As such, Golang offers a servername field that can be set to overwrite what value is used when checking against the certificate's common name (or subject alternative name). Without this change, Skip TLS Verify must be set to true in order for the DataSource to work, removing some of the benefits gained by using mutual TLS. This commit adds the ability to set Grafana's internal Golang TLS config servername field from the UI or a provisioned DataSource. The servername field is optional and the existing behavior is retained if the field is not set. Co-authored-by: Dana Pruitt <dpruitt@vmware.com> Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> * Update docs with PR review changes Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> Co-authored-by: Dana Pruitt <dpruitt@vmware.com> * Update with additional PR requested changes * Minor updates based on PR change requests Co-authored-by: Dana Pruitt <dpruitt@vmware.com>
5 years ago
serverName = ds.JsonData.Get("serverName").MustString()
}
opts := sdkhttpclient.TLSOptions{
InsecureSkipVerify: tlsSkipVerify,
DataSourceSettings: Add servername field to DataSource TLS config (#29279) * DataSourceSettings: Add servername field to DataSource TLS config A DNS lookup URL can be provided in the DataSource URL field in order to dynamically load balance between multiple instances of a DataSource. When using mutual TLS, Golang's TLS config implementation checks that the certificate's common name (< 1.15) or subject alternative name (>= 1.15) has the same value as the domain being accessed. If the DNS entry is dynamically generated for a specific environment, the certificate cannot be generated with a name matching the dynamic DNS URL. As such, Golang offers a servername field that can be set to overwrite what value is used when checking against the certificate's common name (or subject alternative name). Without this change, Skip TLS Verify must be set to true in order for the DataSource to work, removing some of the benefits gained by using mutual TLS. This commit adds the ability to set Grafana's internal Golang TLS config servername field from the UI or a provisioned DataSource. The servername field is optional and the existing behavior is retained if the field is not set. Co-authored-by: Dana Pruitt <dpruitt@vmware.com> Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> * Update docs with PR review changes Co-authored-by: Jeremy Alvis <jalvis@pivotal.io> Co-authored-by: Dana Pruitt <dpruitt@vmware.com> * Update with additional PR requested changes * Minor updates based on PR change requests Co-authored-by: Dana Pruitt <dpruitt@vmware.com>
5 years ago
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) {
return httpClientProvider.GetTLSConfig(ds.HTTPClientOptions())
}
// 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))
}
}