Преглед изворни кода

refactor(dataproxy): TLS Client Auth

Use a SecureJsonData field for TLS
Client Auth instead of 3 new db
fields. Same model as used for
PluginSettings.

Saves and encrypts the pem file
content rather than just saving
the paths to the cert and key.
This allows for uploading from
the Edit Datasource page in
Grafana.
Daniel Lee пре 9 година
родитељ
комит
c9b2c694f1

+ 13 - 4
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

+ 83 - 1
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-----`

+ 0 - 4
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,

+ 17 - 19
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

+ 30 - 35
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:"-"`

+ 3 - 7
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)

+ 3 - 9
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,
 	}))
 }