@@ -34,6 +34,7 @@ import (
3434 "github.com/cockroachdb/cockroach/pkg/util/log"
3535 "github.com/cockroachdb/errors"
3636 "github.com/stretchr/testify/require"
37+ "golang.org/x/sync/errgroup"
3738)
3839
3940func TestListSessionsSecurity (t * testing.T ) {
@@ -104,6 +105,128 @@ func TestListSessionsSecurity(t *testing.T) {
104105 }
105106}
106107
108+ // TestListSessionsPrivileges tests that the VIEWACTIVITY and VIEWACTIVITYREDACTED privileges
109+ // are respected when listing sessions, particularly for other users' sessions.
110+ func TestListSessionsPrivileges (t * testing.T ) {
111+ defer leaktest .AfterTest (t )()
112+ defer log .Scope (t ).Close (t )
113+
114+ // Skip under stress race as the sleep query might finish before the stress race can finish.
115+ skip .UnderStressRace (t , "list sessions privileges" )
116+
117+ ts , sqlDB , _ := serverutils .StartServer (t , base.TestServerArgs {})
118+ defer ts .Stopper ().Stop (context .Background ())
119+ endpoint := "sessions"
120+ appName := "test_sessions_privileges"
121+ user := apiconstants .TestingUserNameNoAdmin ().Normalized ()
122+ sleepQuery := "SELECT pg_sleep(3000)"
123+ sleepQueryRedacted := "SELECT pg_sleep(_)"
124+
125+ serverSQL := sqlutils .MakeSQLRunner (sqlDB )
126+ serverSQL .Exec (t , fmt .Sprintf (`SET application_name = "%s"` , appName ))
127+ queryCtx , cancel := context .WithCancel (context .Background ())
128+
129+ // Run a sleep query as root in another goroutine to make sure that root's session has one
130+ // active query while we list sessions. This sleep query will be cancelled at the end of the
131+ // test.
132+ var g errgroup.Group
133+ g .Go (func () error {
134+ _ , err := serverSQL .DB .ExecContext (queryCtx , sleepQuery )
135+ if strings .Contains (err .Error (), "canceled" ) && strings .Contains (queryCtx .Err ().Error (), "canceled" ) {
136+ // Both errors contain the "canceled" substring, this means the query was
137+ // canceled as expected.
138+ return nil
139+ }
140+ t .Errorf ("unexpected error: %v" , err )
141+ return err
142+ })
143+
144+ // We test all combinations of VIEWACTIVITY and VIEWACTIVITYREDACTED. We could also start
145+ // by granting all privileges and then revoking them one by one, but we want to keep the
146+ // tests as isolated as possible. If a non-admin user has neither privilege, they should
147+ // not see root's session. If a non-admin user has VIEWACTIVITY, they should see root's
148+ // session with the full query. If a non-admin user has VIEWACTIVITYREDACTED, they should
149+ // see root's session with the redacted query. If a non-admin user has both privileges,
150+ // VIEWACTIVITYREDACTED should take precedence.
151+ testCases := []struct {
152+ grantViewActivity bool
153+ grantViewActivityRedacted bool
154+ expectedQuery string
155+ }{
156+ {false , false , "" },
157+ {false , true , sleepQueryRedacted },
158+ {true , false , sleepQuery },
159+ {true , true , sleepQueryRedacted },
160+ }
161+
162+ // Filters sessions by appName.
163+ filterSessions := func (sessions []serverpb.Session ) []serverpb.Session {
164+ var filteredSessions []serverpb.Session
165+ for _ , s := range sessions {
166+ if s .ApplicationName == appName {
167+ filteredSessions = append (filteredSessions , s )
168+ }
169+ }
170+ return filteredSessions
171+ }
172+
173+ for _ , tc := range testCases {
174+ if tc .grantViewActivity {
175+ serverSQL .Exec (t , fmt .Sprintf (`GRANT SYSTEM VIEWACTIVITY TO %s` , user ))
176+ }
177+ if tc .grantViewActivityRedacted {
178+ serverSQL .Exec (t , fmt .Sprintf (`GRANT SYSTEM VIEWACTIVITYREDACTED TO %s` , user ))
179+ }
180+
181+ var response serverpb.ListSessionsResponse
182+ err := srvtestutils .GetStatusJSONProtoWithAdminOption (ts , endpoint , & response , false )
183+
184+ if err != nil {
185+ t .Errorf ("unexpected failure listing sessions from %s; error: %v; response errors: %v" ,
186+ endpoint , err , response .Errors )
187+ }
188+
189+ filteredSessions := filterSessions (response .Sessions )
190+ numberOfSessions := len (filteredSessions )
191+
192+ // A non-admin user with no privileges should not see any other users' sessions.
193+ if ! tc .grantViewActivity && ! tc .grantViewActivityRedacted {
194+ if numberOfSessions != 0 {
195+ t .Errorf ("expected 0 sessions, but got %d" , numberOfSessions )
196+ }
197+ continue
198+ }
199+
200+ // A non-admin user with at least one of the privileges should see other users' sessions.
201+ if numberOfSessions != 1 {
202+ t .Errorf ("expected 1 session, but got %d" , numberOfSessions )
203+ } else {
204+ session := filteredSessions [0 ]
205+ numberOfActiveQueries := len (session .ActiveQueries )
206+ if numberOfActiveQueries != 1 {
207+ t .Errorf ("expected 1 active query, but got %d" , numberOfActiveQueries )
208+ } else {
209+ activeQuery := session .ActiveQueries [0 ].Sql
210+ if activeQuery != tc .expectedQuery {
211+ t .Errorf ("expected active query to be %s, but got %s" , tc .expectedQuery , activeQuery )
212+ }
213+ }
214+ }
215+
216+ // Only revoke the privilege if we granted it in this test case.
217+ if tc .grantViewActivity {
218+ serverSQL .Exec (t , fmt .Sprintf (`REVOKE SYSTEM VIEWACTIVITY FROM %s` , user ))
219+ }
220+ if tc .grantViewActivityRedacted {
221+ serverSQL .Exec (t , fmt .Sprintf (`REVOKE SYSTEM VIEWACTIVITYREDACTED FROM %s` , user ))
222+ }
223+ }
224+
225+ // Cancel the query so that the test can finish.
226+ cancel ()
227+ _ = g .Wait ()
228+ }
229+
107230func TestStatusCancelSessionGatewayMetadataPropagation (t * testing.T ) {
108231 defer leaktest .AfterTest (t )()
109232 defer log .Scope (t ).Close (t )
0 commit comments