Commit 3de693af by Marcus Efraimsson Committed by GitHub

MySQL: Limit datasource error details returned from the backend (#19373)

Only return certain mysql errors from backend.
The following errors is returned as is from backend:
error code 1064 (parse error)
error code 1054 (bad column/field selected)
error code 1146 (table not exists)
Any other errors is logged and returned as a generic
error.
Restrict use of certain functions:
Do not allow usage of the following in query:
system_user()
session_user()
current_user() or current_user
user()
show grants

Fixes #19360
parent 7c499ffd
...@@ -38,11 +38,11 @@ func newMssqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin ...@@ -38,11 +38,11 @@ func newMssqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
MetricColumnTypes: []string{"VARCHAR", "CHAR", "NVARCHAR", "NCHAR"}, MetricColumnTypes: []string{"VARCHAR", "CHAR", "NVARCHAR", "NCHAR"},
} }
rowTransformer := mssqlRowTransformer{ queryResultTransformer := mssqlQueryResultTransformer{
log: logger, log: logger,
} }
return sqleng.NewSqlQueryEndpoint(&config, &rowTransformer, newMssqlMacroEngine(), logger) return sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newMssqlMacroEngine(), logger)
} }
func generateConnectionString(datasource *models.DataSource) (string, error) { func generateConnectionString(datasource *models.DataSource) (string, error) {
...@@ -62,11 +62,11 @@ func generateConnectionString(datasource *models.DataSource) (string, error) { ...@@ -62,11 +62,11 @@ func generateConnectionString(datasource *models.DataSource) (string, error) {
return connStr, nil return connStr, nil
} }
type mssqlRowTransformer struct { type mssqlQueryResultTransformer struct {
log log.Logger log log.Logger
} }
func (t *mssqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) { func (t *mssqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
values := make([]interface{}, len(columnTypes)) values := make([]interface{}, len(columnTypes))
valuePtrs := make([]interface{}, len(columnTypes)) valuePtrs := make([]interface{}, len(columnTypes))
...@@ -100,3 +100,7 @@ func (t *mssqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *cor ...@@ -100,3 +100,7 @@ func (t *mssqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *cor
return values, nil return values, nil
} }
func (t *mssqlQueryResultTransformer) TransformQueryError(err error) error {
return err
}
package mysql package mysql
import ( import (
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/sqleng" "github.com/grafana/grafana/pkg/tsdb/sqleng"
) )
...@@ -13,19 +15,29 @@ import ( ...@@ -13,19 +15,29 @@ import (
const rsIdentifier = `([_a-zA-Z0-9]+)` const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)` const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
var restrictedRegExp = regexp.MustCompile(`(?im)([\s]*show[\s]+grants|[\s,]session_user\([^\)]*\)|[\s,]current_user(\([^\)]*\))?|[\s,]system_user\([^\)]*\)|[\s,]user\([^\)]*\))([\s,;]|$)`)
type mySqlMacroEngine struct { type mySqlMacroEngine struct {
*sqleng.SqlMacroEngineBase *sqleng.SqlMacroEngineBase
timeRange *tsdb.TimeRange timeRange *tsdb.TimeRange
query *tsdb.Query query *tsdb.Query
logger log.Logger
} }
func newMysqlMacroEngine() sqleng.SqlMacroEngine { func newMysqlMacroEngine(logger log.Logger) sqleng.SqlMacroEngine {
return &mySqlMacroEngine{SqlMacroEngineBase: sqleng.NewSqlMacroEngineBase()} return &mySqlMacroEngine{SqlMacroEngineBase: sqleng.NewSqlMacroEngineBase(), logger: logger}
} }
func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) { func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
m.timeRange = timeRange m.timeRange = timeRange
m.query = query m.query = query
matches := restrictedRegExp.FindAllStringSubmatch(sql, 1)
if len(matches) > 0 {
m.logger.Error("show grants, session_user(), current_user(), system_user() or user() not allowed in query")
return "", errors.New("Invalid query. Inspect Grafana server log for details")
}
rExp, _ := regexp.Compile(sExpr) rExp, _ := regexp.Compile(sExpr)
var macroError error var macroError error
......
...@@ -6,13 +6,16 @@ import ( ...@@ -6,13 +6,16 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
func TestMacroEngine(t *testing.T) { func TestMacroEngine(t *testing.T) {
Convey("MacroEngine", t, func() { Convey("MacroEngine", t, func() {
engine := &mySqlMacroEngine{} engine := &mySqlMacroEngine{
logger: log.New("test"),
}
query := &tsdb.Query{} query := &tsdb.Query{}
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() { Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
...@@ -157,5 +160,33 @@ func TestMacroEngine(t *testing.T) { ...@@ -157,5 +160,33 @@ func TestMacroEngine(t *testing.T) {
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
}) })
}) })
Convey("Given queries that contains unallowed user functions", func() {
tcs := []string{
"select \nSESSION_USER(), abc",
"SELECT session_User( ) ",
"SELECT session_User( )\n",
"SELECT current_user",
"SELECT current_USER",
"SELECT current_user()",
"SELECT Current_User()",
"SELECT current_user( )",
"SELECT current_user(\t )",
"SELECT user()",
"SELECT USER()",
"SELECT SYSTEM_USER()",
"SELECT System_User()",
"SELECT System_User( )",
"SELECT System_User(\t \t)",
"SHOW \t grants",
" show Grants\n",
"show grants;",
}
for _, tc := range tcs {
_, err := engine.Interpolate(nil, nil, tc)
So(err.Error(), ShouldEqual, "Invalid query. Inspect Grafana server log for details")
}
})
}) })
} }
...@@ -2,11 +2,14 @@ package mysql ...@@ -2,11 +2,14 @@ package mysql
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/VividCortex/mysqlerr"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
...@@ -59,18 +62,18 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin ...@@ -59,18 +62,18 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
MetricColumnTypes: []string{"CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"}, MetricColumnTypes: []string{"CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT"},
} }
rowTransformer := mysqlRowTransformer{ rowTransformer := mysqlQueryResultTransformer{
log: logger, log: logger,
} }
return sqleng.NewSqlQueryEndpoint(&config, &rowTransformer, newMysqlMacroEngine(), logger) return sqleng.NewSqlQueryEndpoint(&config, &rowTransformer, newMysqlMacroEngine(logger), logger)
} }
type mysqlRowTransformer struct { type mysqlQueryResultTransformer struct {
log log.Logger log log.Logger
} }
func (t *mysqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) { func (t *mysqlQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
values := make([]interface{}, len(columnTypes)) values := make([]interface{}, len(columnTypes))
for i := range values { for i := range values {
...@@ -128,3 +131,16 @@ func (t *mysqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *cor ...@@ -128,3 +131,16 @@ func (t *mysqlRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *cor
return values, nil return values, nil
} }
func (t *mysqlQueryResultTransformer) TransformQueryError(err error) error {
if driverErr, ok := err.(*mysql.MySQLError); ok {
if driverErr.Number != mysqlerr.ER_PARSE_ERROR && driverErr.Number != mysqlerr.ER_BAD_FIELD_ERROR && driverErr.Number != mysqlerr.ER_NO_SUCH_TABLE {
t.log.Error("query error", "err", err)
return errQueryFailed
}
}
return err
}
var errQueryFailed = errors.New("Query failed. Please inspect Grafana server log for details")
...@@ -33,13 +33,13 @@ func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndp ...@@ -33,13 +33,13 @@ func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndp
MetricColumnTypes: []string{"UNKNOWN", "TEXT", "VARCHAR", "CHAR"}, MetricColumnTypes: []string{"UNKNOWN", "TEXT", "VARCHAR", "CHAR"},
} }
rowTransformer := postgresRowTransformer{ queryResultTransformer := postgresQueryResultTransformer{
log: logger, log: logger,
} }
timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false) timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false)
return sqleng.NewSqlQueryEndpoint(&config, &rowTransformer, newPostgresMacroEngine(timescaledb), logger) return sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newPostgresMacroEngine(timescaledb), logger)
} }
func generateConnectionString(datasource *models.DataSource) string { func generateConnectionString(datasource *models.DataSource) string {
...@@ -54,11 +54,11 @@ func generateConnectionString(datasource *models.DataSource) string { ...@@ -54,11 +54,11 @@ func generateConnectionString(datasource *models.DataSource) string {
return u.String() return u.String()
} }
type postgresRowTransformer struct { type postgresQueryResultTransformer struct {
log log.Logger log log.Logger
} }
func (t *postgresRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) { func (t *postgresQueryResultTransformer) TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
values := make([]interface{}, len(columnTypes)) values := make([]interface{}, len(columnTypes))
valuePtrs := make([]interface{}, len(columnTypes)) valuePtrs := make([]interface{}, len(columnTypes))
...@@ -93,3 +93,7 @@ func (t *postgresRowTransformer) Transform(columnTypes []*sql.ColumnType, rows * ...@@ -93,3 +93,7 @@ func (t *postgresRowTransformer) Transform(columnTypes []*sql.ColumnType, rows *
return values, nil return values, nil
} }
func (t *postgresQueryResultTransformer) TransformQueryError(err error) error {
return err
}
...@@ -31,9 +31,12 @@ type SqlMacroEngine interface { ...@@ -31,9 +31,12 @@ type SqlMacroEngine interface {
Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error)
} }
// SqlTableRowTransformer transforms a query result row to RowValues with proper types. // SqlQueryResultTransformer transforms a query result row to RowValues with proper types.
type SqlTableRowTransformer interface { type SqlQueryResultTransformer interface {
Transform(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) // TransformQueryResult transforms a query result row to RowValues with proper types.
TransformQueryResult(columnTypes []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error)
// TransformQueryError transforms a query error.
TransformQueryError(err error) error
} }
type engineCacheType struct { type engineCacheType struct {
...@@ -55,7 +58,7 @@ var NewXormEngine = func(driverName string, connectionString string) (*xorm.Engi ...@@ -55,7 +58,7 @@ var NewXormEngine = func(driverName string, connectionString string) (*xorm.Engi
type sqlQueryEndpoint struct { type sqlQueryEndpoint struct {
macroEngine SqlMacroEngine macroEngine SqlMacroEngine
rowTransformer SqlTableRowTransformer queryResultTransformer SqlQueryResultTransformer
engine *xorm.Engine engine *xorm.Engine
timeColumnNames []string timeColumnNames []string
metricColumnTypes []string metricColumnTypes []string
...@@ -70,9 +73,9 @@ type SqlQueryEndpointConfiguration struct { ...@@ -70,9 +73,9 @@ type SqlQueryEndpointConfiguration struct {
MetricColumnTypes []string MetricColumnTypes []string
} }
var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, rowTransformer SqlTableRowTransformer, macroEngine SqlMacroEngine, log log.Logger) (tsdb.TsdbQueryEndpoint, error) { var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, queryResultTransformer SqlQueryResultTransformer, macroEngine SqlMacroEngine, log log.Logger) (tsdb.TsdbQueryEndpoint, error) {
queryEndpoint := sqlQueryEndpoint{ queryEndpoint := sqlQueryEndpoint{
rowTransformer: rowTransformer, queryResultTransformer: queryResultTransformer,
macroEngine: macroEngine, macroEngine: macroEngine,
timeColumnNames: []string{"time"}, timeColumnNames: []string{"time"},
log: log, log: log,
...@@ -160,7 +163,7 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource, ...@@ -160,7 +163,7 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
rows, err := db.Query(rawSQL) rows, err := db.Query(rawSQL)
if err != nil { if err != nil {
queryResult.Error = err queryResult.Error = e.queryResultTransformer.TransformQueryError(err)
return return
} }
...@@ -242,7 +245,7 @@ func (e *sqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows, ...@@ -242,7 +245,7 @@ func (e *sqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
return fmt.Errorf("query row limit exceeded, limit %d", rowLimit) return fmt.Errorf("query row limit exceeded, limit %d", rowLimit)
} }
values, err := e.rowTransformer.Transform(columnTypes, rows) values, err := e.queryResultTransformer.TransformQueryResult(columnTypes, rows)
if err != nil { if err != nil {
return err return err
} }
...@@ -340,7 +343,7 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.R ...@@ -340,7 +343,7 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.R
return fmt.Errorf("query row limit exceeded, limit %d", rowLimit) return fmt.Errorf("query row limit exceeded, limit %d", rowLimit)
} }
values, err := e.rowTransformer.Transform(columnTypes, rows) values, err := e.queryResultTransformer.TransformQueryResult(columnTypes, rows)
if err != nil { if err != nil {
return err return err
} }
......
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