Skip to content

Commit 8b90335

Browse files
craig[bot]spilchenyuzefovich
committed
155110: sql: implement row_security session variable r=spilchen a=spilchen Complete the implementation of the `row_security` session variable, which was previously defined but non-functional. When set to `'off'`, non-admin queries that would be affected by row-level security (RLS) fail with an error instead of silently filtering rows. This matches postgres' behavior. Admin users and table owners (without FORCE) remain exempt from RLS regardless of this setting. The variable defaults to `'on'` and is tracked in the memo to invalidate cached plans when changed. Closes #138916. Epic: none Release note (sql change): The `row_security` session variable now behaves like in postgres, allowing users to detect when RLS is applied. 155132: roachtest/sqlsmith: only skip tpch and tpcc setups r=yuzefovich a=yuzefovich In 1d29ae8 by mistake we skipped all sqlsmith setups even though only tpch and tpcc ones are affected. Informs: #153489. Release note: None Co-authored-by: Matt Spilchen <[email protected]> Co-authored-by: Yahor Yuzefovich <[email protected]>
3 parents efc9eff + 7e43381 + 5ac76e0 commit 8b90335

File tree

11 files changed

+254
-16
lines changed

11 files changed

+254
-16
lines changed

pkg/cmd/roachtest/tests/sqlsmith.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ import (
2929

3030
func registerSQLSmith(r registry.Registry) {
3131
const numNodes = 4
32+
const tpchName = "tpch-sf1"
33+
const tpccName = "tpcc"
3234
setups := map[string]sqlsmith.Setup{
3335
"empty": sqlsmith.Setups["empty"],
3436
"seed": sqlsmith.Setups["seed"],
3537
sqlsmith.RandTableSetupName: sqlsmith.Setups[sqlsmith.RandTableSetupName],
36-
"tpch-sf1": func(r *rand.Rand) []string {
38+
tpchName: func(r *rand.Rand) []string {
3739
return []string{`
3840
RESTORE TABLE tpch.* FROM '/' IN 'gs://cockroach-fixtures-us-east1/workload/tpch/scalefactor=1/backup?AUTH=implicit'
3941
WITH into_db = 'defaultdb', unsafe_restore_incompatible_version;
4042
`}
4143
},
42-
"tpcc": func(r *rand.Rand) []string {
44+
tpccName: func(r *rand.Rand) []string {
4345
const version = "version=2.1.0,fks=true,interleaved=false,seed=1,warehouses=1"
4446
var stmts []string
4547
for _, t := range []string{
@@ -310,6 +312,11 @@ WITH into_db = 'defaultdb', unsafe_restore_incompatible_version;
310312
}
311313

312314
register := func(setup, setting string) {
315+
var skip string
316+
switch setup {
317+
case tpchName, tpccName:
318+
skip = "153489. uses ancient fixture"
319+
}
313320
var clusterSpec spec.ClusterSpec
314321
if strings.Contains(setting, "multi-region") {
315322
clusterSpec = r.MakeClusterSpec(numNodes, spec.Geo())
@@ -328,7 +335,7 @@ WITH into_db = 'defaultdb', unsafe_restore_incompatible_version;
328335
Leases: registry.MetamorphicLeases,
329336
NativeLibs: registry.LibGEOS,
330337
Timeout: time.Minute * 20,
331-
Skip: "153489. uses ancient fixture",
338+
Skip: skip,
332339
// NB: sqlsmith failures should never block a release.
333340
NonReleaseBlocker: true,
334341
Run: func(ctx context.Context, t test.Test, c cluster.Cluster) {

pkg/sql/exec_util.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3836,6 +3836,10 @@ func (m *sessionDataMutator) SetAllowPrepareAsOptPlan(val bool) {
38363836
m.data.AllowPrepareAsOptPlan = val
38373837
}
38383838

3839+
func (m *sessionDataMutator) SetRowSecurity(val bool) {
3840+
m.data.RowSecurity = val
3841+
}
3842+
38393843
func (m *sessionDataMutator) SetSaveTablesPrefix(prefix string) {
38403844
m.data.SaveTablesPrefix = prefix
38413845
}

pkg/sql/logictest/testdata/logic_test/information_schema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4243,7 +4243,7 @@ reorder_joins_limit 8
42434243
require_explicit_primary_keys off
42444244
results_buffer_size 524288
42454245
role none
4246-
row_security off
4246+
row_security on
42474247
search_path "$user", public
42484248
serial_normalization rowid
42494249
server_encoding UTF8

pkg/sql/logictest/testdata/logic_test/pg_catalog

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3145,7 +3145,7 @@ reorder_joins_limit 8
31453145
require_explicit_primary_keys off NULL NULL NULL string
31463146
results_buffer_size 524288 NULL NULL NULL string
31473147
role none NULL NULL NULL string
3148-
row_security off NULL NULL NULL string
3148+
row_security on NULL NULL NULL string
31493149
search_path "$user", public NULL NULL NULL string
31503150
serial_normalization rowid NULL NULL NULL string
31513151
server_encoding UTF8 NULL NULL NULL string
@@ -3390,7 +3390,7 @@ reorder_joins_limit 8
33903390
require_explicit_primary_keys off NULL user NULL off off
33913391
results_buffer_size 524288 NULL user NULL 524288 524288
33923392
role none NULL user NULL none none
3393-
row_security off NULL user NULL off off
3393+
row_security on NULL user NULL on on
33943394
search_path "$user", public NULL user NULL "$user", public "$user", public
33953395
serial_normalization rowid NULL user NULL rowid rowid
33963396
server_encoding UTF8 NULL user NULL UTF8 UTF8

pkg/sql/logictest/testdata/logic_test/row_level_security

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5728,3 +5728,206 @@ statement ok
57285728
SET allow_view_with_security_invoker_clause = off
57295729

57305730
subtest end
5731+
5732+
subtest row_security_off
5733+
5734+
statement ok
5735+
CREATE USER alice;
5736+
5737+
statement ok
5738+
CREATE TABLE accounts (
5739+
id INT PRIMARY KEY,
5740+
owner TEXT,
5741+
balance INT
5742+
);
5743+
5744+
statement ok
5745+
INSERT INTO accounts VALUES
5746+
(1, 'alice', 100),
5747+
(2, 'bob', 200);
5748+
5749+
statement ok
5750+
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
5751+
5752+
statement ok
5753+
GRANT ALL ON accounts TO alice;
5754+
5755+
statement ok
5756+
SET ROLE alice;
5757+
5758+
# RLS enabled but no policies (deny all) - row_security='off' should fail.
5759+
query ITI
5760+
SELECT * FROM accounts;
5761+
----
5762+
5763+
statement ok
5764+
SET SESSION row_security = 'off';
5765+
5766+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5767+
SELECT * FROM accounts;
5768+
5769+
statement ok
5770+
RESET row_security;
5771+
5772+
statement ok
5773+
SET ROLE root;
5774+
5775+
# Add policy and test with alice as non-owner.
5776+
statement ok
5777+
CREATE POLICY user_is_owner ON accounts
5778+
USING (owner = current_user());
5779+
5780+
statement ok
5781+
SET ROLE alice;
5782+
5783+
query ITI
5784+
SELECT * FROM accounts;
5785+
----
5786+
1 alice 100
5787+
5788+
statement ok
5789+
SET SESSION row_security = 'off';
5790+
5791+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5792+
SELECT * FROM accounts;
5793+
5794+
statement ok
5795+
RESET row_security;
5796+
5797+
# Test INSERT and UPDATE.
5798+
statement ok
5799+
INSERT INTO accounts VALUES (3, 'alice', 300);
5800+
5801+
statement ok
5802+
UPDATE accounts SET balance = balance + 50 WHERE id = 3;
5803+
5804+
statement ok
5805+
SET SESSION row_security = 'off';
5806+
5807+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5808+
INSERT INTO accounts VALUES (5, 'alice', 500);
5809+
5810+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5811+
UPDATE accounts SET balance = balance + 50 WHERE id = 3;
5812+
5813+
statement ok
5814+
RESET row_security;
5815+
5816+
# Test DELETE and UPSERT.
5817+
statement ok
5818+
DELETE FROM accounts WHERE id = 1;
5819+
5820+
statement ok
5821+
UPSERT INTO accounts VALUES (3, 'alice', 400);
5822+
5823+
statement ok
5824+
SET SESSION row_security = 'off';
5825+
5826+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5827+
DELETE FROM accounts WHERE id = 3;
5828+
5829+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5830+
UPSERT INTO accounts VALUES (6, 'alice', 600);
5831+
5832+
statement ok
5833+
RESET row_security;
5834+
5835+
statement ok
5836+
SET ROLE root;
5837+
5838+
# Table owner bypasses RLS without FORCE.
5839+
statement ok
5840+
ALTER TABLE accounts OWNER TO alice;
5841+
5842+
statement ok
5843+
SET ROLE alice;
5844+
5845+
statement ok
5846+
DROP POLICY user_is_owner ON accounts;
5847+
5848+
statement ok
5849+
CREATE POLICY deny_all ON accounts USING (false);
5850+
5851+
query ITI rowsort
5852+
SELECT * FROM accounts;
5853+
----
5854+
2 bob 200
5855+
3 alice 400
5856+
5857+
statement ok
5858+
SET SESSION row_security = 'off';
5859+
5860+
query ITI rowsort
5861+
SELECT * FROM accounts;
5862+
----
5863+
2 bob 200
5864+
3 alice 400
5865+
5866+
statement ok
5867+
RESET row_security;
5868+
5869+
# With FORCE, table owner is restricted.
5870+
statement ok
5871+
ALTER TABLE accounts FORCE ROW LEVEL SECURITY;
5872+
5873+
query ITI
5874+
SELECT * FROM accounts;
5875+
----
5876+
5877+
statement ok
5878+
SET SESSION row_security = 'off';
5879+
5880+
statement error pgcode 42501 pq: query would be affected by row-level security policy for table "accounts"
5881+
SELECT * FROM accounts;
5882+
5883+
statement ok
5884+
RESET row_security;
5885+
5886+
statement ok
5887+
SET ROLE root;
5888+
5889+
# Verify setting doesn't apply to admin.
5890+
statement ok
5891+
SET SESSION row_security = 'off';
5892+
5893+
query ITI rowsort
5894+
SELECT * FROM accounts;
5895+
----
5896+
2 bob 200
5897+
3 alice 400
5898+
5899+
statement ok
5900+
RESET row_security;
5901+
5902+
# Non-RLS table allows row_security='off'.
5903+
statement ok
5904+
CREATE TABLE non_rls (id INT PRIMARY KEY);
5905+
5906+
statement ok
5907+
INSERT INTO non_rls VALUES (1), (2);
5908+
5909+
statement ok
5910+
GRANT ALL ON non_rls TO alice;
5911+
5912+
statement ok
5913+
SET ROLE alice;
5914+
5915+
statement ok
5916+
SET SESSION row_security = 'off';
5917+
5918+
query I rowsort
5919+
SELECT * FROM non_rls;
5920+
----
5921+
1
5922+
2
5923+
5924+
statement ok
5925+
SET ROLE root;
5926+
5927+
statement ok
5928+
DROP TABLE accounts, non_rls;
5929+
5930+
statement ok
5931+
DROP USER alice;
5932+
5933+
subtest end

pkg/sql/logictest/testdata/logic_test/show_source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ reorder_joins_limit 8
209209
require_explicit_primary_keys off
210210
results_buffer_size 524288
211211
role none
212-
row_security off
212+
row_security on
213213
search_path "$user", public
214214
serial_normalization rowid
215215
server_encoding UTF8

pkg/sql/opt/memo/memo.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ type Memo struct {
211211
useExistsFilterHoistRule bool
212212
disableSlowCascadeFastPathForRBRTables bool
213213
useImprovedHoistJoinProject bool
214+
rowSecurity bool
214215

215216
// txnIsoLevel is the isolation level under which the plan was created. This
216217
// affects the planning of some locking operations, so it must be included in
@@ -318,6 +319,7 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) {
318319
useExistsFilterHoistRule: evalCtx.SessionData().OptimizerUseExistsFilterHoistRule,
319320
disableSlowCascadeFastPathForRBRTables: evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables,
320321
useImprovedHoistJoinProject: evalCtx.SessionData().OptimizerUseImprovedHoistJoinProject,
322+
rowSecurity: evalCtx.SessionData().RowSecurity,
321323
txnIsoLevel: evalCtx.TxnIsoLevel,
322324
}
323325
m.metadata.Init()
@@ -493,6 +495,7 @@ func (m *Memo) IsStale(
493495
m.useExistsFilterHoistRule != evalCtx.SessionData().OptimizerUseExistsFilterHoistRule ||
494496
m.disableSlowCascadeFastPathForRBRTables != evalCtx.SessionData().OptimizerDisableCrossRegionCascadeFastPathForRBRTables ||
495497
m.useImprovedHoistJoinProject != evalCtx.SessionData().OptimizerUseImprovedHoistJoinProject ||
498+
m.rowSecurity != evalCtx.SessionData().RowSecurity ||
496499
m.txnIsoLevel != evalCtx.TxnIsoLevel {
497500
return true, nil
498501
}

pkg/sql/opt/memo/memo_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,12 @@ func TestMemoIsStale(t *testing.T) {
674674
notStale()
675675
o.Memo().Metadata().ClearRLSEnabled()
676676
notStale()
677+
678+
// Stale row_security.
679+
evalCtx.SessionData().RowSecurity = true
680+
stale()
681+
evalCtx.SessionData().RowSecurity = false
682+
notStale()
677683
}
678684

679685
// TestStatsAvailable tests that the statisticsBuilder correctly identifies

pkg/sql/opt/optbuilder/row_level_security.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
1313
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
1414
"github.com/cockroachdb/cockroach/pkg/sql/parser"
15+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
16+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
1517
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
1618
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1719
"github.com/cockroachdb/cockroach/pkg/sql/types"
@@ -85,6 +87,11 @@ func (b *Builder) isExemptFromRLSPolicies(
8587
if isAdmin || isOwnerAndNotForced || bypassRLS {
8688
return true
8789
}
90+
// RLS applies. Reject the query if row-level security is disabled in the session.
91+
if !b.evalCtx.SessionData().RowSecurity {
92+
panic(pgerror.Newf(pgcode.InsufficientPrivilege,
93+
"query would be affected by row-level security policy for table %q", tabMeta.Table.Name()))
94+
}
8895
return false
8996
}
9097

pkg/sql/sessiondatapb/local_only_session_data.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,8 @@ message LocalOnlySessionData {
741741
bool optimizer_use_improved_hoist_join_project = 186;
742742
// EnableInspectCommand controls use of the INSPECT command.
743743
bool enable_inspect_command = 187;
744+
// RowSecurity controls whether row-level security is enabled.
745+
bool row_security = 188;
744746

745747
///////////////////////////////////////////////////////////////////////////
746748
// WARNING: consider whether a session parameter you're adding needs to //

0 commit comments

Comments
 (0)