Skip to content

Commit 3fa8211

Browse files
souravcrlclaude
andcommitted
sql: add VIEWEVENTLOG system privilege for event log visibility
Previously, viewing the event log required `admin` or `VIEWCLUSTERMETADATA`, both of which are overprivileged for users who only need to read cluster events (e.g. SREs and auditors correlating events during incidents). This change introduces a new `VIEWEVENTLOG` system privilege that grants read-only access to `system.eventlog`. The gRPC Events API now accepts either `VIEWEVENTLOG` or the existing `VIEWCLUSTERMETADATA` privilege, and the error message mentions both options. The privilege also grants implicit `SELECT` on `system.eventlog` so the DB Console SQL-based events page works correctly. Non-admin users can now be granted event log visibility via: ```sql GRANT SYSTEM VIEWEVENTLOG TO <user>; ``` Fixes: #169421 Epic: none Release note (sql change): Added a new `VIEWEVENTLOG` system privilege that grants read-only access to the event log. Non-admin users can be granted event log visibility via `GRANT SYSTEM VIEWEVENTLOG TO <user>` without needing `VIEWCLUSTERMETADATA` or the `admin` role. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 27686a4 commit 3fa8211

9 files changed

Lines changed: 132 additions & 9 deletions

File tree

pkg/server/admin.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,23 +1478,23 @@ func (s *adminServer) Events(
14781478
) (_ *serverpb.EventsResponse, retErr error) {
14791479
ctx = s.AnnotateCtx(ctx)
14801480

1481-
err := s.privilegeChecker.RequireViewClusterMetadataPermission(ctx)
1482-
if err != nil {
1481+
if err := s.privilegeChecker.RequireViewEventLogPermission(ctx); err != nil {
14831482
// NB: not using srverrors.ServerError() here since the priv checker
14841483
// already returns a proper gRPC error status.
14851484
return nil, err
14861485
}
14871486
redactEvents := !req.UnredactedEvents
14881487

1488+
userName, err := authserver.UserFromIncomingRPCContext(ctx)
1489+
if err != nil {
1490+
return nil, srverrors.ServerError(ctx, err)
1491+
}
1492+
14891493
limit := req.Limit
14901494
if limit == 0 {
14911495
limit = apiconstants.DefaultAPIEventLimit
14921496
}
14931497

1494-
userName, err := authserver.UserFromIncomingRPCContext(ctx)
1495-
if err != nil {
1496-
return nil, srverrors.ServerError(ctx, err)
1497-
}
14981498
r, err := s.eventsHelper(ctx, req, userName, int(limit), 0, redactEvents)
14991499
if err != nil {
15001500
return nil, srverrors.ServerError(ctx, err)

pkg/server/privchecker/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type CheckerForRPCHandlers interface {
4444
RequireViewClusterMetadataPermission(ctx context.Context) error
4545
RequireRepairClusterPermission(ctx context.Context) error
4646
RequireViewDebugPermission(ctx context.Context) error
47+
RequireViewEventLogPermission(ctx context.Context) error
4748
}
4849

4950
// SQLPrivilegeChecker is the part of the privilege checker that can

pkg/server/privchecker/privchecker.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,33 @@ func (c *adminPrivilegeChecker) RequireViewDebugPermission(ctx context.Context)
190190
privilege.VIEWDEBUG.DisplayName())
191191
}
192192

193+
// RequireViewEventLogPermission requires the user have admin or the
194+
// VIEWEVENTLOG or VIEWCLUSTERMETADATA system privilege and returns an
195+
// error if the user does not have any.
196+
func (c *adminPrivilegeChecker) RequireViewEventLogPermission(ctx context.Context) (err error) {
197+
userName, isAdmin, err := c.GetUserAndRole(ctx)
198+
if err != nil {
199+
return srverrors.ServerError(ctx, err)
200+
}
201+
if isAdmin {
202+
return nil
203+
}
204+
if hasViewEventLog, err := c.HasGlobalPrivilege(ctx, userName, privilege.VIEWEVENTLOG); err != nil {
205+
return srverrors.ServerError(ctx, err)
206+
} else if hasViewEventLog {
207+
return nil
208+
}
209+
if hasViewClusterMetadata, err := c.HasGlobalPrivilege(ctx, userName, privilege.VIEWCLUSTERMETADATA); err != nil {
210+
return srverrors.ServerError(ctx, err)
211+
} else if hasViewClusterMetadata {
212+
return nil
213+
}
214+
return grpcstatus.Errorf(
215+
codes.PermissionDenied, "this operation requires the %s or %s system privilege",
216+
privilege.VIEWEVENTLOG.DisplayName(),
217+
privilege.VIEWCLUSTERMETADATA.DisplayName())
218+
}
219+
193220
// GetUserAndRole is part of the CheckerForRPCHandlers interface.
194221
func (c *adminPrivilegeChecker) GetUserAndRole(
195222
ctx context.Context,

pkg/server/privchecker/privchecker_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func TestAdminPrivilegeChecker(t *testing.T) {
5959
sqlDB.Exec(t, "GRANT SYSTEM VIEWDEBUG TO withviewdebug")
6060
sqlDB.Exec(t, "CREATE USER withviewjob")
6161
sqlDB.Exec(t, "GRANT SYSTEM VIEWJOB TO withviewjob")
62+
sqlDB.Exec(t, "CREATE USER withvieweventlog")
63+
sqlDB.Exec(t, "GRANT SYSTEM VIEWEVENTLOG TO withvieweventlog")
6264

6365
execCfg := ts.ExecutorConfig().(sql.ExecutorConfig)
6466
kvDB := ts.DB()
@@ -101,8 +103,9 @@ func TestAdminPrivilegeChecker(t *testing.T) {
101103
withviewclustermetadata := username.MakeSQLUsernameFromPreNormalizedString("withviewclustermetadata")
102104
withViewDebug := username.MakeSQLUsernameFromPreNormalizedString("withviewdebug")
103105
withViewJob := username.MakeSQLUsernameFromPreNormalizedString("withviewjob")
106+
withViewEventLog := username.MakeSQLUsernameFromPreNormalizedString("withvieweventlog")
104107

105-
// Test HasGlobalPrivilege for VIEWJOB.
108+
// Test HasGlobalPrivilege for VIEWJOB and VIEWEVENTLOG.
106109
globalPrivTests := []struct {
107110
name string
108111
privilege privilege.Kind
@@ -111,6 +114,8 @@ func TestAdminPrivilegeChecker(t *testing.T) {
111114
}{
112115
{"viewjob-granted", privilege.VIEWJOB, withViewJob, true},
113116
{"viewjob-not-granted", privilege.VIEWJOB, withoutPrivs, false},
117+
{"vieweventlog-granted", privilege.VIEWEVENTLOG, withViewEventLog, true},
118+
{"vieweventlog-not-granted", privilege.VIEWEVENTLOG, withoutPrivs, false},
114119
}
115120
for _, tt := range globalPrivTests {
116121
t.Run(tt.name, func(t *testing.T) {
@@ -163,6 +168,13 @@ func TestAdminPrivilegeChecker(t *testing.T) {
163168
withAdmin: false, withoutPrivs: true, withViewDebug: false,
164169
},
165170
},
171+
{
172+
"requireViewEventLogPermission",
173+
underTest.RequireViewEventLogPermission,
174+
map[username.SQLUsername]bool{
175+
withAdmin: false, withoutPrivs: true, withViewEventLog: false, withviewclustermetadata: false,
176+
},
177+
},
166178
}
167179

168180
for _, tt := range tests {

pkg/sql/authorization.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,18 @@ func (p *planner) CheckPrivilegeForUser(
286286
return nil
287287
}
288288
}
289+
// VIEWEVENTLOG grants implicit SELECT on system.eventlog.
290+
if tableID == keys.EventLogTableID {
291+
hasViewEventLog, err := p.HasPrivilege(
292+
ctx, syntheticprivilege.GlobalPrivilegeObject, privilege.VIEWEVENTLOG, user,
293+
)
294+
if err != nil {
295+
return err
296+
}
297+
if hasViewEventLog {
298+
return nil
299+
}
300+
}
289301
}
290302
}
291303
return insufficientPrivilegeError(user, privilegeKind, privilegeObject)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# LogicTest: local
2+
3+
# Verify that the VIEWEVENTLOG privilege grants access to system.eventlog.
4+
5+
# testuser should not be able to read system.eventlog.
6+
user testuser
7+
8+
statement error user testuser does not have SELECT privilege on relation eventlog
9+
SELECT * FROM system.eventlog LIMIT 0
10+
11+
user root
12+
13+
# Grant VIEWEVENTLOG to testuser.
14+
statement ok
15+
GRANT SYSTEM VIEWEVENTLOG TO testuser
16+
17+
user testuser
18+
19+
# testuser should now be able to read system.eventlog.
20+
statement ok
21+
SELECT * FROM system.eventlog LIMIT 0
22+
23+
user root
24+
25+
# Revoke VIEWEVENTLOG from testuser.
26+
statement ok
27+
REVOKE SYSTEM VIEWEVENTLOG FROM testuser
28+
29+
user testuser
30+
31+
# testuser should no longer be able to read system.eventlog.
32+
statement error user testuser does not have SELECT privilege on relation eventlog
33+
SELECT * FROM system.eventlog LIMIT 0
34+
35+
user root
36+
37+
# Test that inheriting VIEWEVENTLOG through a role works.
38+
statement ok
39+
CREATE ROLE eventlogviewer
40+
41+
statement ok
42+
GRANT SYSTEM VIEWEVENTLOG TO eventlogviewer
43+
44+
statement ok
45+
GRANT eventlogviewer TO testuser
46+
47+
user testuser
48+
49+
# testuser should be able to read system.eventlog through role membership.
50+
statement ok
51+
SELECT * FROM system.eventlog LIMIT 0
52+
53+
user root
54+
55+
statement ok
56+
REVOKE eventlogviewer FROM testuser
57+
58+
user testuser
59+
60+
# testuser should no longer be able to read system.eventlog.
61+
statement error user testuser does not have SELECT privilege on relation eventlog
62+
SELECT * FROM system.eventlog LIMIT 0

pkg/sql/logictest/tests/local/generated_test.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/privilege/kind.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ const (
7373
BUILTIN_UNSAFE_ALLOWED Kind = 42
7474
MAINTAIN Kind = 43
7575
TEMPORARY Kind = 44
76-
largestKind = TEMPORARY
76+
VIEWEVENTLOG Kind = 45
77+
largestKind = VIEWEVENTLOG
7778

7879
// RULE, SET, and ALTERSYSTEM are PostgreSQL ACL-only pseudo-privileges.
7980
// They exist solely for ACL character mapping used by acldefault, aclexplode,
@@ -187,6 +188,8 @@ func (k Kind) InternalKey() KindInternalKey {
187188
return "MAINTAIN"
188189
case TEMPORARY:
189190
return "TEMPORARY"
191+
case VIEWEVENTLOG:
192+
return "VIEWEVENTLOG"
190193
case SET:
191194
return "SET"
192195
case ALTERSYSTEM:

pkg/sql/privilege/privilege.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ var (
101101
ALL, BACKUP, RESTORE, MODIFYCLUSTERSETTING, EXTERNALCONNECTION, VIEWACTIVITY, VIEWACTIVITYREDACTED,
102102
VIEWCLUSTERSETTING, CANCELQUERY, NOSQLLOGIN, VIEWCLUSTERMETADATA, VIEWDEBUG, EXTERNALIOIMPLICITACCESS, VIEWJOB,
103103
MODIFYSQLCLUSTERSETTING, REPLICATION, MANAGEVIRTUALCLUSTER, VIEWSYSTEMTABLE, CREATEROLE, CREATELOGIN, CREATEDB, CONTROLJOB,
104-
REPAIRCLUSTER, BYPASSRLS, REPLICATIONDEST, REPLICATIONSOURCE, INSPECT,
104+
REPAIRCLUSTER, BYPASSRLS, REPLICATIONDEST, REPLICATIONSOURCE, INSPECT, VIEWEVENTLOG,
105105
}
106106
VirtualTablePrivileges = List{ALL, SELECT}
107107
ExternalConnectionPrivileges = List{ALL, USAGE, DROP, UPDATE}

0 commit comments

Comments
 (0)