Skip to content

Commit 693c395

Browse files
craig[bot]angles-n-daemons
andcommitted
Merge #151362
151362: sql: gate system database behind session variable r=angles-n-daemons a=angles-n-daemons 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 Fixes: #151333 Epic: CRDB-24527 Release note (sql change): Adds a new session variable `allow_unsafe_internals` and corresponding cluster setting (`sql.defaults.allow_unsafe_internals`) which controls access to the `system` database. Co-authored-by: Brian Dillmann <brian.dillmann@cockroachlabs.com>
2 parents 3ad5418 + 83cfdfe commit 693c395

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
@@ -662,6 +662,7 @@ ALL_TESTS = [
662662
"//pkg/sql/ttl/ttljob:ttljob_test",
663663
"//pkg/sql/types:types_disallowed_imports_test",
664664
"//pkg/sql/types:types_test",
665+
"//pkg/sql/unsafesql:unsafesql_test",
665666
"//pkg/sql/vecindex/cspann/memstore:memstore_test",
666667
"//pkg/sql/vecindex/cspann/quantize:quantize_test",
667668
"//pkg/sql/vecindex/cspann/utils:utils_test",
@@ -2397,6 +2398,8 @@ GO_TARGETS = [
23972398
"//pkg/sql/ttl/ttlschedule:ttlschedule",
23982399
"//pkg/sql/types:types",
23992400
"//pkg/sql/types:types_test",
2401+
"//pkg/sql/unsafesql:unsafesql",
2402+
"//pkg/sql/unsafesql:unsafesql_test",
24002403
"//pkg/sql/vecindex/cspann/commontest:commontest",
24012404
"//pkg/sql/vecindex/cspann/memstore:memstore",
24022405
"//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
@@ -546,6 +546,7 @@ go_library(
546546
"//pkg/sql/tablemetadatacache/util",
547547
"//pkg/sql/ttl/ttlbase",
548548
"//pkg/sql/types",
549+
"//pkg/sql/unsafesql",
549550
"//pkg/sql/vecindex",
550551
"//pkg/sql/vecindex/vecpb",
551552
"//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
@@ -4383,6 +4383,10 @@ func (m *sessionDataMutator) SetUseProcTxnControlExtendedProtocolFix(val bool) {
43834383
m.data.UseProcTxnControlExtendedProtocolFix = val
43844384
}
43854385

4386+
func (m *sessionDataMutator) SetAllowUnsafeInternals(val bool) {
4387+
m.data.AllowUnsafeInternals = val
4388+
}
4389+
43864390
// Utility functions related to scrubbing sensitive information on SQL Stats.
43874391

43884392
// 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
@@ -2946,6 +2946,7 @@ name setting
29462946
allow_create_trigger_function_with_argv_references off NULL NULL NULL string
29472947
allow_ordinal_column_references off NULL NULL NULL string
29482948
allow_role_memberships_to_change_during_transaction off NULL NULL NULL string
2949+
allow_unsafe_internals on NULL NULL NULL string
29492950
alter_primary_region_super_region_override off NULL NULL NULL string
29502951
always_distribute_full_scans off NULL NULL NULL string
29512952
application_name · NULL NULL NULL string
@@ -3187,6 +3188,7 @@ name setting
31873188
allow_create_trigger_function_with_argv_references off NULL user NULL off off
31883189
allow_ordinal_column_references off NULL user NULL off off
31893190
allow_role_memberships_to_change_during_transaction off NULL user NULL off off
3191+
allow_unsafe_internals on NULL user NULL on on
31903192
alter_primary_region_super_region_override off NULL user NULL off off
31913193
always_distribute_full_scans off NULL user NULL off off
31923194
application_name · NULL user NULL · ·
@@ -3412,6 +3414,7 @@ name source min_val
34123414
allow_create_trigger_function_with_argv_references NULL NULL NULL NULL NULL
34133415
allow_ordinal_column_references NULL NULL NULL NULL NULL
34143416
allow_role_memberships_to_change_during_transaction NULL NULL NULL NULL NULL
3417+
allow_unsafe_internals NULL NULL NULL NULL NULL
34153418
alter_primary_region_super_region_override NULL NULL NULL NULL NULL
34163419
always_distribute_full_scans NULL NULL NULL NULL NULL
34173420
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)