Skip to content

Commit 2b71644

Browse files
authored
fix: fixes bug with authorizer leakage in show queries (#27196) (#27197)
This PR fixes and authorization leak when running `SHOW QUERIES` as a user with access to the database you are using. Previously non-admin users could run `SHOW QUERIES` on DBs they owned, but, there was no filtering to stop them from seeing queries on other dbs too: ``` # as admin > CREATE USER bar WITH PASSWORD 'password' > GRANT ALL on "mydb" TO "bar" # as 'bar' user > SHOW QUERIES ERR: error authorizing query: bar not authorized to execute statement 'SHOW QUERIES', requires READ on > use mydb Using database mydb > SHOW QUERIES qid query database duration status --- ----- -------- -------- ------ 31 SELECT * FROM actions foobar 585ms running # this wasn't granted to 'bar'!! 32 SHOW QUERIES mydb 15µs running ``` (cherry picked from commit 5778e07)
1 parent d5a48e0 commit 2b71644

File tree

2 files changed

+128
-2
lines changed

2 files changed

+128
-2
lines changed

query/executor_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"time"
99

1010
"github.com/influxdata/influxdb/query"
11+
"github.com/influxdata/influxdb/services/meta"
1112
"github.com/influxdata/influxql"
13+
"github.com/stretchr/testify/require"
1214
)
1315

1416
var errUnexpected = errors.New("unexpected error")
@@ -354,6 +356,126 @@ func TestQueryExecutor_ShowQueries(t *testing.T) {
354356
}
355357
}
356358

