Skip to content

Commit b0835cc

Browse files
authored
feat: object warehouse support rbac (#17262)
* feat: object warehouse support rbac * add WAREHOUSE as ident note: only support_forward_warehouse_request will apply warehouse rbac. Now only support SystemResourcesManagement * add new privilege type forward test * add new ident parse func: grant_ident * 1. Normalize the code 2. grant ownership on warehouse object only supported for warehouses managed by the system * fix warehouse uid modify to id * add CreateWarehouse in available_privileges_on_global
1 parent d201732 commit b0835cc

34 files changed

+597
-104
lines changed

Diff for: src/meta/app/src/principal/ownership_object.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ pub enum OwnershipObject {
5252
UDF {
5353
name: String,
5454
},
55+
56+
Warehouse {
57+
id: String,
58+
},
5559
}
5660

5761
impl OwnershipObject {
@@ -80,6 +84,7 @@ impl fmt::Display for OwnershipObject {
8084
}
8185
OwnershipObject::UDF { name } => write!(f, "UDF {name}"),
8286
OwnershipObject::Stage { name } => write!(f, "STAGE {name}"),
87+
OwnershipObject::Warehouse { id } => write!(f, "Warehouse {id}"),
8388
}
8489
}
8590
}
@@ -119,6 +124,7 @@ impl KeyCodec for OwnershipObject {
119124
}
120125
OwnershipObject::Stage { name } => b.push_raw("stage-by-name").push_str(name),
121126
OwnershipObject::UDF { name } => b.push_raw("udf-by-name").push_str(name),
127+
OwnershipObject::Warehouse { id } => b.push_raw("warehouse-by-id").push_str(id),
122128
}
123129
}
124130

@@ -165,9 +171,13 @@ impl KeyCodec for OwnershipObject {
165171
let name = p.next_str()?;
166172
Ok(OwnershipObject::UDF { name })
167173
}
174+
"warehouse-by-id" => {
175+
let id = p.next_str()?;
176+
Ok(OwnershipObject::Warehouse { id })
177+
}
168178
_ => Err(kvapi::KeyError::InvalidSegment {
169179
i: p.index(),
170-
expect: "database-by-id|database-by-catalog-id|table-by-id|table-by-catalog-id|stage-by-name|udf-by-name"
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"
171181
.to_string(),
172182
got: q.to_string(),
173183
}),

Diff for: src/meta/app/src/principal/tenant_ownership_object_ident.rs

+16
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,22 @@ mod tests {
254254
let parsed = TenantOwnershipObjectIdent::from_str_key(&key).unwrap();
255255
assert_eq!(role_grantee, parsed);
256256
}
257+
258+
// warehouse
259+
{
260+
let role_grantee = TenantOwnershipObjectIdent::new_unchecked(
261+
Tenant::new_literal("test"),
262+
OwnershipObject::Warehouse {
263+
id: "n87s".to_string(),
264+
},
265+
);
266+
267+
let key = role_grantee.to_string_key();
268+
assert_eq!("__fd_object_owners/test/warehouse-by-id/n87s", key);
269+
270+
let parsed = TenantOwnershipObjectIdent::from_str_key(&key).unwrap();
271+
assert_eq!(role_grantee, parsed);
272+
}
257273
}
258274

259275
#[test]

Diff for: src/meta/app/src/principal/user_grant.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ impl GrantObject {
111111
GrantObject::Stage(_) => {
112112
UserPrivilegeSet::available_privileges_on_stage(available_ownership)
113113
}
114-
GrantObject::Warehouse(_) => UserPrivilegeSet::available_privileges_on_warehouse(),
114+
GrantObject::Warehouse(_) => {
115+
UserPrivilegeSet::available_privileges_on_warehouse(available_ownership)
116+
}
115117
}
116118
}
117119

Diff for: src/meta/app/src/principal/user_privilege.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ pub enum UserPrivilegeType {
7676
Write = 1 << 19,
7777
// Privilege to Create database
7878
CreateDatabase = 1 << 20,
79+
// Privilege to Create warehouse
80+
CreateWarehouse = 1 << 21,
7981
// Discard Privilege Type
8082
Set = 1 << 4,
8183
}
@@ -102,6 +104,7 @@ const ALL_PRIVILEGES: BitFlags<UserPrivilegeType> = make_bitflags!(
102104
| Read
103105
| Write
104106
| CreateDatabase
107+
| CreateWarehouse
105108
}
106109
);
107110

