From ea566fff24ed0e1a68cd2e0f53fb616cd9782fc7 Mon Sep 17 00:00:00 2001 From: Daniel Low Date: Tue, 24 Nov 2015 16:17:21 +0000 Subject: [PATCH] Add TLS for mysql Use ssl_mode for mysql and add docs add docs for the new parameters in config Tolerate ssl_mode without client authentication Client cert is not necessary for a SSL connection. So we tolerate failure if client cert is not provided. Improve error message if missing server_cert_name and mode is not skip-verify. --- conf/defaults.ini | 8 ++++- docs/sources/installation/configuration.md | 19 +++++++++- pkg/services/sqlstore/sqlstore.go | 27 ++++++++++++++ pkg/services/sqlstore/tls_mysql.go | 41 ++++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 pkg/services/sqlstore/tls_mysql.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 3655a48a18a..bef3269132c 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -63,9 +63,15 @@ name = grafana user = root password = -# For "postgres" only, either "disable", "require" or "verify-full" +# For "postgres", use either "disable", "require" or "verify-full" +# For "mysql", use either "true", "false", or "skip-verify". ssl_mode = disable +ca_cert_path = +client_key_path = +client_cert_path = +server_cert_name = + # For "sqlite3" only, path relative to data_path setting path = grafana.db diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index daa2d665e07..247cfbb0d19 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -156,7 +156,24 @@ The database user's password (not applicable for `sqlite3`). ### ssl_mode -For `postgres` only, either `disable`, `require` or `verify-full`. +For Postgres, use either `disable`, `require` or `verify-full`. +For MySQL, use either `true`, `false`, or `skip-verify`. + +### ca_cert_path + +(MySQL only) The path to the CA certificate to use. On many linux systems, certs can be found in `/etc/ssl/certs`. + +### client_key_path + +(MySQL only) The path to the client key. Only if server requires client authentication. + +### client_cert_path + +(MySQL only) The path to the client cert. Only if server requires client authentication. + +### server_cert_name + +(MySQL only) The common name field of the certificate used by the `mysql` server. Not necessary if `ssl_mode` is set to `skip-verify`.
diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 88c35d630f8..9217d6c32ec 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -18,8 +18,17 @@ import ( "github.com/go-xorm/xorm" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + "github.com/go-sql-driver/mysql" ) +type MySQLConfig struct { + SslMode string + CaCertPath string + ClientKeyPath string + ClientCertPath string + ServerCertName string +} + var ( x *xorm.Engine dialect migrator.Dialect @@ -30,6 +39,8 @@ var ( Type, Host, Name, User, Pwd, Path, SslMode string } + mysqlConfig MySQLConfig + UseSQLite3 bool ) @@ -115,6 +126,14 @@ func getEngine() (*xorm.Engine, error) { case "mysql": cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name) + if mysqlConfig.SslMode == "true" || mysqlConfig.SslMode == "skip-verify" { + tlsCert, err := makeCert("custom", mysqlConfig) + if err != nil { + return nil, err + } + mysql.RegisterTLSConfig("custom", tlsCert) + cnnstr += "&tls=custom" + } case "postgres": var host, port = "127.0.0.1", "5432" fields := strings.Split(DbCfg.Host, ":") @@ -156,4 +175,12 @@ func LoadConfig() { } DbCfg.SslMode = sec.Key("ssl_mode").String() DbCfg.Path = sec.Key("path").MustString("data/grafana.db") + + if DbCfg.Type == "mysql" { + mysqlConfig.SslMode = DbCfg.SslMode + mysqlConfig.CaCertPath = sec.Key("ca_cert_path").String() + mysqlConfig.ClientKeyPath = sec.Key("client_key_path").String() + mysqlConfig.ClientCertPath = sec.Key("client_cert_path").String() + mysqlConfig.ServerCertName = sec.Key("server_cert_name").String() + } } diff --git a/pkg/services/sqlstore/tls_mysql.go b/pkg/services/sqlstore/tls_mysql.go new file mode 100644 index 00000000000..f5c25a63149 --- /dev/null +++ b/pkg/services/sqlstore/tls_mysql.go @@ -0,0 +1,41 @@ +package sqlstore + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" +) + +func makeCert(tlsPoolName string, config MySQLConfig) (*tls.Config, error) { + rootCertPool := x509.NewCertPool() + pem, err := ioutil.ReadFile(config.CaCertPath) + if err != nil { + return nil, fmt.Errorf("Could not read DB CA Cert path: %v", config.CaCertPath) + } + if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { + return nil, err + } + clientCert := make([]tls.Certificate, 0, 1) + if (config.ClientCertPath != "" && config.ClientKeyPath != "") { + + certs, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientKeyPath) + if err != nil { + return nil, err + } + clientCert = append(clientCert, certs) + } + tlsConfig := &tls.Config{ + RootCAs: rootCertPool, + Certificates: clientCert, + } + tlsConfig.ServerName = config.ServerCertName + if config.SslMode == "skip-verify" { + tlsConfig.InsecureSkipVerify = true + } + // Return more meaningful error before it is too late + if config.ServerCertName == "" && !tlsConfig.InsecureSkipVerify{ + return nil, fmt.Errorf("server_cert_name is missing. Consider using ssl_mode = skip-verify.") + } + return tlsConfig, nil +}