From 56b7e2dfaf0a38161a7f62490505d2ba5a39d113 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Wed, 24 Aug 2016 23:43:25 -0400 Subject: [PATCH] Added support for TLS client auth for datasource proxies (#5801) --- pkg/api/app_routes.go | 17 ++++++++- pkg/api/dataproxy.go | 35 ++++++++++++++----- pkg/api/datasources.go | 4 +++ pkg/api/dtos/models.go | 3 ++ pkg/models/datasource.go | 9 +++++ pkg/services/sqlstore/datasource.go | 7 ++++ .../sqlstore/migrations/datasource_mig.go | 11 ++++++ .../plugins/partials/ds_http_settings.html | 18 ++++++++++ 8 files changed, 94 insertions(+), 10 deletions(-) diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 7923b0475a3..aee67ac8478 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -1,6 +1,11 @@ package api import ( + "crypto/tls" + "net" + "net/http" + "time" + "gopkg.in/macaron.v1" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -11,6 +16,16 @@ import ( "github.com/grafana/grafana/pkg/util" ) +var pluginProxyTransport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, +} + func InitAppPluginRoutes(r *macaron.Macaron) { for _, plugin := range plugins.Apps { for _, route := range plugin.Routes { @@ -40,7 +55,7 @@ func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler path := c.Params("*") proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId) - proxy.Transport = dataProxyTransport + proxy.Transport = pluginProxyTransport proxy.ServeHTTP(c.Resp, c.Req.Request) } } diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 34c4271ebf6..7f527ed3c74 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -17,14 +17,27 @@ import ( "github.com/grafana/grafana/pkg/util" ) -var dataProxyTransport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, +func dataProxyTransport(ds *m.DataSource) (*http.Transport, error) { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + } + + if ds.TlsAuth { + cert, err := tls.LoadX509KeyPair(ds.TlsClientCert, ds.TlsClientKey) + if err != nil { + return nil, err + } + transport.TLSClientConfig.Certificates = []tls.Certificate{cert} + } + return transport, nil } func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { @@ -128,7 +141,11 @@ func ProxyDataSourceRequest(c *middleware.Context) { } proxy := NewReverseProxy(ds, proxyPath, targetUrl) - proxy.Transport = dataProxyTransport + proxy.Transport, err = dataProxyTransport(ds) + if err != nil { + c.JsonApiErr(400, "Unable to load TLS certificate", err) + return + } proxy.ServeHTTP(c.Resp, c.Req.Request) c.Resp.Header().Del("Set-Cookie") } diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 2b9964f7a71..6d30a7dc44a 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -33,6 +33,7 @@ func GetDataSources(c *middleware.Context) { Database: ds.Database, User: ds.User, BasicAuth: ds.BasicAuth, + TlsAuth: ds.TlsAuth, IsDefault: ds.IsDefault, JsonData: ds.JsonData, } @@ -165,6 +166,9 @@ 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 170a5a868fc..d902f4a58a0 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -77,6 +77,9 @@ type DataSource struct { 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"` diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index 883cc3a90bd..c4400e9f3dd 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -43,6 +43,9 @@ type DataSource struct { BasicAuth bool BasicAuthUser string BasicAuthPassword string + TlsAuth bool + TlsClientCert string + TlsClientKey string WithCredentials bool IsDefault bool JsonData *simplejson.Json @@ -87,6 +90,9 @@ type AddDataSourceCommand struct { 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"` @@ -108,6 +114,9 @@ type UpdateDataSourceCommand struct { 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"` diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 0e6219785bd..29581c03096 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -80,6 +80,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, Created: time.Now(), @@ -126,6 +129,9 @@ 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, Updated: time.Now(), @@ -133,6 +139,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error { 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 90f7dac85e6..58205bbb196 100644 --- a/pkg/services/sqlstore/migrations/datasource_mig.go +++ b/pkg/services/sqlstore/migrations/datasource_mig.go @@ -101,4 +101,15 @@ func addDataSourceMigration(mg *Migrator) { mg.AddMigration("Add column with_credentials", NewAddColumnMigration(tableV2, &Column{ 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, + })) } diff --git a/public/app/features/plugins/partials/ds_http_settings.html b/public/app/features/plugins/partials/ds_http_settings.html index 0022f6e6c19..5d980f43fdd 100644 --- a/public/app/features/plugins/partials/ds_http_settings.html +++ b/public/app/features/plugins/partials/ds_http_settings.html @@ -49,6 +49,10 @@ label="With Credentials" checked="current.withCredentials" switch-class="max-width-6"> + +
@@ -64,5 +68,19 @@
+ +
+ + Client Cert + + +
+ +
+ + Client Key + + +