Skip to content

Commit 765bd2b

Browse files
committed
feat(rbac): RBAC support connection object
1. **New Configuration Parameter:** Introduces `enable_experimental_connection_rbac_check` to toggle RBAC permission verification for connections. Disabled by default for backward compatibility. 2. **Global Privileges:** Adds `CREATE CONNECTION` and `ACCESS CONNECTION` global privileges governing connection creation and unrestricted usage rights respectively. 3. **Ownership Model:** Implements `OWNERSHIP` semantics, allowing privileged users/roles to perform arbitrary DDL operations on connections. 4. **Show grants on connection <connection_name>.
1 parent f58ace5 commit 765bd2b

34 files changed

+891
-57
lines changed

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ pub enum OwnershipObject {
5656
Warehouse {
5757
id: String,
5858
},
59+
60+
Connection {
61+
name: String,
62+
},
5963
}
6064

6165
impl OwnershipObject {
@@ -84,7 +88,8 @@ impl fmt::Display for OwnershipObject {
8488
}
8589
OwnershipObject::UDF { name } => write!(f, "UDF {name}"),
8690
OwnershipObject::Stage { name } => write!(f, "STAGE {name}"),
87-
OwnershipObject::Warehouse { id } => write!(f, "Warehouse {id}"),
91+
OwnershipObject::Warehouse { id } => write!(f, "WAREHOUSE {id}"),
92+
OwnershipObject::Connection { name } => write!(f, "CONNECTION {name}"),
8893
}
8994
}
9095
}
@@ -125,6 +130,7 @@ impl KeyCodec for OwnershipObject {
125130
OwnershipObject::Stage { name } => b.push_raw("stage-by-name").push_str(name),
126131
OwnershipObject::UDF { name } => b.push_raw("udf-by-name").push_str(name),
127132
OwnershipObject::Warehouse { id } => b.push_raw("warehouse-by-id").push_str(id),
133+
OwnershipObject::Connection { name } => b.push_raw("connection-by-name").push_str(name),
128134
}
129135
}
130136

@@ -175,9 +181,13 @@ impl KeyCodec for OwnershipObject {
175181
let id = p.next_str()?;
176182
Ok(OwnershipObject::Warehouse { id })
177183
}
184+
"connection-by-name" => {
185+
let name = p.next_str()?;
186+
Ok(OwnershipObject::Connection { name })
187+
}
178188
_ => Err(kvapi::KeyError::InvalidSegment {
179189
i: p.index(),
180-
expect: "database-by-id|database-by-catalog-id|table-by-id|table-by-catalog-id|stage-by-name|udf-by-name|warehouse-by-id"
190+
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"
181191
.to_string(),
182192
got: q.to_string(),
183193
}),

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub enum GrantObject {
6060
UDF(String),
6161
Stage(String),
6262
Warehouse(String),
63+
Connection(String),
6364
}
6465

6566
impl GrantObject {
@@ -93,6 +94,7 @@ impl GrantObject {
9394
(GrantObject::Stage(lstage), GrantObject::Stage(rstage)) => lstage == rstage,
9495
(GrantObject::UDF(udf), GrantObject::UDF(rudf)) => udf == rudf,
9596
(GrantObject::Warehouse(w), GrantObject::Warehouse(rw)) => w == rw,
97+
(GrantObject::Connection(c), GrantObject::Connection(rc)) => c == rc,
9698
_ => false,
9799
}
98100
}
@@ -116,6 +118,9 @@ impl GrantObject {
116118
GrantObject::Warehouse(_) => {
117119
UserPrivilegeSet::available_privileges_on_warehouse(available_ownership)
118120
}
121+
GrantObject::Connection(_) => {
122+
UserPrivilegeSet::available_privileges_on_connection(available_ownership)
123+
}
119124
}
120125
}
121126

