Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions go/vt/tabletserver/query_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,16 @@ type QueryEngine struct {
maxResultSize sync2.AtomicInt64
maxDMLRows sync2.AtomicInt64
streamBufferSize sync2.AtomicInt64
strictTableAcl bool
enableAutoCommit bool
exemptACL string
// tableaclExemptCount count the number of accesses allowed
// based on membership in the superuser ACL
tableaclExemptCount sync2.AtomicInt64
tableaclAllowed *stats.MultiCounters
tableaclDenied *stats.MultiCounters
tableaclPseudoDenied *stats.MultiCounters
strictTableAcl bool
enableAutoCommit bool
enableTableAclDryRun bool
exemptACL string

// Loggers
accessCheckerLogger *logutil.ThrottledLogger
Expand Down Expand Up @@ -166,6 +173,7 @@ func NewQueryEngine(config Config) *QueryEngine {
qe.strictMode.Set(1)
}
qe.strictTableAcl = config.StrictTableAcl
qe.enableTableAclDryRun = config.EnableTableAclDryRun
qe.exemptACL = config.TableAclExemptACL
qe.maxResultSize = sync2.AtomicInt64(config.MaxResultSize)
qe.maxDMLRows = sync2.AtomicInt64(config.MaxDMLRows)
Expand All @@ -174,6 +182,9 @@ func NewQueryEngine(config Config) *QueryEngine {
// Loggers
qe.accessCheckerLogger = logutil.NewThrottledLogger("accessChecker", 1*time.Second)

var tableACLAllowedName string
var tableACLDeniedName string
var tableACLPseudoDeniedName string
// Stats
if config.EnablePublishStats {
stats.Publish(config.StatsPrefix+"MaxResultSize", stats.IntFunc(qe.maxResultSize.Get))
Expand All @@ -183,7 +194,16 @@ func NewQueryEngine(config Config) *QueryEngine {
stats.Publish(config.StatsPrefix+"RowcacheSpotCheckRatio", stats.FloatFunc(func() float64 {
return float64(qe.spotCheckFreq.Get()) / spotCheckMultiplier
}))
stats.Publish(config.StatsPrefix+"TableACLExemptCount", stats.IntFunc(qe.tableaclExemptCount.Get))
tableACLAllowedName = "TableACLAllowed"
tableACLDeniedName = "TableACLDenied"
tableACLPseudoDeniedName = "TableACLPseudoDenied"
}

qe.tableaclAllowed = stats.NewMultiCounters(tableACLAllowedName, []string{"TableName", "TableGroup", "PlanID", "Username"})
qe.tableaclDenied = stats.NewMultiCounters(tableACLDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"})
qe.tableaclPseudoDenied = stats.NewMultiCounters(tableACLPseudoDeniedName, []string{"TableName", "TableGroup", "PlanID", "Username"})

return qe
}

Expand Down
28 changes: 24 additions & 4 deletions go/vt/tabletserver/query_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (qre *QueryExecutor) execDmlAutoCommit() (reply *mproto.QueryResult, err er
return reply, err
}

// checkPermissions
func (qre *QueryExecutor) checkPermissions() error {
// Skip permissions check if we have a background context.
if qre.ctx == context.Background() {
Expand All @@ -207,19 +208,38 @@ func (qre *QueryExecutor) checkPermissions() error {
case QR_FAIL_RETRY:
return NewTabletError(ErrRetry, "Query disallowed due to rule: %s", desc)
}
// a superuser that exempts from table ACL checking

// a superuser that exempts from table ACL checking.
if qre.qe.exemptACL == username {
qre.qe.tableaclExemptCount.Add(1)
return nil
}
// Perform table ACL check if it is enabled
if qre.plan.Authorized != nil && !qre.plan.Authorized.IsMember(username) {
tableACLStatsKey := []string{
qre.plan.TableName,
// TODO(shengzhe): use table group instead of username.
username,
qre.plan.PlanId.String(),
username,
}
if qre.plan.Authorized == nil {
return NewTabletError(ErrFail, "table acl error: nil acl")
}
// perform table ACL check if it is enabled.
if !qre.plan.Authorized.IsMember(username) {
if qre.qe.enableTableAclDryRun {
qre.qe.tableaclPseudoDenied.Add(tableACLStatsKey, 1)
return nil
}
errStr := fmt.Sprintf("table acl error: %q cannot run %v on table %q", username, qre.plan.PlanId, qre.plan.TableName)
// Raise error if in strictTableAcl mode, else just log an error
// raise error if in strictTableAcl mode, else just log an error.
if qre.qe.strictTableAcl {
qre.qe.tableaclDenied.Add(tableACLStatsKey, 1)
return NewTabletError(ErrFail, "%s", errStr)
}
qre.qe.accessCheckerLogger.Errorf("%s", errStr)
return nil
}
qre.qe.tableaclAllowed.Add(tableACLStatsKey, 1)
return nil
}

Expand Down
59 changes: 59 additions & 0 deletions go/vt/tabletserver/query_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,65 @@ func TestQueryExecutorTableAclExemptACL(t *testing.T) {
}
}

func TestQueryExecutorTableAclDryRun(t *testing.T) {
aclName := fmt.Sprintf("simpleacl-test-%d", rand.Int63())
tableacl.Register(aclName, &simpleacl.Factory{})
tableacl.SetDefaultACL(aclName)
db := setUpQueryExecutorTest()
query := "select * from test_table limit 1000"
want := &mproto.QueryResult{
Fields: getTestTableFields(),
RowsAffected: 0,
Rows: [][]sqltypes.Value{},
}
db.AddQuery(query, want)
db.AddQuery("select * from test_table where 1 != 1", &mproto.QueryResult{
Fields: getTestTableFields(),
})

username := "u2"
callInfo := &fakeCallInfo{
remoteAddr: "1.2.3.4",
username: username,
}
ctx := callinfo.NewContext(context.Background(), callInfo)

config := &tableaclpb.Config{
TableGroups: []*tableaclpb.TableGroupSpec{{
Name: "group02",
TableNamesOrPrefixes: []string{"test_table"},
Readers: []string{"u1"},
}},
}

if err := tableacl.InitFromProto(config); err != nil {
t.Fatalf("unable to load tableacl config, error: %v", err)
}

tableACLStatsKey := strings.Join([]string{
"test_table",
username,
planbuilder.PLAN_PASS_SELECT.String(),
username,
}, ".")
// enable Config.StrictTableAcl
sqlQuery := newTestSQLQuery(ctx, enableRowCache|enableSchemaOverrides|enableStrict|enableStrictTableAcl)
sqlQuery.qe.enableTableAclDryRun = true
qre := newTestQueryExecutor(ctx, sqlQuery, query, 0)
defer sqlQuery.disallowQueries()
checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
beforeCount := sqlQuery.qe.tableaclPseudoDenied.Counters.Counts()[tableACLStatsKey]
// query should fail because current user do not have read permissions
_, err := qre.Execute()
if err != nil {
t.Fatalf("qre.Execute() = %v, want: nil", err)
}
afterCount := sqlQuery.qe.tableaclPseudoDenied.Counters.Counts()[tableACLStatsKey]
if afterCount-beforeCount != 1 {
t.Fatalf("table acl pseudo denied count should increase by one. got: %d, want: %d", afterCount, beforeCount+1)
}
}

func TestQueryExecutorBlacklistQRFail(t *testing.T) {
db := setUpQueryExecutorTest()
query := "select * from test_table where name = 1 limit 1000"
Expand Down
95 changes: 49 additions & 46 deletions go/vt/tabletserver/queryctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func init() {
flag.BoolVar(&qsConfig.StrictMode, "queryserver-config-strict-mode", DefaultQsConfig.StrictMode, "allow only predictable DMLs and enforces MySQL's STRICT_TRANS_TABLES")
// tableacl related configurations.
flag.BoolVar(&qsConfig.StrictTableAcl, "queryserver-config-strict-table-acl", DefaultQsConfig.StrictTableAcl, "only allow queries that pass table acl checks")
flag.BoolVar(&qsConfig.EnableTableAclDryRun, "queryserver-config-enable-table-acl-dry-run", DefaultQsConfig.EnableTableAclDryRun, "If this flag is enabled, tabletserver will emit monitoring metrics and let the request pass regardless of table acl check results")
flag.StringVar(&qsConfig.TableAclExemptACL, "queryserver-config-acl-exempt-acl", DefaultQsConfig.TableAclExemptACL, "an acl that exempt from table acl checking (this acl is free to access any vitess tables).")
flag.BoolVar(&qsConfig.TerseErrors, "queryserver-config-terse-errors", DefaultQsConfig.TerseErrors, "prevent bind vars from escaping in returned errors")
flag.BoolVar(&qsConfig.EnablePublishStats, "queryserver-config-enable-publish-stats", DefaultQsConfig.EnablePublishStats, "set this flag to true makes queryservice publish monitoring stats")
Expand Down Expand Up @@ -103,29 +104,30 @@ func (c *RowCacheConfig) GetSubprocessFlags(socket string) []string {

// Config contains all the configuration for query service
type Config struct {
PoolSize int
StreamPoolSize int
TransactionCap int
TransactionTimeout float64
MaxResultSize int
MaxDMLRows int
StreamBufferSize int
QueryCacheSize int
SchemaReloadTime float64
QueryTimeout float64
TxPoolTimeout float64
IdleTimeout float64
RowCache RowCacheConfig
SpotCheckRatio float64
StrictMode bool
StrictTableAcl bool
TerseErrors bool
EnablePublishStats bool
EnableAutoCommit bool
StatsPrefix string
DebugURLPrefix string
PoolNamePrefix string
TableAclExemptACL string
PoolSize int
StreamPoolSize int
TransactionCap int
TransactionTimeout float64
MaxResultSize int
MaxDMLRows int
StreamBufferSize int
QueryCacheSize int
SchemaReloadTime float64
QueryTimeout float64
TxPoolTimeout float64
IdleTimeout float64
RowCache RowCacheConfig
SpotCheckRatio float64
StrictMode bool
StrictTableAcl bool
TerseErrors bool
EnablePublishStats bool
EnableAutoCommit bool
EnableTableAclDryRun bool
StatsPrefix string
DebugURLPrefix string
PoolNamePrefix string
TableAclExemptACL string
}

// DefaultQSConfig is the default value for the query service config.
Expand All @@ -137,29 +139,30 @@ type Config struct {
// great (the overhead makes the final packets on the wire about twice
// bigger than this).
var DefaultQsConfig = Config{
PoolSize: 16,
StreamPoolSize: 750,
TransactionCap: 20,
TransactionTimeout: 30,
MaxResultSize: 10000,
MaxDMLRows: 500,
QueryCacheSize: 5000,
SchemaReloadTime: 30 * 60,
QueryTimeout: 0,
TxPoolTimeout: 1,
IdleTimeout: 30 * 60,
StreamBufferSize: 32 * 1024,
RowCache: RowCacheConfig{Memory: -1, Connections: -1, Threads: -1},
SpotCheckRatio: 0,
StrictMode: true,
StrictTableAcl: false,
TerseErrors: false,
EnablePublishStats: true,
EnableAutoCommit: false,
StatsPrefix: "",
DebugURLPrefix: "/debug",
PoolNamePrefix: "",
TableAclExemptACL: "",
PoolSize: 16,
StreamPoolSize: 750,
TransactionCap: 20,
TransactionTimeout: 30,
MaxResultSize: 10000,
MaxDMLRows: 500,
QueryCacheSize: 5000,
SchemaReloadTime: 30 * 60,
QueryTimeout: 0,
TxPoolTimeout: 1,
IdleTimeout: 30 * 60,
StreamBufferSize: 32 * 1024,
RowCache: RowCacheConfig{Memory: -1, Connections: -1, Threads: -1},
SpotCheckRatio: 0,
StrictMode: true,
StrictTableAcl: false,
TerseErrors: false,
EnablePublishStats: true,
EnableAutoCommit: false,
EnableTableAclDryRun: false,
StatsPrefix: "",
DebugURLPrefix: "/debug",
PoolNamePrefix: "",
TableAclExemptACL: "",
}

var qsConfig Config
Expand Down