@@ -129,6 +132,7 @@ impl Display for UserPrivilegeType {
129132
UserPrivilegeType::Read => "Read",
130133
UserPrivilegeType::Write => "Write",
131134
UserPrivilegeType::CreateDatabase => "CREATE DATABASE",
135+
UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE",
132136
})
133137
}
134138
}
@@ -166,6 +170,9 @@ impl From<databend_common_ast::ast::UserPrivilegeType> for UserPrivilegeType {
166170
databend_common_ast::ast::UserPrivilegeType::CreateDatabase => {
167171
UserPrivilegeType::CreateDatabase
168172
}
173+
databend_common_ast::ast::UserPrivilegeType::CreateWarehouse => {
174+
UserPrivilegeType::CreateWarehouse
175+
}
169176
databend_common_ast::ast::UserPrivilegeType::Set => UserPrivilegeType::Set,
170177
}
171178
}
@@ -199,8 +206,8 @@ impl UserPrivilegeSet {
199206
let database_privs = Self::available_privileges_on_database(false);
200207
let stage_privs_without_ownership = Self::available_privileges_on_stage(false);
201208
let udf_privs_without_ownership = Self::available_privileges_on_udf(false);
202-
let wh_privs_without_ownership = Self::available_privileges_on_warehouse();
203-
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask });
209+
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 });
204211
(database_privs.privileges
205212
| privs
206213
| stage_privs_without_ownership.privileges
@@ -234,8 +241,12 @@ impl UserPrivilegeSet {
234241
}
235242
}
236243

