Skip to content

Commit 83cfdfe

Browse files
sql: gate system database behind session variable
It today isn't quite clear which internal objects are safe for external usage. For example, selecting from crdb_internal.zones is considered safe, while deleting from system.jobs is not. To address this, we're adding a gate protecting some of these internals, as well as auditing around their usage so that we can better see how the internal state of the database may have been modified. The start of this effort is this PR, which adds an gate on the privilege check for system descriptors. This gate is simple now, it checks whether the caller is internal or if the override session variable is set to access "unsafe internals". It should be noted that allowance is the default to start, so that extensive testing can be done on this feature before its rolled out more widely. Fixes: #149594, #151333 Epic: CRDB-24527 Release note (sql change): Adds a new session variable `allow_unsafe_internals` which controls access to the `system` database.
1 parent 9d40033 commit 83cfdfe

File tree

14 files changed

+240
-0
lines changed

14 files changed

+240
-0
lines changed

pkg/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ ALL_TESTS = [
658658
"//pkg/sql/ttl/ttljob:ttljob_test",
659659
"//pkg/sql/types:types_disallowed_imports_test",
660660
"//pkg/sql/types:types_test",
661+
"//pkg/sql/unsafesql:unsafesql_test",
661662
"//pkg/sql/vecindex/cspann/memstore:memstore_test",
662663
"//pkg/sql/vecindex/cspann/quantize:quantize_test",
663664
"//pkg/sql/vecindex/cspann/utils:utils_test",
@@ -2387,6 +2388,8 @@ GO_TARGETS = [
23872388
"//pkg/sql/ttl/ttlschedule:ttlschedule",
23882389
"//pkg/sql/types:types",
23892390
"//pkg/sql/types:types_test",
2391+
"//pkg/sql/unsafesql:unsafesql",
2392+
"//pkg/sql/unsafesql:unsafesql_test",
23902393
"//pkg/sql/vecindex/cspann/commontest:commontest",
23912394
"//pkg/sql/vecindex/cspann/memstore:memstore",
23922395
"//pkg/sql/vecindex/cspann/memstore:memstore_test",

pkg/sql/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ go_library(
545545
"//pkg/sql/tablemetadatacache/util",
546546
"//pkg/sql/ttl/ttlbase",
547547
"//pkg/sql/types",
548+
"//pkg/sql/unsafesql",
548549
"//pkg/sql/vecindex",
549550
"//pkg/sql/vecindex/vecpb",
550551
"//pkg/sql/vecindex/vecstore",

pkg/sql/authorization.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
3333
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
3434
"github.com/cockroachdb/cockroach/pkg/sql/syntheticprivilege"
35+
"github.com/cockroachdb/cockroach/pkg/sql/unsafesql"
3536
"github.com/cockroachdb/errors"
3637
)
3738

@@ -121,6 +122,16 @@ func (p *planner) HasPrivilege(
121122
return false, errors.AssertionFailedf("cannot use CheckPrivilege without a txn")
122123
}
123124

125+
// Check for system table access restrictions before any admin bypasses
126+
if d, ok := privilegeObject.(catalog.TableDescriptor); ok {
127+
// Check for system table access restrictions before any admin bypasses
128+
if catalog.IsSystemDescriptor(d) {
129+
if err := unsafesql.CheckInternalsAccess(p.SessionData()); err != nil {
130+
return false, err
131+
}
132+
}
133+
}
134+
124135
// root, admin and node user should always have privileges, except NOSQLLOGIN.
125136
// This allows us to short-circuit privilege checks for
126137
// virtual object such that we don't have to query the system.privileges

pkg/sql/authorization_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package sql_test
88
import (
99
"context"
1010
"fmt"
11+
"strings"
1112
"sync"
1213
"testing"
1314
"time"
@@ -16,13 +17,17 @@ import (
1617
"github.com/cockroachdb/cockroach/pkg/security/username"
1718
"github.com/cockroachdb/cockroach/pkg/sql"
1819
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
20+
"github.com/cockroachdb/cockroach/pkg/sql/isql"
1921
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
22+
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
2023
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
2124
"github.com/cockroachdb/cockroach/pkg/testutils/skip"
2225
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
2326
"github.com/cockroachdb/cockroach/pkg/util/ctxgroup"
2427
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
2528
"github.com/cockroachdb/cockroach/pkg/util/log"
29+
"github.com/cockroachdb/errors"
30+
"github.com/lib/pq"
2631
"github.com/stretchr/testify/require"
2732
)
2833

@@ -289,3 +294,84 @@ func TestEnsureUserOnlyBelongsToRoles(t *testing.T) {
289294
require.Empty(t, getRoles(testUser))
290295
})
291296
}
297+
298+
func checkUnsafeErr(t *testing.T, err error) {
299+
var pqErr *pq.Error
300+
301+
if errors.Is(err, sqlerrors.ErrUnsafeTableAccess) {
302+
return
303+
}
304+
305+
if errors.As(err, &pqErr) &&
306+
pqErr.Code == "42501" &&
307+
strings.Contains(pqErr.Message, "Access to crdb_internal and system is restricted") {
308+
return
309+
}
310+
311+
t.Fatal("expected unsafe access error, got", err)
312+
}
313+
314+
func TestCheckUnsafeInternalsAccess(t *testing.T) {
315+
defer leaktest.AfterTest(t)()
316+
defer log.Scope(t).Close(t)
317+
318+
ctx := context.Background()
319+
s := serverutils.StartServerOnly(t, base.TestServerArgs{
320+
DefaultTestTenant: base.TestControlsTenantsExplicitly,
321+
})
322+
defer s.Stopper().Stop(ctx)
323+
324+
q := "SELECT * FROM system.namespace"
325+
326+
t.Run("as an external querier", func(t *testing.T) {
327+
for _, test := range []struct {
328+
AllowUnsafeInternals bool
329+
Passes bool
330+
}{
331+
{AllowUnsafeInternals: false, Passes: false},
332+
{AllowUnsafeInternals: true, Passes: true},
333+
} {
334+
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
335+
conn := s.SQLConn(t)
336+
_, err := conn.Exec("SET allow_unsafe_internals = $1", test.AllowUnsafeInternals)
337+
require.NoError(t, err)
338+
339+
_, err = conn.Query(q)
340+
if test.Passes {
341+
require.NoError(t, err)
342+
} else {
343+
checkUnsafeErr(t, err)
344+
}
345+
})
346+
}
347+
})
348+
349+
t.Run("as an internal querier", func(t *testing.T) {
350+
for _, test := range []struct {
351+
AllowUnsafeInternals bool
352+
Passes bool
353+
}{
354+
{AllowUnsafeInternals: false, Passes: true},
355+
{AllowUnsafeInternals: true, Passes: true},
356+
} {
357+
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
358+
idb := s.InternalDB().(isql.DB)
359+
err := idb.Txn(ctx, func(ctx context.Context, txn isql.Txn) error {
360+
txn.SessionData().LocalOnlySessionData.AllowUnsafeInternals = test.AllowUnsafeInternals
361+
362+
_, err := txn.QueryBuffered(ctx, "internal-query", txn.KV(), q)
363+
return err
364+
})
365+
366+
require.NoError(t, err)
367+
368+
if test.Passes {
369+
require.NoError(t, err)
370+
} else {
371+
checkUnsafeErr(t, err)
372+
}
373+
})
374+
}
375+
})
376+
377+
}

