mirror of
https://github.com/grafana/grafana.git
synced 2025-09-26 09:14:03 +08:00
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
This commit is contained in:

committed by
GitHub

parent
7c499ffdd8
commit
3de693af49
@ -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) {
|
|||||||
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
|
|||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *mssqlQueryResultTransformer) TransformQueryError(err error) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
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 (
|
|||||||
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 (
|
|||||||
"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) {
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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 {
|
|||||||
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 *
|
|||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *postgresQueryResultTransformer) TransformQueryError(err error) error {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -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
|
|||||||
|
|
||||||
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 {
|
|||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
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
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user