237-
pub fn available_privileges_on_warehouse() -> Self {
238-
make_bitflags!(UserPrivilegeType::{ Usage }).into()
244+
pub fn available_privileges_on_warehouse(available_ownership: bool) -> Self {
245+
if available_ownership {
246+
make_bitflags!(UserPrivilegeType::{ Usage | Ownership }).into()
247+
} else {
248+
make_bitflags!(UserPrivilegeType::{ Usage }).into()
249+
}
239250
}
240251

241252
pub fn available_privileges_on_udf(available_ownership: bool) -> Self {

Diff for: src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs

+8
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ impl FromToProto for mt::principal::OwnershipObject {
8888
pb::ownership_object::Object::Stage(pb::ownership_object::OwnershipStageObject {
8989
stage,
9090
}) => Ok(mt::principal::OwnershipObject::Stage { name: stage }),
91+
pb::ownership_object::Object::Warehouse(
92+
pb::ownership_object::OwnershipWarehouseObject { id },
93+
) => Ok(mt::principal::OwnershipObject::Warehouse { id }),
9194
}
9295
}
9396

@@ -123,6 +126,11 @@ impl FromToProto for mt::principal::OwnershipObject {
123126
stage: name.clone(),
124127
}),
125128
),
129+
mt::principal::OwnershipObject::Warehouse { id } => {
130+
Some(pb::ownership_object::Object::Warehouse(
131+
pb::ownership_object::OwnershipWarehouseObject { id: id.clone() },
132+
))
133+
}
126134
};
127135
Ok(pb::OwnershipObject {
128136
ver: VER,

Diff for: src/meta/proto-conv/src/util.rs

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
149149
(117, "2025-01-21: Add: config.proto: add disable_list_batch in WebhdfsConfig"),
150150
(118, "2025-01-22: Add: config.proto: add user_name in WebhdfsConfig"),
151151
(119, "2025-01-25: Add: virtual_column add alias_names and auto_generated field"),
152+
(120, "2025-02-11: Add: Add new UserPrivilege CreateWarehouse and new OwnershipObject::Warehouse"),
152153
// Dear developer:
153154
// If you're gonna add a new metadata version, you'll have to add a test for it.
154155
// You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)

Diff for: src/meta/proto-conv/tests/it/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,4 @@ mod v116_marked_deleted_index_meta;
117117
mod v117_webhdfs_add_disable_list_batch;
118118
mod v118_webhdfs_add_user_name;
119119
mod v119_virtual_column;
120+
mod v120_warehouse_ownershipobject;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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_v120_grant_object() -> anyhow::Result<()> {
40+
let role_info_v120 = vec![
41+
10, 2, 114, 49, 18, 172, 1, 10, 21, 10, 8, 10, 0, 160, 6, 120, 168, 6, 24, 16, 128, 128,
42+
128, 1, 160, 6, 120, 168, 6, 24, 10, 31, 10, 21, 18, 13, 10, 7, 100, 101, 102, 97, 117,
43+
108, 116, 18, 2, 100, 98, 160, 6, 120, 168, 6, 24, 16, 2, 160, 6, 120, 168, 6, 24, 10, 35,
44+
10, 25, 26, 17, 10, 7, 100, 101, 102, 97, 117, 108, 116, 18, 2, 100, 98, 26, 2, 116, 98,
45+
160, 6, 120, 168, 6, 24, 16, 2, 160, 6, 120, 168, 6, 24, 10, 22, 10, 12, 34, 4, 10, 2, 102,
46+
49, 160, 6, 120, 168, 6, 24, 16, 1, 160, 6, 120, 168, 6, 24, 10, 24, 10, 12, 42, 4, 10, 2,
47+
115, 49, 160, 6, 120, 168, 6, 24, 16, 128, 128, 32, 160, 6, 120, 168, 6, 24, 10, 21, 10, 8,
48+
10, 0, 160, 6, 120, 168, 6, 24, 16, 254, 255, 191, 1, 160, 6, 120, 168, 6, 24, 160, 6, 120,
49+
168, 6, 24, 26, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, 48,
50+
48, 32, 85, 84, 67, 34, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48,
51+
58, 48, 48, 32, 85, 84, 67, 160, 6, 120, 168, 6, 24,
52+
];
53+
let want = || mt::principal::RoleInfo {
54+
name: "r1".to_string(),
55+
grants: UserGrantSet::new(
56+
vec![
57+
mt::principal::GrantEntry::new(
58+
mt::principal::GrantObject::Global,
59+
make_bitflags!(UserPrivilegeType::{CreateWarehouse}),
60+
),
61+
mt::principal::GrantEntry::new(
62+
mt::principal::GrantObject::Database("default".to_string(), "db".to_string()),
63+
make_bitflags!(UserPrivilegeType::{Create}),
64+
),
65+
mt::principal::GrantEntry::new(
66+
mt::principal::GrantObject::Table(
67+
"default".to_string(),
68+
"db".to_string(),
69+
"tb".to_string(),
70+
),
71+
make_bitflags!(UserPrivilegeType::{Create}),
72+
),
73+
mt::principal::GrantEntry::new(
74+
mt::principal::GrantObject::UDF("f1".to_string()),
75+
make_bitflags!(UserPrivilegeType::{Usage}),
76+
),
77+
mt::principal::GrantEntry::new(
78+
mt::principal::GrantObject::Stage("s1".to_string()),
79+
make_bitflags!(UserPrivilegeType::{Write}),
80+
),
81+
// test new global privilege CreateWarehouse
82+
mt::principal::GrantEntry::new(
83+
mt::principal::GrantObject::Global,
84+
make_bitflags!(UserPrivilegeType::{Create | Select | Insert | Update | Delete | Drop | Alter | Super | CreateUser | DropUser | CreateRole | DropRole | Grant | CreateStage | Set | CreateDataMask | Ownership | Read | Write | CreateWarehouse }),
85+
),
86+
],
87+
HashSet::new(),
88+
),
89+
created_on: DateTime::<Utc>::default(),
90+
update_on: DateTime::<Utc>::default(),
91+
};
92+
93+
common::test_pb_from_to(func_name!(), want())?;
94+
common::test_load_old(func_name!(), role_info_v120.as_slice(), 120, want())?;
95+
96+
Ok(())
97+
}
98+
99+
#[test]
100+
fn test_decode_v120_ownership() -> anyhow::Result<()> {
101+
let ownership_info_v120 = vec![
102+
10, 2, 114, 49, 18, 19, 42, 11, 10, 9, 97, 117, 110, 105, 113, 117, 101, 105, 100, 160, 6,
103+
120, 168, 6, 24, 160, 6, 120, 168, 6, 24,
104+
];
105+
106+
let want = || mt::principal::OwnershipInfo {
107+
role: "r1".to_string(),
108+
object: OwnershipObject::Warehouse {
109+
id: "auniqueid".to_string(),
110+
},
111+
};
112+
common::test_pb_from_to(func_name!(), want())?;
113+
common::test_load_old(func_name!(), ownership_info_v120.as_slice(), 120, want())?;
114+
115+
Ok(())
116+
}

Diff for: src/meta/protos/proto/ownership.proto

+5
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ message OwnershipObject {
4646
string stage = 1;
4747
}
4848

49+
message OwnershipWarehouseObject {
50+
string id = 1;
51+
}
52+
4953
oneof object {
5054
OwnershipDatabaseObject database = 1;
5155
OwnershipTableObject table = 2;
5256
OwnershipUdfObject udf = 3;
5357
OwnershipStageObject stage = 4;
58+
OwnershipWarehouseObject warehouse = 5;
5459
}
5560
}

Diff for: src/query/ast/src/ast/statements/principal.rs

+3
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ pub enum UserPrivilegeType {
150150
Write,
151151
// Privilege to Create database
152152
CreateDatabase,
153+
// Privilege to Create warehouse
154+
CreateWarehouse,
153155
// Discard Privilege Type
154156
Set,
155157
}
@@ -178,6 +180,7 @@ impl Display for UserPrivilegeType {
178180
UserPrivilegeType::Read => "Read",
179181
UserPrivilegeType::Write => "Write",
180182
UserPrivilegeType::CreateDatabase => "CREATE DATABASE",
183+
UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE",
181184
})
182185
}
183186
}