pkg/sql/exec_util.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4267,6 +4267,10 @@ func (m *sessionDataMutator) SetUseProcTxnControlExtendedProtocolFix(val bool) {
42674267
m.data.UseProcTxnControlExtendedProtocolFix = val
42684268
}
42694269

4270+
func (m *sessionDataMutator) SetAllowUnsafeInternals(val bool) {
4271+
m.data.AllowUnsafeInternals = val
4272+
}
4273+
42704274
// Utility functions related to scrubbing sensitive information on SQL Stats.
42714275

42724276
// quantizeCounts ensures that the Count field in the

pkg/sql/logictest/testdata/logic_test/information_schema

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3936,6 +3936,7 @@ variable value
39363936
allow_create_trigger_function_with_argv_references off
39373937
allow_ordinal_column_references off
39383938
allow_role_memberships_to_change_during_transaction off
3939+
allow_unsafe_internals on
39393940
alter_primary_region_super_region_override off
39403941
always_distribute_full_scans off
39413942
application_name ·

pkg/sql/logictest/testdata/logic_test/pg_catalog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2936,6 +2936,7 @@ name setting
29362936
allow_create_trigger_function_with_argv_references off NULL NULL NULL string
29372937
allow_ordinal_column_references off NULL NULL NULL string
29382938
allow_role_memberships_to_change_during_transaction off NULL NULL NULL string
2939+
allow_unsafe_internals on NULL NULL NULL string
29392940
alter_primary_region_super_region_override off NULL NULL NULL string
29402941
always_distribute_full_scans off NULL NULL NULL string
29412942
application_name · NULL NULL NULL string
@@ -3177,6 +3178,7 @@ name setting
31773178
allow_create_trigger_function_with_argv_references off NULL user NULL off off
31783179
allow_ordinal_column_references off NULL user NULL off off
31793180
allow_role_memberships_to_change_during_transaction off NULL user NULL off off
3181+
allow_unsafe_internals on NULL user NULL on on
31803182
alter_primary_region_super_region_override off NULL user NULL off off
31813183
always_distribute_full_scans off NULL user NULL off off
31823184
application_name · NULL user NULL · ·
@@ -3402,6 +3404,7 @@ name source min_val
34023404
allow_create_trigger_function_with_argv_references NULL NULL NULL NULL NULL
34033405
allow_ordinal_column_references NULL NULL NULL NULL NULL
34043406
allow_role_memberships_to_change_during_transaction NULL NULL NULL NULL NULL
3407+
allow_unsafe_internals NULL NULL NULL NULL NULL
34053408
alter_primary_region_super_region_override NULL NULL NULL NULL NULL
34063409
always_distribute_full_scans NULL NULL NULL NULL NULL
34073410
application_name NULL NULL NULL NULL NULL