@@ -124,7 +129,8 @@ impl GrantObject {
124129
GrantObject::Global
125130
| GrantObject::Stage(_)
126131
| GrantObject::UDF(_)
127-
| GrantObject::Warehouse(_) => None,
132+
| GrantObject::Warehouse(_)
133+
| GrantObject::Connection(_) => None,
128134
GrantObject::Database(cat, _) | GrantObject::DatabaseById(cat, _) => Some(cat.clone()),
129135
GrantObject::Table(cat, _, _) | GrantObject::TableById(cat, _, _) => Some(cat.clone()),
130136
}
@@ -146,6 +152,7 @@ impl fmt::Display for GrantObject {
146152
GrantObject::UDF(udf) => write!(f, "UDF {udf}"),
147153
GrantObject::Stage(stage) => write!(f, "STAGE {stage}"),
148154
GrantObject::Warehouse(w) => write!(f, "WAREHOUSE {w}"),
155+
GrantObject::Connection(c) => write!(f, "CONNECTION {c}"),
149156
}
150157
}
151158
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ pub enum UserPrivilegeType {
7878
CreateDatabase = 1 << 20,
7979
// Privilege to Create warehouse
8080
CreateWarehouse = 1 << 21,
81+
// Privilege to Create Connection
82+
CreateConnection = 1 << 22,
83+
// Privilege to Access Connection
84+
AccessConnection = 1 << 23,
8185
// Discard Privilege Type
8286
Set = 1 << 4,
8387
}
@@ -105,6 +109,8 @@ const ALL_PRIVILEGES: BitFlags<UserPrivilegeType> = make_bitflags!(
105109
| Write
106110
| CreateDatabase
107111
| CreateWarehouse
112+
| CreateConnection
113+
| AccessConnection
108114
}
109115
);
110116

@@ -133,6 +139,8 @@ impl Display for UserPrivilegeType {
133139
UserPrivilegeType::Write => "Write",
134140
UserPrivilegeType::CreateDatabase => "CREATE DATABASE",
135141
UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE",
142+
UserPrivilegeType::CreateConnection => "CREATE CONNECTION",
143+
UserPrivilegeType::AccessConnection => "ACCESS CONNECTION",
136144
})
137145
}
138146
}
@@ -173,6 +181,12 @@ impl From<databend_common_ast::ast::UserPrivilegeType> for UserPrivilegeType {
173181
databend_common_ast::ast::UserPrivilegeType::CreateWarehouse => {
174182
UserPrivilegeType::CreateWarehouse
175183
}
184+
databend_common_ast::ast::UserPrivilegeType::CreateConnection => {
185+
UserPrivilegeType::CreateConnection
186+
}
187+
databend_common_ast::ast::UserPrivilegeType::AccessConnection => {
188+
UserPrivilegeType::AccessConnection
189+
}
176190
databend_common_ast::ast::UserPrivilegeType::Set => UserPrivilegeType::Set,
177191
}
178192
}
@@ -207,11 +221,13 @@ impl UserPrivilegeSet {
207221
let stage_privs_without_ownership = Self::available_privileges_on_stage(false);
208222
let udf_privs_without_ownership = Self::available_privileges_on_udf(false);
209223
let wh_privs_without_ownership = Self::available_privileges_on_warehouse(false);
210-
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask | CreateWarehouse });
224+
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 });
211226
(database_privs.privileges
212227
| privs
213228
| stage_privs_without_ownership.privileges
214229
| wh_privs_without_ownership.privileges
230+
| connection_privs_without_ownership.privileges
215231
| udf_privs_without_ownership.privileges)
216232
.into()
217233
}
@@ -251,6 +267,14 @@ impl UserPrivilegeSet {
251267
}
252268
}
253269