Diff for: src/query/ast/src/ast/statements/user.rs

+2
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ pub enum GrantObjectName {
147147
Table(Option<String>, String),
148148
UDF(String),
149149
Stage(String),
150+
Warehouse(String),
150151
}
151152

152153
impl Display for GrantObjectName {
@@ -164,6 +165,7 @@ impl Display for GrantObjectName {
164165
}
165166
GrantObjectName::UDF(udf) => write!(f, " UDF {udf}"),
166167
GrantObjectName::Stage(stage) => write!(f, " STAGE {stage}"),
168+
GrantObjectName::Warehouse(w) => write!(f, " WAREHOUSE {w}"),
167169
}
168170
}
169171
}

Diff for: src/query/ast/src/parser/common.rs

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ pub fn ident(i: Input) -> IResult<Identifier> {
8888
non_reserved_identifier(|token| token.is_reserved_ident(false))(i)
8989
}
9090

91+
pub fn grant_ident(i: Input) -> IResult<Identifier> {
92+
non_reserved_identifier(|token| token.is_grant_reserved_ident(false, true))(i)
93+
}
94+
9195
pub fn plain_ident(i: Input) -> IResult<Identifier> {
9296
plain_identifier(|token| token.is_reserved_ident(false))(i)
9397
}

Diff for: src/query/ast/src/parser/stage.rs

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ use crate::parser::input::Input;
3030
use crate::parser::token::*;
3131
use crate::parser::ErrorKind;
3232

33+
pub fn parameter_to_grant_string(i: Input) -> IResult<String> {
34+
let ident_to_string = |i| map_res(grant_ident, |ident| Ok(ident.name))(i);
35+
let u64_to_string = |i| map(literal_u64, |v| v.to_string())(i);
36+
let boolean_to_string = |i| map(literal_bool, |v| v.to_string())(i);
37+
38+
rule!(
39+
#literal_string
40+
| #ident_to_string
41+
| #u64_to_string
42+
| #boolean_to_string
43+
)(i)
44+
}
45+
3346
pub fn parameter_to_string(i: Input) -> IResult<String> {
3447
let ident_to_string = |i| map_res(ident, |ident| Ok(ident.name))(i);
3548
let u64_to_string = |i| map(literal_u64, |v| v.to_string())(i);

0 commit comments

Comments
 (0)