Commit c9b2c694 by Daniel Lee

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.
parent af07adb1
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
"github.com/grafana/grafana/pkg/util" "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{ transport := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
...@@ -30,8 +30,17 @@ func dataProxyTransport(ds *m.DataSource) (*http.Transport, error) { ...@@ -30,8 +30,17 @@ func dataProxyTransport(ds *m.DataSource) (*http.Transport, error) {
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
} }
if ds.TlsAuth { var tlsAuth bool
cert, err := tls.LoadX509KeyPair(ds.TlsClientCert, ds.TlsClientKey) 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -141,7 +150,7 @@ func ProxyDataSourceRequest(c *middleware.Context) { ...@@ -141,7 +150,7 @@ func ProxyDataSourceRequest(c *middleware.Context) {
} }
proxy := NewReverseProxy(ds, proxyPath, targetUrl) proxy := NewReverseProxy(ds, proxyPath, targetUrl)
proxy.Transport, err = dataProxyTransport(ds) proxy.Transport, err = DataProxyTransport(ds)
if err != nil { if err != nil {
c.JsonApiErr(400, "Unable to load TLS certificate", err) c.JsonApiErr(400, "Unable to load TLS certificate", err)
return return
......
...@@ -7,15 +7,24 @@ import ( ...@@ -7,15 +7,24 @@ import (
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
) )
func TestDataSourceProxy(t *testing.T) { func TestDataSourceProxy(t *testing.T) {
Convey("When getting graphite datasource proxy", t, func() { Convey("When getting graphite datasource proxy", t, func() {
ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE} 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 := 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") requestUrl, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestUrl} req := http.Request{URL: requestUrl}
...@@ -54,7 +63,80 @@ func TestDataSourceProxy(t *testing.T) { ...@@ -54,7 +63,80 @@ func TestDataSourceProxy(t *testing.T) {
So(queryVals["u"][0], ShouldEqual, "user") So(queryVals["u"][0], ShouldEqual, "user")
So(queryVals["p"][0], ShouldEqual, "password") 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-----`
...@@ -33,7 +33,6 @@ func GetDataSources(c *middleware.Context) { ...@@ -33,7 +33,6 @@ func GetDataSources(c *middleware.Context) {
Database: ds.Database, Database: ds.Database,
User: ds.User, User: ds.User,
BasicAuth: ds.BasicAuth, BasicAuth: ds.BasicAuth,
TlsAuth: ds.TlsAuth,
IsDefault: ds.IsDefault, IsDefault: ds.IsDefault,
JsonData: ds.JsonData, JsonData: ds.JsonData,
} }
...@@ -166,9 +165,6 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource { ...@@ -166,9 +165,6 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
BasicAuth: ds.BasicAuth, BasicAuth: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser, BasicAuthUser: ds.BasicAuthUser,
BasicAuthPassword: ds.BasicAuthPassword, BasicAuthPassword: ds.BasicAuthPassword,
TlsAuth: ds.TlsAuth,
TlsClientCert: ds.TlsClientCert,
TlsClientKey: ds.TlsClientKey,
WithCredentials: ds.WithCredentials, WithCredentials: ds.WithCredentials,
IsDefault: ds.IsDefault, IsDefault: ds.IsDefault,
JsonData: ds.JsonData, JsonData: ds.JsonData,
......
...@@ -64,25 +64,23 @@ type DashboardRedirect struct { ...@@ -64,25 +64,23 @@ type DashboardRedirect struct {
} }
type DataSource struct { type DataSource struct {
Id int64 `json:"id"` Id int64 `json:"id"`
OrgId int64 `json:"orgId"` OrgId int64 `json:"orgId"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"` TypeLogoUrl string `json:"typeLogoUrl"`
Access m.DsAccess `json:"access"` Access m.DsAccess `json:"access"`
Url string `json:"url"` Url string `json:"url"`
Password string `json:"password"` Password string `json:"password"`
User string `json:"user"` User string `json:"user"`
Database string `json:"database"` Database string `json:"database"`
BasicAuth bool `json:"basicAuth"` BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"` BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"` BasicAuthPassword string `json:"basicAuthPassword"`
TlsAuth bool `json:"tlsAuth"` WithCredentials bool `json:"withCredentials"`
TlsClientCert string `json:"tlsClientCert"` IsDefault bool `json:"isDefault"`
TlsClientKey string `json:"tlsClientKey"` JsonData *simplejson.Json `json:"jsonData,omitempty"`
WithCredentials bool `json:"withCredentials"` SecureJsonData map[string]string `json:"secureJsonData,omitempty"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
} }
type DataSourceList []DataSource type DataSourceList []DataSource
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
) )
...@@ -43,12 +44,10 @@ type DataSource struct { ...@@ -43,12 +44,10 @@ type DataSource struct {
BasicAuth bool BasicAuth bool
BasicAuthUser string BasicAuthUser string
BasicAuthPassword string BasicAuthPassword string
TlsAuth bool
TlsClientCert string
TlsClientKey string
WithCredentials bool WithCredentials bool
IsDefault bool IsDefault bool
JsonData *simplejson.Json JsonData *simplejson.Json
SecureJsonData securejsondata.SecureJsonData
Created time.Time Created time.Time
Updated time.Time Updated time.Time
...@@ -80,22 +79,20 @@ func IsKnownDataSourcePlugin(dsType string) bool { ...@@ -80,22 +79,20 @@ func IsKnownDataSourcePlugin(dsType string) bool {
// Also acts as api DTO // Also acts as api DTO
type AddDataSourceCommand struct { type AddDataSourceCommand struct {
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"` Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"` Url string `json:"url"`
Password string `json:"password"` Password string `json:"password"`
Database string `json:"database"` Database string `json:"database"`
User string `json:"user"` User string `json:"user"`
BasicAuth bool `json:"basicAuth"` BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"` BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"` BasicAuthPassword string `json:"basicAuthPassword"`
TlsAuth bool `json:"tlsAuth"` WithCredentials bool `json:"withCredentials"`
TlsClientCert string `json:"tlsClientCert"` IsDefault bool `json:"isDefault"`
TlsClientKey string `json:"tlsClientKey"` JsonData *simplejson.Json `json:"jsonData"`
WithCredentials bool `json:"withCredentials"` SecureJsonData map[string]string `json:"secureJsonData"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
...@@ -104,22 +101,20 @@ type AddDataSourceCommand struct { ...@@ -104,22 +101,20 @@ type AddDataSourceCommand struct {
// Also acts as api DTO // Also acts as api DTO
type UpdateDataSourceCommand struct { type UpdateDataSourceCommand struct {
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
Access DsAccess `json:"access" binding:"Required"` Access DsAccess `json:"access" binding:"Required"`
Url string `json:"url"` Url string `json:"url"`
Password string `json:"password"` Password string `json:"password"`
User string `json:"user"` User string `json:"user"`
Database string `json:"database"` Database string `json:"database"`
BasicAuth bool `json:"basicAuth"` BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"` BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"` BasicAuthPassword string `json:"basicAuthPassword"`
TlsAuth bool `json:"tlsAuth"` WithCredentials bool `json:"withCredentials"`
TlsClientCert string `json:"tlsClientCert"` IsDefault bool `json:"isDefault"`
TlsClientKey string `json:"tlsClientKey"` JsonData *simplejson.Json `json:"jsonData"`
WithCredentials bool `json:"withCredentials"` SecureJsonData map[string]string `json:"secureJsonData"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Id int64 `json:"-"` Id int64 `json:"-"`
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
...@@ -80,11 +81,9 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error { ...@@ -80,11 +81,9 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
BasicAuth: cmd.BasicAuth, BasicAuth: cmd.BasicAuth,
BasicAuthUser: cmd.BasicAuthUser, BasicAuthUser: cmd.BasicAuthUser,
BasicAuthPassword: cmd.BasicAuthPassword, BasicAuthPassword: cmd.BasicAuthPassword,
TlsAuth: cmd.TlsAuth,
TlsClientCert: cmd.TlsClientCert,
TlsClientKey: cmd.TlsClientKey,
WithCredentials: cmd.WithCredentials, WithCredentials: cmd.WithCredentials,
JsonData: cmd.JsonData, JsonData: cmd.JsonData,
SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData),
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
...@@ -129,17 +128,14 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error { ...@@ -129,17 +128,14 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
BasicAuth: cmd.BasicAuth, BasicAuth: cmd.BasicAuth,
BasicAuthUser: cmd.BasicAuthUser, BasicAuthUser: cmd.BasicAuthUser,
BasicAuthPassword: cmd.BasicAuthPassword, BasicAuthPassword: cmd.BasicAuthPassword,
TlsAuth: cmd.TlsAuth,
TlsClientCert: cmd.TlsClientCert,
TlsClientKey: cmd.TlsClientKey,
WithCredentials: cmd.WithCredentials, WithCredentials: cmd.WithCredentials,
JsonData: cmd.JsonData, JsonData: cmd.JsonData,
SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData),
Updated: time.Now(), Updated: time.Now(),
} }
sess.UseBool("is_default") sess.UseBool("is_default")
sess.UseBool("basic_auth") sess.UseBool("basic_auth")
sess.UseBool("tls_auth")
sess.UseBool("with_credentials") sess.UseBool("with_credentials")
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds) _, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
......
...@@ -102,14 +102,8 @@ func addDataSourceMigration(mg *Migrator) { ...@@ -102,14 +102,8 @@ func addDataSourceMigration(mg *Migrator) {
Name: "with_credentials", Type: DB_Bool, Nullable: false, Default: "0", Name: "with_credentials", Type: DB_Bool, Nullable: false, Default: "0",
})) }))
// add columns to activate TLS client auth option // add column that can store TLS client auth data
mg.AddMigration("Add column tls_auth", NewAddColumnMigration(tableV2, &Column{ mg.AddMigration("Add secure json data column", NewAddColumnMigration(tableV2, &Column{
Name: "tls_auth", Type: DB_Bool, Nullable: false, Default: "0", Name: "secure_json_data", Type: DB_Text, Nullable: true,
}))
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,
})) }))
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment