Skip to content

Commit cf97c5e

Browse files
authored
feat(rbac): RBAC support Sequence object (#18423)
* feat(rbac): RBAC support Sequence object 1. **New Configuration Parameter:** Introduces `enable_experimental_sequence_rbac_check` to toggle RBAC permission verification for sequences. Disabled by default for backward compatibility. 2. **Global Privileges:** Adds `CREATE SEQUENCE` and `ACCESS SEQUENCE` global privileges governing sequence creation and unrestricted usage rights respectively. 3. **Ownership Model:** Implements `OWNERSHIP` semantics, allowing privileged users/roles to perform arbitrary DDL operations on sequences. 4. **Show grants on sequence <sequence_name>.** * fix test * fix ci * fix ci * fix ci * QueryEnv add UserInfo
1 parent 3075b0a commit cf97c5e

File tree

65 files changed

+1071
-108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1071
-108
lines changed

Cargo.lock

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

src/meta/app/src/principal/ownership_object.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ pub enum OwnershipObject {
6060
Connection {
6161
name: String,
6262
},
63+
64+
Sequence {
65+
name: String,
66+
},
6367
}
6468

6569
impl OwnershipObject {
@@ -90,6 +94,7 @@ impl fmt::Display for OwnershipObject {
9094
OwnershipObject::Stage { name } => write!(f, "STAGE {name}"),
9195
OwnershipObject::Warehouse { id } => write!(f, "WAREHOUSE {id}"),
9296
OwnershipObject::Connection { name } => write!(f, "CONNECTION {name}"),
97+
OwnershipObject::Sequence { name } => write!(f, "SEQUENCE {name}"),
9398
}
9499
}
95100
}
@@ -131,6 +136,7 @@ impl KeyCodec for OwnershipObject {
131136
OwnershipObject::UDF { name } => b.push_raw("udf-by-name").push_str(name),
132137
OwnershipObject::Warehouse { id } => b.push_raw("warehouse-by-id").push_str(id),
133138
OwnershipObject::Connection { name } => b.push_raw("connection-by-name").push_str(name),
139+
OwnershipObject::Sequence { name } => b.push_raw("sequence-by-name").push_str(name),
134140
}
135141
}
136142

@@ -185,6 +191,10 @@ impl KeyCodec for OwnershipObject {
185191
let name = p.next_str()?;
186192
Ok(OwnershipObject::Connection { name })
187193
}
194+
"sequence-by-name" => {
195+
let name = p.next_str()?;
196+
Ok(OwnershipObject::Sequence { name })
197+
}
188198
_ => Err(kvapi::KeyError::InvalidSegment {
189199
i: p.index(),
190200
expect: "database-by-id|database-by-catalog-id|table-by-id|table-by-catalog-id|stage-by-name|udf-by-name|warehouse-by-id|connection-by-name"

src/meta/app/src/principal/user_grant.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub enum GrantObject {
6161
Stage(String),
6262
Warehouse(String),
6363
Connection(String),
64+
Sequence(String),
6465
}
6566

6667
impl GrantObject {
@@ -95,6 +96,7 @@ impl GrantObject {
9596
(GrantObject::UDF(udf), GrantObject::UDF(rudf)) => udf == rudf,
9697
(GrantObject::Warehouse(w), GrantObject::Warehouse(rw)) => w == rw,
9798
(GrantObject::Connection(c), GrantObject::Connection(rc)) => c == rc,
99+
(GrantObject::Sequence(s), GrantObject::Sequence(rs)) => s == rs,
98100
_ => false,
99101
}
100102
}
@@ -121,6 +123,9 @@ impl GrantObject {
121123
GrantObject::Connection(_) => {
122124
UserPrivilegeSet::available_privileges_on_connection(available_ownership)
123125
}
126+
GrantObject::Sequence(_) => {
127+
UserPrivilegeSet::available_privileges_on_sequence(available_ownership)
128+
}
124129
}
125130
}
126131

@@ -130,6 +135,7 @@ impl GrantObject {
130135
| GrantObject::Stage(_)
131136
| GrantObject::UDF(_)
132137
| GrantObject::Warehouse(_)
138+
| GrantObject::Sequence(_)
133139
| GrantObject::Connection(_) => None,
134140
GrantObject::Database(cat, _) | GrantObject::DatabaseById(cat, _) => Some(cat.clone()),
135141
GrantObject::Table(cat, _, _) | GrantObject::TableById(cat, _, _) => Some(cat.clone()),
@@ -153,6 +159,7 @@ impl fmt::Display for GrantObject {
153159
GrantObject::Stage(stage) => write!(f, "STAGE {stage}"),
154160
GrantObject::Warehouse(w) => write!(f, "WAREHOUSE {w}"),
155161
GrantObject::Connection(c) => write!(f, "CONNECTION {c}"),
162+
GrantObject::Sequence(s) => write!(f, "SEQUENCE {s}"),
156163
}
157164
}
158165
}

