diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 7f527ed3c74..8ab80b9e18e 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -17,7 +17,7 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func dataProxyTransport(ds *m.DataSource) (*http.Transport, error) { +func DataProxyTransport(ds *m.DataSource) (*http.Transport, error) { transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, @@ -30,8 +30,17 @@ func dataProxyTransport(ds *m.DataSource) (*http.Transport, error) { TLSHandshakeTimeout: 10 * time.Second, } - if ds.TlsAuth { - cert, err := tls.LoadX509KeyPair(ds.TlsClientCert, ds.TlsClientKey) + var tlsAuth bool + var err error + if ds.JsonData != nil { + tlsAuth, err = ds.JsonData.Get("tlsAuth").Bool() + } + + if err == nil && tlsAuth { + transport.TLSClientConfig.InsecureSkipVerify = false + + decrypted := ds.SecureJsonData.Decrypt() + cert, err := tls.X509KeyPair([]byte(decrypted["tlsClientCert"]), []byte(decrypted["tlsClientKey"])) if err != nil { return nil, err } @@ -141,7 +150,7 @@ func ProxyDataSourceRequest(c *middleware.Context) { } proxy := NewReverseProxy(ds, proxyPath, targetUrl) - proxy.Transport, err = dataProxyTransport(ds) + proxy.Transport, err = DataProxyTransport(ds) if err != nil { c.JsonApiErr(400, "Unable to load TLS certificate", err) return diff --git a/pkg/api/dataproxy_test.go b/pkg/api/dataproxy_test.go index 0e561c726e1..d421eb74af5 100644 --- a/pkg/api/dataproxy_test.go +++ b/pkg/api/dataproxy_test.go @@ -7,15 +7,24 @@ import ( . "github.com/smartystreets/goconvey/convey" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) func TestDataSourceProxy(t *testing.T) { Convey("When getting graphite datasource proxy", t, func() { ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE} - targetUrl, _ := url.Parse(ds.Url) + targetUrl, err := url.Parse(ds.Url) proxy := NewReverseProxy(&ds, "/render", targetUrl) + proxy.Transport, err = DataProxyTransport(&ds) + So(err, ShouldBeNil) + + transport, ok := proxy.Transport.(*http.Transport) + So(ok, ShouldBeTrue) + So(transport.TLSClientConfig.InsecureSkipVerify, ShouldBeTrue) requestUrl, _ := url.Parse("http://grafana.com/sub") req := http.Request{URL: requestUrl} @@ -54,7 +63,80 @@ func TestDataSourceProxy(t *testing.T) { So(queryVals["u"][0], ShouldEqual, "user") So(queryVals["p"][0], ShouldEqual, "password") }) + }) + Convey("When getting kubernetes datasource proxy", t, func() { + setting.SecretKey = "password" + + json := simplejson.New() + json.Set("tlsAuth", true) + ds := m.DataSource{ + Url: "htttp://k8s:8001", + Type: "Kubernetes", + JsonData: json, + SecureJsonData: map[string][]byte{ + "tlsClientCert": util.Encrypt([]byte(clientCert), "password"), + "tlsClientKey": util.Encrypt([]byte(clientKey), "password"), + }, + } + targetUrl, err := url.Parse(ds.Url) + proxy := NewReverseProxy(&ds, "", targetUrl) + proxy.Transport, err = DataProxyTransport(&ds) + So(err, ShouldBeNil) + + transport, ok := proxy.Transport.(*http.Transport) + + Convey("Should add cert", func() { + So(ok, ShouldBeTrue) + So(transport.TLSClientConfig.InsecureSkipVerify, ShouldEqual, false) + So(len(transport.TLSClientConfig.Certificates), ShouldEqual, 1) + }) }) } + +const clientCert string = `-----BEGIN CERTIFICATE----- +MIICsjCCAZoCCQCcd8sOfstQLzANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj +YS1rOHMtc3RobG0wHhcNMTYxMTAyMDkyNTE1WhcNMTcxMTAyMDkyNTE1WjAfMR0w +GwYDVQQDDBRhZG0tZGFuaWVsLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOMliaWyNEUJKM37vWCl5bGub3lMicyRAqGQyY/qxD9yKKM2 +FbucVcmWmg5vvTqQVl5rlQ+c7GI8OD6ptmFl8a26coEki7bFr8bkpSyBSEc5p27b +Z0ORFSqBHWHQbr9PkxPLYW6T3gZYUtRYv3OQgGxLXlvUh85n/mQfuR3N1FgmShHo +GtAFi/ht6leXa0Ms+jNSDLCmXpJm1GIEqgyKX7K3+g3vzo9coYqXq4XTa8Efs2v8 +SCwqWfBC3rHfgs/5DLB8WT4Kul8QzxkytzcaBQfRfzhSV6bkgm7oTzt2/1eRRsf4 +YnXzLE9YkCC9sAn+Owzqf+TYC1KRluWDfqqBTJUCAwEAATANBgkqhkiG9w0BAQsF +AAOCAQEAdMsZg6edWGC+xngizn0uamrUg1ViaDqUsz0vpzY5NWLA4MsBc4EtxWRP +ueQvjUimZ3U3+AX0YWNLIrH1FCVos2jdij/xkTUmHcwzr8rQy+B17cFi+a8jtpgw +AU6WWoaAIEhhbWQfth/Diz3mivl1ARB+YqiWca2mjRPLTPcKJEURDVddQ423el0Q +4JNxS5icu7T2zYTYHAo/cT9zVdLZl0xuLxYm3asK1IONJ/evxyVZima3il6MPvhe +58Hwz+m+HdqHxi24b/1J/VKYbISG4huOQCdLzeNXgvwFlGPUmHSnnKo1/KbQDAR5 +llG/Sw5+FquFuChaA6l5KWy7F3bQyA== +-----END CERTIFICATE-----` + +const clientKey string = `-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA4yWJpbI0RQkozfu9YKXlsa5veUyJzJECoZDJj+rEP3IoozYV +u5xVyZaaDm+9OpBWXmuVD5zsYjw4Pqm2YWXxrbpygSSLtsWvxuSlLIFIRzmnbttn +Q5EVKoEdYdBuv0+TE8thbpPeBlhS1Fi/c5CAbEteW9SHzmf+ZB+5Hc3UWCZKEega +0AWL+G3qV5drQyz6M1IMsKZekmbUYgSqDIpfsrf6De/Oj1yhiperhdNrwR+za/xI +LCpZ8ELesd+Cz/kMsHxZPgq6XxDPGTK3NxoFB9F/OFJXpuSCbuhPO3b/V5FGx/hi +dfMsT1iQIL2wCf47DOp/5NgLUpGW5YN+qoFMlQIDAQABAoIBAQCzy4u312XeW1Cs +Mx6EuOwmh59/ESFmBkZh4rxZKYgrfE5EWlQ7i5SwG4BX+wR6rbNfy6JSmHDXlTkk +CKvvToVNcW6fYHEivDnVojhIERFIJ4+rhQmpBtcNLOQ3/4cZ8X/GxE6b+3lb5l+x +64mnjPLKRaIr5/+TVuebEy0xNTJmjnJ7yiB2HRz7uXEQaVSk/P7KAkkyl/9J3/LM +8N9AX1w6qDaNQZ4/P0++1H4SQenosM/b/GqGTomarEk/GE0NcB9rzmR9VCXa7FRh +WV5jyt9vUrwIEiK/6nUnOkGO8Ei3kB7Y+e+2m6WdaNoU5RAfqXmXa0Q/a0lLRruf +vTMo2WrBAoGBAPRaK4cx76Q+3SJ/wfznaPsMM06OSR8A3ctKdV+ip/lyKtb1W8Pz +k8MYQDH7GwPtSu5QD8doL00pPjugZL/ba7X9nAsI+pinyEErfnB9y7ORNEjIYYzs +DiqDKup7ANgw1gZvznWvb9Ge0WUSXvWS0pFkgootQAf+RmnnbWGH6l6RAoGBAO35 +aGUrLro5u9RD24uSXNU3NmojINIQFK5dHAT3yl0BBYstL43AEsye9lX95uMPTvOQ +Cqcn42Hjp/bSe3n0ObyOZeXVrWcDFAfE0wwB1BkvL1lpgnFO9+VQORlH4w3Ppnpo +jcPkR2TFeDaAYtvckhxe/Bk3OnuFmnsQ3VzM75fFAoGBAI6PvS2XeNU+yA3EtA01 +hg5SQ+zlHswz2TMuMeSmJZJnhY78f5mHlwIQOAPxGQXlf/4iP9J7en1uPpzTK3S0 +M9duK4hUqMA/w5oiIhbHjf0qDnMYVbG+V1V+SZ+cPBXmCDihKreGr5qBKnHpkfV8 +v9WL6o1rcRw4wiQvnaV1gsvBAoGBALtzVTczr6gDKCAIn5wuWy+cQSGTsBunjRLX +xuVm5iEiV+KMYkPvAx/pKzMLP96lRVR3ptyKgAKwl7LFk3u50+zh4gQLr35QH2wL +Lw7rNc3srAhrItPsFzqrWX6/cGuFoKYVS239l/sZzRppQPXcpb7xVvTp2whHcir0 +Wtnpl+TdAoGAGqKqo2KU3JoY3IuTDUk1dsNAm8jd9EWDh+s1x4aG4N79mwcss5GD +FF8MbFPneK7xQd8L6HisKUDAUi2NOyynM81LAftPkvN6ZuUVeFDfCL4vCA0HUXLD ++VrOhtUZkNNJlLMiVRJuQKUOGlg8PpObqYbstQAf/0/yFJMRHG82Tcg= +-----END RSA PRIVATE KEY-----` diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 6d30a7dc44a..2b9964f7a71 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -33,7 +33,6 @@ func GetDataSources(c *middleware.Context) { Database: ds.Database, User: ds.User, BasicAuth: ds.BasicAuth, - TlsAuth: ds.TlsAuth, IsDefault: ds.IsDefault, JsonData: ds.JsonData, } @@ -166,9 +165,6 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource { BasicAuth: ds.BasicAuth, BasicAuthUser: ds.BasicAuthUser, BasicAuthPassword: ds.BasicAuthPassword, - TlsAuth: ds.TlsAuth, - TlsClientCert: ds.TlsClientCert, - TlsClientKey: ds.TlsClientKey, WithCredentials: ds.WithCredentials, IsDefault: ds.IsDefault, JsonData: ds.JsonData, diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index d902f4a58a0..153468c582c 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -64,25 +64,23 @@ type DashboardRedirect struct { } type DataSource struct { - Id int64 `json:"id"` - OrgId int64 `json:"orgId"` - Name string `json:"name"` - Type string `json:"type"` - TypeLogoUrl string `json:"typeLogoUrl"` - Access m.DsAccess `json:"access"` - Url string `json:"url"` - Password string `json:"password"` - User string `json:"user"` - Database string `json:"database"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - TlsAuth bool `json:"tlsAuth"` - TlsClientCert string `json:"tlsClientCert"` - TlsClientKey string `json:"tlsClientKey"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData *simplejson.Json `json:"jsonData,omitempty"` + Id int64 `json:"id"` + OrgId int64 `json:"orgId"` + Name string `json:"name"` + Type string `json:"type"` + TypeLogoUrl string `json:"typeLogoUrl"` + Access m.DsAccess `json:"access"` + Url string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData,omitempty"` + SecureJsonData map[string]string `json:"secureJsonData,omitempty"` } type DataSourceList []DataSource diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index c4400e9f3dd..4a90edd9bfd 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" ) @@ -43,12 +44,10 @@ type DataSource struct { BasicAuth bool BasicAuthUser string BasicAuthPassword string - TlsAuth bool - TlsClientCert string - TlsClientKey string WithCredentials bool IsDefault bool JsonData *simplejson.Json + SecureJsonData securejsondata.SecureJsonData Created time.Time Updated time.Time @@ -80,22 +79,20 @@ func IsKnownDataSourcePlugin(dsType string) bool { // Also acts as api DTO type AddDataSourceCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Access DsAccess `json:"access" binding:"Required"` - Url string `json:"url"` - Password string `json:"password"` - Database string `json:"database"` - User string `json:"user"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - TlsAuth bool `json:"tlsAuth"` - TlsClientCert string `json:"tlsClientCert"` - TlsClientKey string `json:"tlsClientKey"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData *simplejson.Json `json:"jsonData"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Access DsAccess `json:"access" binding:"Required"` + Url string `json:"url"` + Password string `json:"password"` + Database string `json:"database"` + User string `json:"user"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData"` + SecureJsonData map[string]string `json:"secureJsonData"` OrgId int64 `json:"-"` @@ -104,22 +101,20 @@ type AddDataSourceCommand struct { // Also acts as api DTO type UpdateDataSourceCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Access DsAccess `json:"access" binding:"Required"` - Url string `json:"url"` - Password string `json:"password"` - User string `json:"user"` - Database string `json:"database"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - TlsAuth bool `json:"tlsAuth"` - TlsClientCert string `json:"tlsClientCert"` - TlsClientKey string `json:"tlsClientKey"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData *simplejson.Json `json:"jsonData"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Access DsAccess `json:"access" binding:"Required"` + Url string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData"` + SecureJsonData map[string]string `json:"secureJsonData"` OrgId int64 `json:"-"` Id int64 `json:"-"` diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 29581c03096..57abaf35083 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -4,6 +4,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/securejsondata" m "github.com/grafana/grafana/pkg/models" "github.com/go-xorm/xorm" @@ -80,11 +81,9 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error { BasicAuth: cmd.BasicAuth, BasicAuthUser: cmd.BasicAuthUser, BasicAuthPassword: cmd.BasicAuthPassword, - TlsAuth: cmd.TlsAuth, - TlsClientCert: cmd.TlsClientCert, - TlsClientKey: cmd.TlsClientKey, WithCredentials: cmd.WithCredentials, JsonData: cmd.JsonData, + SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData), Created: time.Now(), Updated: time.Now(), } @@ -129,17 +128,14 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error { BasicAuth: cmd.BasicAuth, BasicAuthUser: cmd.BasicAuthUser, BasicAuthPassword: cmd.BasicAuthPassword, - TlsAuth: cmd.TlsAuth, - TlsClientCert: cmd.TlsClientCert, - TlsClientKey: cmd.TlsClientKey, WithCredentials: cmd.WithCredentials, JsonData: cmd.JsonData, + SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData), Updated: time.Now(), } sess.UseBool("is_default") sess.UseBool("basic_auth") - sess.UseBool("tls_auth") sess.UseBool("with_credentials") _, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds) diff --git a/pkg/services/sqlstore/migrations/datasource_mig.go b/pkg/services/sqlstore/migrations/datasource_mig.go index 58205bbb196..396f80df99f 100644 --- a/pkg/services/sqlstore/migrations/datasource_mig.go +++ b/pkg/services/sqlstore/migrations/datasource_mig.go @@ -102,14 +102,8 @@ func addDataSourceMigration(mg *Migrator) { Name: "with_credentials", Type: DB_Bool, Nullable: false, Default: "0", })) - // add columns to activate TLS client auth option - mg.AddMigration("Add column tls_auth", NewAddColumnMigration(tableV2, &Column{ - Name: "tls_auth", Type: DB_Bool, Nullable: false, Default: "0", - })) - mg.AddMigration("Add column tls_client_cert", NewAddColumnMigration(tableV2, &Column{ - Name: "tls_client_cert", Type: DB_NVarchar, Length: 255, Nullable: true, - })) - mg.AddMigration("Add column tls_client_key", NewAddColumnMigration(tableV2, &Column{ - Name: "tls_client_key", Type: DB_NVarchar, Length: 255, Nullable: true, + // add column that can store TLS client auth data + mg.AddMigration("Add secure json data column", NewAddColumnMigration(tableV2, &Column{ + Name: "secure_json_data", Type: DB_Text, Nullable: true, })) }