Skip to content

sql: gate SHOW SCHEDULES behind VIEWJOB and add VIEWEVENTLOG privilege#169679

Open
souravcrl wants to merge 2 commits into
cockroachdb:masterfrom
souravcrl:add-viewschedule-privilege
Open

sql: gate SHOW SCHEDULES behind VIEWJOB and add VIEWEVENTLOG privilege#169679
souravcrl wants to merge 2 commits into
cockroachdb:masterfrom
souravcrl:add-viewschedule-privilege

Conversation

@souravcrl
Copy link
Copy Markdown
Contributor

@souravcrl souravcrl commented May 4, 2026

Summary

  • Gates SHOW SCHEDULES behind the existing VIEWJOB system privilege,
    which also grants implicit SELECT on system.scheduled_jobs and
    system.jobs so the delegated query executes.
  • Adds a new VIEWEVENTLOG (Kind 45) system privilege, granting
    read-only access to system.eventlog and the DB Console Events page.
    The gRPC Events API now accepts either VIEWEVENTLOG or the existing
    VIEWCLUSTERMETADATA.
  • Fixes the DB Console schedules API to check for SQL execution errors
    before accessing results, preventing runtime crashes on privilege errors.

Previously, SHOW SCHEDULES was implicitly gated by SELECT on
system.scheduled_jobs (admin-only), and viewing the event log required
admin or VIEWCLUSTERMETADATA — both overprivileged for users who
only need schedule or event visibility. Non-admin users can now be
granted scoped access:

GRANT SYSTEM VIEWJOB TO <user>;
GRANT SYSTEM VIEWEVENTLOG TO <user>;

Fixes: #169420
Fixes: #169421
Fixes: #103341
Epic: none

Release note (sql change): SHOW SCHEDULES now requires the VIEWJOB
system privilege, and a new VIEWEVENTLOG system privilege grants
read-only access to the event log. Non-admin users can be granted scoped
visibility via GRANT SYSTEM VIEWJOB TO <user> and
GRANT SYSTEM VIEWEVENTLOG TO <user> without needing broad system
table access or the admin role.

DB Console Demo

Demo

Individual Screenshots

Schedules — without VIEWJOB (error)

Schedules error

Events — without VIEWEVENTLOG (error)

Events error

Schedules — with VIEWJOB (working)

Schedules working

Events — with VIEWEVENTLOG (working)

Events working

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@souravcrl souravcrl requested review from a team as code owners May 4, 2026 17:29
@souravcrl souravcrl requested review from kyle-a-wong and removed request for a team May 4, 2026 17:29
@trunk-io
Copy link
Copy Markdown
Contributor

trunk-io Bot commented May 4, 2026

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@cockroach-teamcity
Copy link
Copy Markdown
Member

This change is Reviewable

@blathers-crl
Copy link
Copy Markdown

blathers-crl Bot commented May 4, 2026

Detected infrastructure failure (matched: self-hosted runner lost communication with the server). Automatically rerunning failed jobs. (run link)