src/meta/app/src/principal/user_privilege.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ pub enum UserPrivilegeType {
8282
CreateConnection = 1 << 22,
8383
// Privilege to Access Connection
8484
AccessConnection = 1 << 23,
85+
// Privilege to Create Sequence
86+
CreateSequence = 1 << 24,
87+
// Privilege to Access Sequence
88+
AccessSequence = 1 << 25,
8589
// Discard Privilege Type
8690
Set = 1 << 4,
8791
}
@@ -141,6 +145,8 @@ impl Display for UserPrivilegeType {
141145
UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE",
142146
UserPrivilegeType::CreateConnection => "CREATE CONNECTION",
143147
UserPrivilegeType::AccessConnection => "ACCESS CONNECTION",
148+
UserPrivilegeType::CreateSequence => "CREATE SEQUENCE",
149+
UserPrivilegeType::AccessSequence => "ACCESS SEQUENCE",
144150
})
145151
}
146152
}
@@ -187,6 +193,12 @@ impl From<databend_common_ast::ast::UserPrivilegeType> for UserPrivilegeType {
187193
databend_common_ast::ast::UserPrivilegeType::AccessConnection => {
188194
UserPrivilegeType::AccessConnection
189195
}
196+
databend_common_ast::ast::UserPrivilegeType::CreateSequence => {
197+
UserPrivilegeType::CreateSequence
198+
}
199+
databend_common_ast::ast::UserPrivilegeType::AccessSequence => {
200+
UserPrivilegeType::AccessSequence
201+
}
190202
databend_common_ast::ast::UserPrivilegeType::Set => UserPrivilegeType::Set,
191203
}
192204
}
@@ -222,12 +234,14 @@ impl UserPrivilegeSet {
222234
let udf_privs_without_ownership = Self::available_privileges_on_udf(false);
223235
let wh_privs_without_ownership = Self::available_privileges_on_warehouse(false);
224236
let connection_privs_without_ownership = Self::available_privileges_on_connection(false);
225-
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask | CreateWarehouse | CreateConnection });
237+
let seq_privs_without_ownership = Self::available_privileges_on_sequence(false);
238+
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask | CreateWarehouse | CreateConnection | CreateSequence });
226239
(database_privs.privileges
227240
| privs
228241
| stage_privs_without_ownership.privileges
229242
| wh_privs_without_ownership.privileges
230243
| connection_privs_without_ownership.privileges
244+
| seq_privs_without_ownership.privileges
231245
| udf_privs_without_ownership.privileges)
232246
.into()
233247
}
@@ -275,6 +289,14 @@ impl UserPrivilegeSet {
275289
}
276290
}
277291