359+
// TestQueryExecutor_ShowQueries_NonAdminFiltering verifies that SHOW QUERIES
360+
// filters results based on the requesting user's database-level read
361+
// permissions. A non-admin user should only see queries running against
362+
// databases they have read access to, while an admin should see all queries.
363+
func TestQueryExecutor_ShowQueries_NonAdminFiltering(t *testing.T) {
364+
const (
365+
dbColumn = 2
366+
allowedDB = "mydb"
367+
forbiddenDB = "secretdb"
368+
nonAdminUser = "bar"
369+
adminUser = "alice"
370+
)
371+
372+
e := NewQueryExecutor()
373+
374+
// blockCh keeps the "long-running" queries alive so they appear in SHOW QUERIES.
375+
blockCh := make(chan struct{})
376+
defer close(blockCh)
377+
378+
e.StatementExecutor = &StatementExecutor{
379+
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
380+
switch stmt.(type) {
381+
case *influxql.ShowQueriesStatement:
382+
return e.TaskManager.ExecuteStatement(ctx, stmt)
383+
case *influxql.SelectStatement:
384+
// Block until the test completes so this query stays visible.
385+
<-blockCh
386+
return nil
387+
}
388+
t.Errorf("unexpected statement: %s", stmt)
389+
return errUnexpected
390+
},
391+
}
392+
393+
// Start a "long-running" query on the forbidden database (by the admin user).
394+
q1, err := influxql.ParseQuery("SELECT * FROM cpu")
395+
require.NoError(t, err)
396+
go func() {
397+
discardOutput(e.ExecuteQuery(q1, query.ExecutionOptions{
398+
Database: forbiddenDB,
399+
UserID: adminUser,
400+
}, nil))
401+
}()
402+
403+
// Start a "long-running" query on the allowed database (by the non-admin user).
404+
q2, err := influxql.ParseQuery("SELECT * FROM mem")
405+
require.NoError(t, err)
406+
go func() {
407+
discardOutput(e.ExecuteQuery(q2, query.ExecutionOptions{
408+
Database: allowedDB,
409+
UserID: nonAdminUser,
410+
}, nil))
411+
}()
412+
413+
// Give the background queries a moment to register with the TaskManager.
414+
time.Sleep(50 * time.Millisecond)
415+
416+
// Verify both queries are tracked (sanity check).
417+
allQueries := e.TaskManager.Queries()
418+
require.GreaterOrEqual(t, len(allQueries), 2, "expected at least 2 running queries")
419+
420+
// Helper to collect database names from SHOW QUERIES result rows.
421+
databases := func(rows [][]interface{}) []string {
422+
var dbs []string
423+
for _, row := range rows {
424+
if db, ok := row[dbColumn].(string); ok && db != "" {
425+
dbs = append(dbs, db)
426+
}
427+
}
428+
return dbs
429+
}
430+
431+
t.Run("non-admin user only sees allowed databases", func(t *testing.T) {
432+
showQ, err := influxql.ParseQuery("SHOW QUERIES")
433+
require.NoError(t, err)
434+
435+
nonAdminUserInfo := &meta.UserInfo{
436+
Name: nonAdminUser,
437+
Admin: false,
438+
Privileges: map[string]influxql.Privilege{
439+
allowedDB: influxql.AllPrivileges,
440+
},
441+
}
442+
443+
results := e.ExecuteQuery(showQ, query.ExecutionOptions{
444+
UserID: nonAdminUser,
445+
CoarseAuthorizer: nonAdminUserInfo,
446+
}, nil)
447+
result := <-results
448+
require.NoError(t, result.Err)
449+
require.Len(t, result.Series, 1)
450+
451+
dbs := databases(result.Series[0].Values)
452+
require.Contains(t, dbs, allowedDB, "non-admin user should see queries on databases they have read access to")
453+
require.NotContains(t, dbs, forbiddenDB, "non-admin user should not see queries on databases they lack read access to")
454+
})
455+
456+
t.Run("admin user sees all databases", func(t *testing.T) {
457+
showQ, err := influxql.ParseQuery("SHOW QUERIES")
458+
require.NoError(t, err)
459+
460+
adminUserInfo := &meta.UserInfo{
461+
Name: adminUser,
462+
Admin: true,
463+
}
464+
465+
results := e.ExecuteQuery(showQ, query.ExecutionOptions{
466+
UserID: adminUser,
467+
CoarseAuthorizer: adminUserInfo,
468+
}, nil)
469+
result := <-results
470+
require.NoError(t, result.Err)
471+
require.Len(t, result.Series, 1)
472+
473+
dbs := databases(result.Series[0].Values)
474+
require.Contains(t, dbs, allowedDB, "admin user should see all queries")
475+
require.Contains(t, dbs, forbiddenDB, "admin user should see all queries")
476+
})
477+
}
478+
357479
func TestQueryExecutor_Limit_Timeout(t *testing.T) {
358480
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
359481
if err != nil {

query/task_manager.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func NewTaskManager() *TaskManager {
103103
func (t *TaskManager) ExecuteStatement(ctx *ExecutionContext, stmt influxql.Statement) error {
104104
switch stmt := stmt.(type) {
105105
case *influxql.ShowQueriesStatement:
106-
rows, err := t.executeShowQueriesStatement(stmt)
106+
rows, err := t.executeShowQueriesStatement(stmt, ctx.CoarseAuthorizer)
107107
if err != nil {
108108
return err
109109
}
@@ -133,14 +133,18 @@ func (t *TaskManager) executeKillQueryStatement(stmt *influxql.KillQueryStatemen
133133
return t.KillQuery(stmt.QueryID)
134134
}
135135

136-
func (t *TaskManager) executeShowQueriesStatement(q *influxql.ShowQueriesStatement) (models.Rows, error) {
136+
func (t *TaskManager) executeShowQueriesStatement(q *influxql.ShowQueriesStatement, authorizer CoarseAuthorizer) (models.Rows, error) {
137137
t.mu.RLock()
138138
defer t.mu.RUnlock()
139139

140140
now := time.Now()
141141

142142
values := make([][]interface{}, 0, len(t.queries))
143143
for id, qi := range t.queries {
144+
if authorizer != nil && qi.database != "" && !authorizer.AuthorizeDatabase(influxql.ReadPrivilege, qi.database) {
145+
continue
146+
}
147+
144148
d := now.Sub(qi.startTime)
145149

146150
d = prettyTime(d)

0 commit comments

Comments
 (0)