@souravcrl souravcrl changed the title sql: add VIEWSCHEDULE system privilege for SHOW SCHEDULES sql: add VIEWSCHEDULE and VIEWEVENTLOG system privileges May 5, 2026
Comment thread pkg/sql/delegate/show_schedules.go Outdated
Comment on lines +28 to +33
if err := d.catalog.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject,
d.catalog.GetCurrentUser(), privilege.VIEWSCHEDULE,
); err != nil {
return nil, err
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: This upfront guard exclusively requires VIEWSCHEDULE on GlobalPrivilegeObject (a synthetic privilege object), so the VIEWSYSTEMTABLE fallback in authorization.go:264-273 — which only applies to system table descriptors — is never reached. Any non-admin user who currently accesses SHOW SCHEDULES via VIEWSYSTEMTABLE or direct SELECT grants on system.scheduled_jobs/system.jobs will get "does not have VIEWSCHEDULE system privilege" after this change, even though they have sufficient access to the underlying data.

The simplest fix is to remove this upfront guard entirely: the delegated query (SELECT ... FROM system.scheduled_jobs) will naturally hit the table-level privilege checks in authorization.go, which already correctly handle VIEWSYSTEMTABLE, VIEWSCHEDULE, and direct SELECT grants. Alternatively, expand this check to also accept VIEWSYSTEMTABLE.

Note that the analogous SHOW JOBS command has no such upfront gate, confirming this is inconsistent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this comment. do we actually need the check here if there already is one in authorization.go?

We should be able to confirm this via a logictest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the VIEWSYSTEMTABLE regression. I tested removing the upfront guard entirely and the behavior is:

  • Without the guard, the error is "user testuser does not have SELECT privilege on relation scheduled_jobs" — which doesn't tell the user what privilege they need.
  • With the guard, we get a clear "does not have VIEWSCHEDULE system privilege".

So the guard serves a UX purpose (clear error message), but as written it blocks VIEWSYSTEMTABLE users which is a regression. I've updated the guard to also check VIEWSYSTEMTABLE — if the user has neither VIEWSCHEDULE nor VIEWSYSTEMTABLE, they get the clear error. If they have VIEWSYSTEMTABLE, the guard passes and the query executes with the implicit SELECT from authorization.go.

I've also added a logictest case confirming that VIEWSYSTEMTABLE still works for SHOW SCHEDULES.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

AI Review: Potential Issue(s) Detected

An inline comment has been added to pkg/sql/delegate/show_schedules.go (lines 28-33) identifying a privilege regression.

Summary: The upfront VIEWSCHEDULE guard blocks users who previously accessed SHOW SCHEDULES via VIEWSYSTEMTABLE or direct SELECT grants on the underlying system tables. Since the check targets GlobalPrivilegeObject (a synthetic object), the VIEWSYSTEMTABLE fallback in authorization.go is never reached. This silently revokes access for non-admin users relying on existing privilege paths.

View full analysis


If helpful: add O-AI-Review-Real-Issue-Found label.
If not helpful: add O-AI-Review-Not-Helpful label.

@github-actions github-actions Bot added the o-AI-Review-Potential-Issue-Detected AI reviewer found potential issue. Never assign manually—auto-applied by GH action only. label May 5, 2026
@souravcrl
Copy link
Copy Markdown
Contributor Author

DB Console Manual Test Results

Tested the Schedules and Events pages with a non-admin user (testnopriv).

Test Flow (recorded via Playwright):

Step Page Privilege Result
1 Schedules (/#/schedules) None Error: "user testnopriv does not have VIEWSCHEDULE system privilege"
2 Events (/#/events) None Error: "user testnopriv does not have SELECT privilege on relation eventlog"
3 Schedules GRANT SYSTEM VIEWSCHEDULE Shows 2 schedules (sql-schema-telemetry, sql-stats-compaction)
4 Events GRANT SYSTEM VIEWEVENTLOG Shows event log entries
5 Schedules REVOKE SYSTEM VIEWSCHEDULE Error returns
6 Events REVOKE SYSTEM VIEWEVENTLOG Error returns

All pages display inline error alerts when privilege is missing, and render data correctly when privilege is granted.

@souravcrl souravcrl requested a review from a team May 5, 2026 07:38
Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! I reviewed the privilege-related changes, and it looks good, with just a minor nit.

For changes in the frontend and RPC endpoints, please ask the o11y team.

This also seems to resolve #103341 -- i will add that issue linkage to this PR.

@rafiss made 3 comments.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on kyle-a-wong and souravcrl).


pkg/server/admin.go line 1494 at r2 (raw file):

			// Fall back to the existing VIEWCLUSTERMETADATA check.
			if err := s.privilegeChecker.RequireViewClusterMetadataPermission(ctx); err != nil {
				return nil, err

When a non-admin lacks both privileges, the user only sees "requires the VIEWCLUSTERMETADATA system privilege" — they're never told VIEWEVENTLOG would also work.

consider using errors.WithMessage so we can mention that information. another alternative would be to modify the privilegeChecker interface so both privileges can be checked with the same call.

Comment thread pkg/sql/delegate/show_schedules.go Outdated
Comment on lines +28 to +33
if err := d.catalog.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject,
d.catalog.GetCurrentUser(), privilege.VIEWSCHEDULE,
); err != nil {
return nil, err
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this comment. do we actually need the check here if there already is one in authorization.go?

We should be able to confirm this via a logictest.

@souravcrl souravcrl force-pushed the add-viewschedule-privilege branch 2 times, most recently from 8b57f1a to 7057256 Compare May 6, 2026 09:42
@souravcrl souravcrl requested a review from rafiss May 6, 2026 10:12
Copy link
Copy Markdown
Contributor Author

@souravcrl souravcrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@souravcrl made 1 comment.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on kyle-a-wong and rafiss).


pkg/server/admin.go line 1494 at r2 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

When a non-admin lacks both privileges, the user only sees "requires the VIEWCLUSTERMETADATA system privilege" — they're never told VIEWEVENTLOG would also work.

consider using errors.WithMessage so we can mention that information. another alternative would be to modify the privilegeChecker interface so both privileges can be checked with the same call.

done

Copy link
Copy Markdown
Member

@jasonlmfong jasonlmfong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this would also benefit from some privilege checker test like
pkg/server/privchecker/privchecker_test.go

Comment thread pkg/server/admin.go Outdated
ctx = s.AnnotateCtx(ctx)

err := s.privilegeChecker.RequireViewClusterMetadataPermission(ctx)
// Accept either VIEWEVENTLOG or VIEWCLUSTERMETADATA.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth refactoring this out into a function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have used the RequireViewEventLogPermission function here which checks both VIEWEVENTLOG and VIEWCLUSTERMETADATA internally

@souravcrl souravcrl force-pushed the add-viewschedule-privilege branch 2 times, most recently from fb125fe to 268ac92 Compare May 7, 2026 11:26
@souravcrl
Copy link
Copy Markdown
Contributor Author

i think this would also benefit from some privilege checker test like pkg/server/privchecker/privchecker_test.go

Have added the tests for VIEWSCHEDULE and VIEWEVENTLOG to privchecker_test.go PTAL

@souravcrl souravcrl requested a review from jasonlmfong May 7, 2026 11:38
Comment thread pkg/sql/delegate/show_schedules.go Outdated
Comment on lines +35 to +45
) == nil
if !hasViewSchedule {
hasViewSystemTable := cat.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject, user, privilege.VIEWSYSTEMTABLE,
) == nil
if !hasViewSystemTable {
return nil, pgerror.Newf(pgcode.InsufficientPrivilege,
"user %s does not have %s system privilege",
user, privilege.VIEWSCHEDULE)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Both CheckPrivilege calls use == nil to test the result, which swallows all errors — not just InsufficientPrivilege. If CheckPrivilege fails due to a transient error (KV unavailability, context cancellation, descriptor resolution failure, etc.), the code will misinterpret it as "user lacks the privilege" and return a misleading InsufficientPrivilege error to an authorized user.

The established pattern in this package (see show_all_cluster_settings.go:28-34, show_default_session_variables_for_role.go:31-35) correctly distinguishes permission errors from operational errors. Consider:

Suggested change
) == nil
if !hasViewSchedule {
hasViewSystemTable := cat.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject, user, privilege.VIEWSYSTEMTABLE,
) == nil
if !hasViewSystemTable {
return nil, pgerror.Newf(pgcode.InsufficientPrivilege,
"user %s does not have %s system privilege",
user, privilege.VIEWSCHEDULE)
}
}
hasViewSchedule := false
if err := cat.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject, user, privilege.VIEWSCHEDULE,
); err == nil {
hasViewSchedule = true
} else if pgerror.GetPGCode(err) != pgcode.InsufficientPrivilege {
return nil, err
}
if !hasViewSchedule {
if err := cat.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject, user, privilege.VIEWSYSTEMTABLE,
); err == nil {
// OK — VIEWSYSTEMTABLE grants access too.
} else if pgerror.GetPGCode(err) != pgcode.InsufficientPrivilege {
return nil, err
} else {
return nil, pgerror.Newf(pgcode.InsufficientPrivilege,
"user %s does not have %s system privilege",
user, privilege.VIEWSCHEDULE)
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is valid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — addressed in dc4e9c5. Also replaced VIEWSCHEDULE with VIEWJOB (per updated requirements) and properly distinguished InsufficientPrivilege from transient errors, matching the pattern in show_all_cluster_settings.go and show_default_session_variables_for_role.go.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

AI Review: Potential Issue Detected

Inline comment has been added to pkg/sql/delegate/show_schedules.go (lines 35-45) where both CheckPrivilege() calls swallow all errors by comparing == nil, treating any error (including KV unavailability, context cancellation, descriptor resolution failures) as "user lacks privilege." This violates the established pattern used elsewhere in the same package (show_all_cluster_settings.go, show_default_session_variables_for_role.go) which check pgerror.GetPGCode(err) != pgcode.InsufficientPrivilege and propagate non-permission errors.

View full analysis


If helpful: add O-AI-Review-Real-Issue-Found label.
If not helpful: add O-AI-Review-Not-Helpful label.

Copy link
Copy Markdown
Member

@jasonlmfong jasonlmfong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looks good, just have a nit on a doc
also look into fixing the ai review callout

Comment thread pkg/server/privchecker/privchecker.go Outdated

// RequireViewEventLogPermission requires the user have admin or the
// VIEWEVENTLOG or VIEWCLUSTERMETADATA system privilege and returns an
// error if the user does not have it.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the use of "it" here is confusing
maybe something like error if the user does not have any or error otherwise

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — changed to error if the user does not have any in dc4e9c5.

Comment thread pkg/sql/delegate/show_schedules.go Outdated
Comment on lines +35 to +45
) == nil
if !hasViewSchedule {
hasViewSystemTable := cat.CheckPrivilege(
d.ctx, syntheticprivilege.GlobalPrivilegeObject, user, privilege.VIEWSYSTEMTABLE,
) == nil
if !hasViewSystemTable {
return nil, pgerror.Newf(pgcode.InsufficientPrivilege,
"user %s does not have %s system privilege",
user, privilege.VIEWSCHEDULE)
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is valid

@souravcrl souravcrl force-pushed the add-viewschedule-privilege branch from dc4e9c5 to 81a7c3b Compare May 8, 2026 10:36
@blathers-crl
Copy link
Copy Markdown

blathers-crl Bot commented May 8, 2026

Detected infrastructure failure (matched: self-hosted runner lost communication with the server). Automatically rerunning failed jobs. (run link)

@rafiss rafiss changed the title sql: add VIEWSCHEDULE and VIEWEVENTLOG system privileges sql: gate SHOW SCHEDULES behind VIEWJOB and add VIEWEVENTLOG privilege May 13, 2026
Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. consider asking a DR engineer to review the changes to SHOW SCHEDULES privilege requirements.

i have linked #103341 since this PR will resolve that issue.

@souravcrl
Copy link
Copy Markdown
Contributor Author

lgtm. consider asking a DR engineer to review the changes to SHOW SCHEDULES privilege requirements.

i have linked #103341 since this PR will resolve that issue.

@msbutler can you please take a look? I have updated the PR to use the VIEWJOB privilege for the SHOW schedules view and also updated the commit and PR message for it as discussed with @biplav-crl.

@msbutler msbutler self-requested a review May 13, 2026 16:26
Copy link
Copy Markdown
Collaborator

@msbutler msbutler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice test! one cleanup suggestion

Comment thread pkg/sql/delegate/show_schedules.go Outdated
user := cat.GetCurrentUser()
globalPrivObj := syntheticprivilege.GlobalPrivilegeObject
hasViewJob := false
if err := cat.CheckPrivilege(d.ctx, globalPrivObj, user, privilege.VIEWJOB); err == nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this error branching makes me a little nervous-- i had to convince myself that if err != nil, then we always return an error.

could you make this cleaner by calling a helper on the error path? i.e.

if err := cat.CheckPrivilege(d.ctx, globalPrivObj, user, privilege.VIEWJOB); err != nil {
   return processPrivError(err)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — extracted a processPrivError helper that returns nil for InsufficientPrivilege and propagates unexpected errors. The flow is now: check VIEWJOB → if missing, check VIEWSYSTEMTABLE → if both missing, return privilege error.

souravcrl and others added 2 commits May 27, 2026 15:23
Previously, `SHOW SCHEDULES` was implicitly gated by `SELECT` on
`system.scheduled_jobs` and `system.jobs`, which only `admin` has by
default. This made it impossible to grant schedule visibility to
non-admin users without granting broad access to system tables.

This change gates `SHOW SCHEDULES` behind the existing `VIEWJOB`
system privilege (which already controls job visibility and is a
natural fit for schedule visibility). Admin users satisfy this check
implicitly through `ALL` privileges. The privilege also grants implicit
`SELECT` on `system.scheduled_jobs` and `system.jobs` so the delegated
query executes successfully.

The delegator performs an upfront privilege check (rather than relying
solely on the authorization.go fallback) because without it the user
would see a confusing "does not have SELECT privilege on relation
scheduled_jobs" error instead of a clear message about the VIEWJOB
privilege. The check properly distinguishes InsufficientPrivilege from
transient errors to avoid swallowing operational failures.

On the DB Console side, the schedules API now checks for SQL execution
errors before accessing results, preventing runtime crashes when the
user lacks the required privilege.

Fixes: cockroachdb#169420
Epic: none
Release note (sql change): `SHOW SCHEDULES` now requires the `VIEWJOB`
system privilege. Non-admin users can be granted schedule visibility via
`GRANT SYSTEM VIEWJOB TO <user>` without needing direct `SELECT` on
system tables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: cockroachdb#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>
@souravcrl souravcrl force-pushed the add-viewschedule-privilege branch from 81a7c3b to 3fa8211 Compare May 27, 2026 09:54
@souravcrl souravcrl requested a review from a team as a code owner May 27, 2026 09:54
@souravcrl souravcrl requested review from spilchen and removed request for a team May 27, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

o-AI-Review-Potential-Issue-Detected AI reviewer found potential issue. Never assign manually—auto-applied by GH action only.

Projects

None yet

5 participants