pkg/sql/logictest/testdata/logic_test/show_source

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ variable value
4040
allow_create_trigger_function_with_argv_references off
4141
allow_ordinal_column_references off
4242
allow_role_memberships_to_change_during_transaction off
43+
allow_unsafe_internals on
4344
alter_primary_region_super_region_override off
4445
always_distribute_full_scans off
4546
application_name ·

pkg/sql/sessiondatapb/local_only_session_data.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,10 @@ message LocalOnlySessionData {
729729
// mutations), regardless of the table being multi-region.
730730
bool parallelize_multi_key_lookup_joins_only_on_mr_mutations = 182 [(gogoproto.customname) = "ParallelizeMultiKeyLookupJoinsOnlyOnMRMutations"];
731731

732+
// AllowUnsafeInternals allows the caller to access the crdb_internal
733+
// or system databases within queries.
734+
bool allow_unsafe_internals = 183;
735+
732736
///////////////////////////////////////////////////////////////////////////
733737
// WARNING: consider whether a session parameter you're adding needs to //
734738
// be propagated to the remote nodes. If so, that parameter should live //

pkg/sql/sqlerrors/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ var (
661661
ErrNoType = pgerror.New(pgcode.InvalidName, "no type specified")
662662
ErrNoFunction = pgerror.New(pgcode.InvalidName, "no function specified")
663663
ErrNoMatch = pgerror.New(pgcode.UndefinedObject, "no object matched")
664+
ErrUnsafeTableAccess = errors.WithHint(pgerror.New(pgcode.InsufficientPrivilege, "Access to crdb_internal and system is restricted."), "These interfaces are unsupported in production. To proceed, set the session variable allow_unsafe_internals = true (not recommended), or contact Cockroach Labs for a supported alternative.")
664665
)
665666

666667
var ErrNoZoneConfigApplies = errors.New("no zone config applies")

0 commit comments

Comments
 (0)