292+
pub fn available_privileges_on_sequence(available_ownership: bool) -> Self {
293+
if available_ownership {
294+
make_bitflags!(UserPrivilegeType::{ AccessSequence | Ownership }).into()
295+
} else {
296+
make_bitflags!(UserPrivilegeType::{ AccessSequence }).into()
297+
}
298+
}
299+
278300
pub fn available_privileges_on_udf(available_ownership: bool) -> Self {
279301
if available_ownership {
280302
make_bitflags!(UserPrivilegeType::{ Usage | Ownership }).into()

src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ impl FromToProto for mt::principal::OwnershipObject {
9494
pb::ownership_object::Object::Connection(
9595
pb::ownership_object::OwnershipConnectionObject { connection },
9696
) => Ok(mt::principal::OwnershipObject::Connection { name: connection }),
97+
pb::ownership_object::Object::Sequence(
98+
pb::ownership_object::OwnershipSequenceObject { sequence },
99+
) => Ok(mt::principal::OwnershipObject::Sequence { name: sequence }),
97100
}
98101
}
99102

@@ -141,6 +144,13 @@ impl FromToProto for mt::principal::OwnershipObject {
141144
},
142145
))
143146
}
147+
mt::principal::OwnershipObject::Sequence { name } => {
148+
Some(pb::ownership_object::Object::Sequence(
149+
pb::ownership_object::OwnershipSequenceObject {
150+
sequence: name.clone(),
151+
},
152+
))
153+
}
144154
};
145155
Ok(pb::OwnershipObject {
146156
ver: VER,

src/meta/proto-conv/src/user_from_to_protobuf_impl.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ impl FromToProto for mt::principal::GrantObject {
197197
pb::grant_object::Object::Connection(pb::grant_object::GrantConnectionObject {
198198
connection,
199199
}) => Ok(mt::principal::GrantObject::Connection(connection)),
200+
pb::grant_object::Object::Sequence(pb::grant_object::GrantSequenceObject {
201+
sequence,
202+
}) => Ok(mt::principal::GrantObject::Sequence(sequence)),
200203
}
201204
}
202205

@@ -249,6 +252,11 @@ impl FromToProto for mt::principal::GrantObject {
249252
connection: c.clone(),
250253
}),
251254
),
255+
mt::principal::GrantObject::Sequence(s) => Some(pb::grant_object::Object::Sequence(
256+
pb::grant_object::GrantSequenceObject {
257+
sequence: s.clone(),
258+
},
259+
)),
252260
};
253261
Ok(pb::GrantObject {
254262
ver: VER,

src/meta/proto-conv/src/util.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
168168
(136, "2025-07-17: Add: Task"),
169169
(137, "2025-07-22: Add: GrantConnectionObject and UserPrivilegeType AccessConnection, AccessConnection"),
170170
(138, "2025-07-23: Add: TableStatistics add index size"),
171+
(139, "2025-07-25: Add: Grant/OwnershipSequenceObject and UserPrivilegeType AccessSequence, AccessSequence"),
171172
// Dear developer:
172173
// If you're gonna add a new metadata version, you'll have to add a test for it.
173174
// You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)

src/meta/proto-conv/tests/it/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@ mod v135_udf_immutable;
130130
mod v136_add_task;
131131
mod v137_add_grant_object_connection;
132132
mod v138_table_statistics;
133+
mod v139_add_grant_ownership_object_sequence;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2023 Datafuse Labs.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::collections::HashSet;
16+
17+
use chrono::DateTime;
18+
use chrono::Utc;
19+
use databend_common_meta_app as mt;
20+
use databend_common_meta_app::principal::OwnershipObject;
21+
use databend_common_meta_app::principal::UserGrantSet;
22+
use databend_common_meta_app::principal::UserPrivilegeType;
23+
use enumflags2::make_bitflags;
24+
use fastrace::func_name;
25+
26+
use crate::common;
27+
28+
// These bytes are built when a new version in introduced,
29+
// and are kept for backward compatibility test.
30+
//
31+
// *************************************************************
32+
// * These messages should never be updated, *
33+
// * only be added when a new version is added, *
34+
// * or be removed when an old version is no longer supported. *
35+
// *************************************************************
36+
//
37+
38+
#[test]
39+
fn test_decode_v139_grant_object() -> anyhow::Result<()> {
40+
let role_info_v139 = vec![
41+
10, 2, 114, 49, 18, 86, 10, 23, 10, 9, 10, 0, 160, 6, 139, 1, 168, 6, 24, 16, 128, 128,
42+
128, 8, 160, 6, 139, 1, 168, 6, 24, 10, 27, 10, 13, 82, 4, 10, 2, 115, 49, 160, 6, 139, 1,
43+
168, 6, 24, 16, 128, 128, 128, 16, 160, 6, 139, 1, 168, 6, 24, 10, 23, 10, 9, 10, 0, 160,
44+
6, 139, 1, 168, 6, 24, 16, 254, 255, 191, 31, 160, 6, 139, 1, 168, 6, 24, 160, 6, 139, 1,
45+
168, 6, 24, 26, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, 48,
46+
48, 32, 85, 84, 67, 34, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48,
47+
58, 48, 48, 32, 85, 84, 67, 160, 6, 139, 1, 168, 6, 24,
48+
];
49+
50+
let want = || mt::principal::RoleInfo {
51+
name: "r1".to_string(),
52+
grants: UserGrantSet::new(
53+
vec![
54+
mt::principal::GrantEntry::new(
55+
mt::principal::GrantObject::Global,
56+
make_bitflags!(UserPrivilegeType::{CreateSequence}),
57+
),
58+
mt::principal::GrantEntry::new(
59+
mt::principal::GrantObject::Sequence("s1".to_string()),
60+
make_bitflags!(UserPrivilegeType::{AccessSequence}),
61+
),
62+
// test new global privilege CreateSequence, AccessConnection
63+
mt::principal::GrantEntry::new(
64+
mt::principal::GrantObject::Global,
65+
make_bitflags!(UserPrivilegeType::{Create | Select | Insert | Update | Delete | Drop | Alter | Super | CreateUser | DropUser | CreateRole | DropRole | Grant | CreateStage | Set | CreateDataMask | Ownership | Read | Write | CreateWarehouse | CreateConnection | AccessConnection | CreateSequence | AccessSequence }),
66+
),
67+
],
68+
HashSet::new(),
69+
),
70+
created_on: DateTime::<Utc>::default(),
71+
update_on: DateTime::<Utc>::default(),
72+
};
73+
74+
common::test_pb_from_to(func_name!(), want())?;
75+
common::test_load_old(func_name!(), role_info_v139.as_slice(), 139, want())?;
76+
77+
Ok(())
78+
}
79+
80+
#[test]
81+
fn test_decode_v139_ownership() -> anyhow::Result<()> {
82+
let ownership_info_v139 = vec![
83+
10, 2, 114, 49, 18, 13, 58, 4, 10, 2, 115, 49, 160, 6, 139, 1, 168, 6, 24, 160, 6, 139, 1,
84+
168, 6, 24,
85+
];
86+
87+
let want = || mt::principal::OwnershipInfo {
88+
role: "r1".to_string(),
89+
object: OwnershipObject::Sequence {
90+
name: "s1".to_string(),
91+
},
92+
};
93+
common::test_pb_from_to(func_name!(), want())?;
94+
common::test_load_old(func_name!(), ownership_info_v139.as_slice(), 139, want())?;
95+
96+
Ok(())
97+
}

src/meta/protos/proto/ownership.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,17 @@ message OwnershipObject {
5454
string connection = 1;
5555
}
5656

57+
message OwnershipSequenceObject {
58+
string sequence = 1;
59+
}
60+
5761
oneof object {
5862
OwnershipDatabaseObject database = 1;
5963
OwnershipTableObject table = 2;
6064
OwnershipUdfObject udf = 3;
6165
OwnershipStageObject stage = 4;
6266
OwnershipWarehouseObject warehouse = 5;
6367
OwnershipConnectionObject connection = 6;
68+
OwnershipSequenceObject sequence = 7;
6469
}
6570
}

0 commit comments

Comments
 (0)