270+
pub fn available_privileges_on_connection(available_ownership: bool) -> Self {
271+
if available_ownership {
272+
make_bitflags!(UserPrivilegeType::{ AccessConnection | Ownership }).into()
273+
} else {
274+
make_bitflags!(UserPrivilegeType::{ AccessConnection }).into()
275+
}
276+
}
277+
254278
pub fn available_privileges_on_udf(available_ownership: bool) -> Self {
255279
if available_ownership {
256280
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
@@ -91,6 +91,9 @@ impl FromToProto for mt::principal::OwnershipObject {
9191
pb::ownership_object::Object::Warehouse(
9292
pb::ownership_object::OwnershipWarehouseObject { id },
9393
) => Ok(mt::principal::OwnershipObject::Warehouse { id }),
94+
pb::ownership_object::Object::Connection(
95+
pb::ownership_object::OwnershipConnectionObject { connection },
96+
) => Ok(mt::principal::OwnershipObject::Connection { name: connection }),
9497
}
9598
}
9699

@@ -131,6 +134,13 @@ impl FromToProto for mt::principal::OwnershipObject {
131134
pb::ownership_object::OwnershipWarehouseObject { id: id.clone() },
132135
))
133136
}
137+
mt::principal::OwnershipObject::Connection { name } => {
138+
Some(pb::ownership_object::Object::Connection(
139+
pb::ownership_object::OwnershipConnectionObject {
140+
connection: name.clone(),
141+
},
142+
))
143+
}
134144
};
135145
Ok(pb::OwnershipObject {
136146
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
@@ -194,6 +194,9 @@ impl FromToProto for mt::principal::GrantObject {
194194
pb::grant_object::Object::Warehouse(pb::grant_object::GrantWarehouseObject {
195195
warehouse,
196196
}) => Ok(mt::principal::GrantObject::Warehouse(warehouse)),
197+
pb::grant_object::Object::Connection(pb::grant_object::GrantConnectionObject {
198+
connection,
199+
}) => Ok(mt::principal::GrantObject::Connection(connection)),
197200
}
198201
}
199202

@@ -241,6 +244,11 @@ impl FromToProto for mt::principal::GrantObject {
241244
warehouse: w.clone(),
242245
},
243246
)),
247+
mt::principal::GrantObject::Connection(c) => Some(
248+
pb::grant_object::Object::Connection(pb::grant_object::GrantConnectionObject {
249+
connection: c.clone(),
250+
}),
251+
),
244252
};
245253
Ok(pb::GrantObject {
246254
ver: VER,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
165165
(133, "2025-06-25: Add: Add new StageFileCompression Zip"),
166166
(134, "2025-06-27: Add: SequenceMeta.storage_version"),
167167
(135, "2025-07-16: Add: UDFServer.immutable, UDFScript.immutable"),
168+
(136, "2025-07-18: Add: GrantConnectionObject and UserPrivilegeType AccessConnection, AccessConnection"),
168169
// Dear developer:
169170
// If you're gonna add a new metadata version, you'll have to add a test for it.
170171
// 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
@@ -127,3 +127,4 @@ mod v132_remove_sequence_meta_start;
127127
mod v133_stage_file_compression;
128128
mod v134_add_sequence_meta_storage_version;
129129
mod v135_udf_immutable;
130+
mod v136_add_grant_object_connection;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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_v136_grant_object() -> anyhow::Result<()> {
40+
let role_info_v136 = vec![
41+
10, 2, 114, 49, 18, 214, 1, 10, 23, 10, 9, 10, 0, 160, 6, 136, 1, 168, 6, 24, 16, 128, 128,
42+
128, 2, 160, 6, 136, 1, 168, 6, 24, 10, 27, 10, 13, 74, 4, 10, 2, 99, 49, 160, 6, 136, 1,
43+
168, 6, 24, 16, 128, 128, 128, 4, 160, 6, 136, 1, 168, 6, 24, 10, 33, 10, 22, 18, 13, 10,
44+
7, 100, 101, 102, 97, 117, 108, 116, 18, 2, 100, 98, 160, 6, 136, 1, 168, 6, 24, 16, 2,
45+
160, 6, 136, 1, 168, 6, 24, 10, 37, 10, 26, 26, 17, 10, 7, 100, 101, 102, 97, 117, 108,
46+
116, 18, 2, 100, 98, 26, 2, 116, 98, 160, 6, 136, 1, 168, 6, 24, 16, 2, 160, 6, 136, 1,
47+
168, 6, 24, 10, 24, 10, 13, 34, 4, 10, 2, 102, 49, 160, 6, 136, 1, 168, 6, 24, 16, 1, 160,
48+
6, 136, 1, 168, 6, 24, 10, 26, 10, 13, 42, 4, 10, 2, 115, 49, 160, 6, 136, 1, 168, 6, 24,
49+
16, 128, 128, 32, 160, 6, 136, 1, 168, 6, 24, 10, 23, 10, 9, 10, 0, 160, 6, 136, 1, 168, 6,
50+
24, 16, 254, 255, 191, 7, 160, 6, 136, 1, 168, 6, 24, 160, 6, 136, 1, 168, 6, 24, 26, 23,
51+
49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, 48, 48, 32, 85, 84, 67,
52+
34, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, 48, 48, 32, 85,
53+
84, 67, 160, 6, 136, 1, 168, 6, 24,
54+
];
55+
let want = || mt::principal::RoleInfo {
56+
name: "r1".to_string(),
57+
grants: UserGrantSet::new(
58+
vec![
59+
mt::principal::GrantEntry::new(
60+
mt::principal::GrantObject::Global,
61+
make_bitflags!(UserPrivilegeType::{CreateConnection}),
62+
),
63+
mt::principal::GrantEntry::new(
64+
mt::principal::GrantObject::Connection("c1".to_string()),
65+
make_bitflags!(UserPrivilegeType::{AccessConnection}),
66+
),
67+
mt::principal::GrantEntry::new(
68+
mt::principal::GrantObject::Database("default".to_string(), "db".to_string()),
69+
make_bitflags!(UserPrivilegeType::{Create}),
70+
),
71+
mt::principal::GrantEntry::new(
72+
mt::principal::GrantObject::Table(
73+
"default".to_string(),
74+
"db".to_string(),
75+
"tb".to_string(),
76+
),
77+
make_bitflags!(UserPrivilegeType::{Create}),
78+
),
79+
mt::principal::GrantEntry::new(
80+
mt::principal::GrantObject::UDF("f1".to_string()),
81+
make_bitflags!(UserPrivilegeType::{Usage}),
82+
),
83+
mt::principal::GrantEntry::new(
84+
mt::principal::GrantObject::Stage("s1".to_string()),
85+
make_bitflags!(UserPrivilegeType::{Write}),
86+
),
87+
// test new global privilege CreateConneciton, AccessConnection
88+
mt::principal::GrantEntry::new(
89+
mt::principal::GrantObject::Global,
90+
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 }),
91+
),
92+
],
93+
HashSet::new(),
94+
),
95+
created_on: DateTime::<Utc>::default(),
96+
update_on: DateTime::<Utc>::default(),
97+
};
98+
99+
common::test_pb_from_to(func_name!(), want())?;
100+
common::test_load_old(func_name!(), role_info_v136.as_slice(), 136, want())?;
101+
102+
Ok(())
103+
}
104+
105+
#[test]
106+
fn test_decode_v136_ownership() -> anyhow::Result<()> {
107+
let ownership_info_v136 = vec![
108+
10, 2, 114, 49, 18, 13, 50, 4, 10, 2, 99, 49, 160, 6, 136, 1, 168, 6, 24, 160, 6, 136, 1,
109+
168, 6, 24,
110+
];
111+
112+
let want = || mt::principal::OwnershipInfo {
113+
role: "r1".to_string(),
114+
object: OwnershipObject::Connection {
115+
name: "c1".to_string(),
116+
},
117+
};
118+
common::test_pb_from_to(func_name!(), want())?;
119+
common::test_load_old(func_name!(), ownership_info_v136.as_slice(), 136, want())?;
120+
121+
Ok(())
122+
}

src/meta/protos/proto/ownership.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,16 @@ message OwnershipObject {
5050
string id = 1;
5151
}
5252

53+
message OwnershipConnectionObject {
54+
string connection = 1;
55+
}
56+
5357
oneof object {
5458
OwnershipDatabaseObject database = 1;
5559
OwnershipTableObject table = 2;
5660
OwnershipUdfObject udf = 3;
5761
OwnershipStageObject stage = 4;
5862
OwnershipWarehouseObject warehouse = 5;
63+
OwnershipConnectionObject connection = 6;
5964
}
6065
}

src/meta/protos/proto/user.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ message GrantObject {
8181
string warehouse = 1;
8282
}
8383

84+
message GrantConnectionObject {
85+
string connection = 1;
86+
}
87+
8488
oneof object {
8589
GrantGlobalObject global = 1;
8690
GrantDatabaseObject database = 2;
@@ -90,6 +94,7 @@ message GrantObject {
9094
GrantDatabaseIdObject databasebyid = 6;
9195
GrantTableIdObject tablebyid = 7;
9296
GrantWarehouseObject warehouse = 8;
97+
GrantConnectionObject connection = 9;
9398
}
9499
}
95100

0 commit comments

Comments
 (0)