Skip to content

Commit

Permalink
feat(rbac): add GrantObject::Warehouse (#17029)
Browse files Browse the repository at this point in the history
Warehouse permissions are granted to roles, and each warehouse can only be assigned to one role.
  • Loading branch information
TCeason authored Dec 11, 2024
1 parent c9e399b commit 042f6f7
Show file tree
Hide file tree
Showing 31 changed files with 422 additions and 55 deletions.
1 change: 1 addition & 0 deletions src/common/exception/src/exception_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ build_exceptions! {
// Cluster error codes.
ClusterUnknownNode(2401),
ClusterNodeAlreadyExists(2402),
InvalidWarehouse(2403),

// Stage error codes.
UnknownStage(2501),
Expand Down
11 changes: 9 additions & 2 deletions src/meta/app/src/principal/user_grant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub enum GrantObject {
TableById(String, u64, u64),
UDF(String),
Stage(String),
Warehouse(String),
}

impl GrantObject {
Expand Down Expand Up @@ -89,6 +90,7 @@ impl GrantObject {
(GrantObject::Table(_, _, _), _) => false,
(GrantObject::Stage(lstage), GrantObject::Stage(rstage)) => lstage == rstage,
(GrantObject::UDF(udf), GrantObject::UDF(rudf)) => udf == rudf,
(GrantObject::Warehouse(w), GrantObject::Warehouse(rw)) => w == rw,
_ => false,
}
}
Expand All @@ -109,12 +111,16 @@ impl GrantObject {
GrantObject::Stage(_) => {
UserPrivilegeSet::available_privileges_on_stage(available_ownership)
}
GrantObject::Warehouse(_) => UserPrivilegeSet::available_privileges_on_warehouse(),
}
}

pub fn catalog(&self) -> Option<String> {
match self {
GrantObject::Global | GrantObject::Stage(_) | GrantObject::UDF(_) => None,
GrantObject::Global
| GrantObject::Stage(_)
| GrantObject::UDF(_)
| GrantObject::Warehouse(_) => None,
GrantObject::Database(cat, _) | GrantObject::DatabaseById(cat, _) => Some(cat.clone()),
GrantObject::Table(cat, _, _) | GrantObject::TableById(cat, _, _) => Some(cat.clone()),
}
Expand All @@ -135,6 +141,7 @@ impl fmt::Display for GrantObject {
}
GrantObject::UDF(udf) => write!(f, "UDF {udf}"),
GrantObject::Stage(stage) => write!(f, "STAGE {stage}"),
GrantObject::Warehouse(w) => write!(f, "WAREHOUSE {w}"),
}
}
}
Expand Down Expand Up @@ -179,7 +186,7 @@ impl GrantEntry {
}

impl fmt::Display for GrantEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let privileges: UserPrivilegeSet = self.privileges.into();
let privileges_str = if self.has_all_available_privileges() {
"ALL".to_string()
Expand Down
8 changes: 7 additions & 1 deletion src/meta/app/src/principal/user_privilege.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use enumflags2::BitFlags;
num_derive::FromPrimitive,
)]
pub enum UserPrivilegeType {
// UsagePrivilege is a synonym for “no privileges”, if object is udf, means can use this udf
// UsagePrivilege is a synonym for “no privileges”, if object is udf/warehouse, means can use this udf/warehouse
Usage = 1 << 0,
// Privilege to select rows from tables in a database.
Select = 1 << 2,
Expand Down Expand Up @@ -199,10 +199,12 @@ impl UserPrivilegeSet {
let database_privs = Self::available_privileges_on_database(false);
let stage_privs_without_ownership = Self::available_privileges_on_stage(false);
let udf_privs_without_ownership = Self::available_privileges_on_udf(false);
let wh_privs_without_ownership = Self::available_privileges_on_warehouse();
let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask });
(database_privs.privileges
| privs
| stage_privs_without_ownership.privileges
| wh_privs_without_ownership.privileges
| udf_privs_without_ownership.privileges)
.into()
}
Expand Down Expand Up @@ -232,6 +234,10 @@ impl UserPrivilegeSet {
}
}

pub fn available_privileges_on_warehouse() -> Self {
make_bitflags!(UserPrivilegeType::{ Usage }).into()
}

pub fn available_privileges_on_udf(available_ownership: bool) -> Self {
if available_ownership {
make_bitflags!(UserPrivilegeType::{ Usage | Ownership }).into()
Expand Down
8 changes: 8 additions & 0 deletions src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ impl FromToProto for mt::principal::GrantObject {
pb::grant_object::Object::Stage(pb::grant_object::GrantStageObject { stage }) => {
Ok(mt::principal::GrantObject::Stage(stage))
}
pb::grant_object::Object::Warehouse(pb::grant_object::GrantWarehouseObject {
warehouse,
}) => Ok(mt::principal::GrantObject::Warehouse(warehouse)),
}
}

Expand Down Expand Up @@ -235,6 +238,11 @@ impl FromToProto for mt::principal::GrantObject {
stage: stage.clone(),
},
)),
mt::principal::GrantObject::Warehouse(w) => Some(pb::grant_object::Object::Warehouse(
pb::grant_object::GrantWarehouseObject {
warehouse: w.clone(),
},
)),
};
Ok(pb::GrantObject {
ver: VER,
Expand Down
1 change: 1 addition & 0 deletions src/meta/proto-conv/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
(110, "2024-09-18: Add: database.proto: DatabaseMeta.gc_in_progress"),
(111, "2024-11-13: Add: Enable AWS Glue as an Apache Iceberg type when creating a catalog."),
(112, "2024-11-28: Add: virtual_column add data_types field"),
(113, "2024-12-10: Add: GrantWarehouseObject"),
// Dear developer:
// If you're gonna add a new metadata version, you'll have to add a test for it.
// You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)
Expand Down
1 change: 1 addition & 0 deletions src/meta/proto-conv/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ mod v109_procedure_with_args;
mod v110_database_meta_gc_in_progress;
mod v111_add_glue_as_iceberg_catalog_option;
mod v112_virtual_column;
mod v113_warehouse_grantobject;
40 changes: 40 additions & 0 deletions src/meta/proto-conv/tests/it/v113_warehouse_grantobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2023 Datafuse Labs.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use databend_common_meta_app as mt;
use fastrace::func_name;

use crate::common;

// These bytes are built when a new version in introduced,
// and are kept for backward compatibility test.
//
// *************************************************************
// * These messages should never be updated, *
// * only be added when a new version is added, *
// * or be removed when an old version is no longer supported. *
// *************************************************************
//

#[test]
fn test_decode_v113_grant_object() -> anyhow::Result<()> {
let grant_object_v113 = vec![66, 4, 10, 2, 119, 116, 160, 6, 113, 168, 6, 24];

let want = || mt::principal::GrantObject::Warehouse("wt".to_string());

common::test_pb_from_to(func_name!(), want())?;
common::test_load_old(func_name!(), grant_object_v113.as_slice(), 113, want())?;

Ok(())
}
5 changes: 5 additions & 0 deletions src/meta/protos/proto/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ message GrantObject {
string stage = 1;
}

message GrantWarehouseObject {
string warehouse = 1;
}

oneof object {
GrantGlobalObject global = 1;
GrantDatabaseObject database = 2;
Expand All @@ -85,6 +89,7 @@ message GrantObject {
GrantStageObject stage = 5;
GrantDatabaseIdObject databasebyid = 6;
GrantTableIdObject tablebyid = 7;
GrantWarehouseObject warehouse = 8;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/query/ast/src/ast/statements/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub enum AccountMgrLevel {
Table(Option<String>, String),
UDF(String),
Stage(String),
Warehouse(String),
}

impl Display for AccountMgrLevel {
Expand All @@ -242,6 +243,7 @@ impl Display for AccountMgrLevel {
}
AccountMgrLevel::UDF(udf) => write!(f, " UDF {udf}"),
AccountMgrLevel::Stage(stage) => write!(f, " STAGE {stage}"),
AccountMgrLevel::Warehouse(w) => write!(f, " WAREHOUSE {w}"),
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/query/ast/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3084,9 +3084,31 @@ pub fn grant_source(i: Input) -> IResult<AccountMgrSource> {
},
);

let warehouse_privs = map(
rule! {
USAGE ~ ON ~ WAREHOUSE ~ #ident
},
|(_, _, _, w)| AccountMgrSource::Privs {
privileges: vec![UserPrivilegeType::Usage],
level: AccountMgrLevel::Warehouse(w.to_string()),
},
);

let warehouse_all_privs = map(
rule! {
ALL ~ PRIVILEGES? ~ ON ~ WAREHOUSE ~ #ident
},
|(_, _, _, _, w)| AccountMgrSource::Privs {
privileges: vec![UserPrivilegeType::Usage],
level: AccountMgrLevel::Warehouse(w.to_string()),
},
);

rule!(
#role : "ROLE <role_name>"
| #warehouse_all_privs: "ALL [ PRIVILEGES ] ON WAREHOUSE <warehouse_name>"
| #udf_privs: "USAGE ON UDF <udf_name>"
| #warehouse_privs: "USAGE ON WAREHOUSE <warehouse_name>"
| #privs : "<privileges> ON <privileges_level>"
| #stage_privs : "<stage_privileges> ON STAGE <stage_name>"
| #udf_all_privs: "ALL [ PRIVILEGES ] ON UDF <udf_name>"
Expand Down Expand Up @@ -3229,11 +3251,16 @@ pub fn grant_all_level(i: Input) -> IResult<AccountMgrLevel> {
let stage = map(rule! { STAGE ~ #ident}, |(_, stage_name)| {
AccountMgrLevel::Stage(stage_name.to_string())
});

let warehouse = map(rule! { WAREHOUSE ~ #ident}, |(_, w)| {
AccountMgrLevel::Warehouse(w.to_string())
});
rule!(
#global : "*.*"
| #db : "<database>.*"
| #table : "<database>.<table>"
| #stage : "STAGE <stage_name>"
| #warehouse : "WAREHOUSE <warehouse_name>"
)(i)
}

Expand Down
4 changes: 4 additions & 0 deletions src/query/ast/tests/it/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ fn test_statement() {
r#"GRANT usage ON UDF a TO 'test-grant';"#,
r#"REVOKE usage ON UDF a FROM 'test-grant';"#,
r#"REVOKE all ON UDF a FROM 'test-grant';"#,
r#"GRANT all ON warehouse a TO role 'test-grant';"#,
r#"GRANT usage ON warehouse a TO role 'test-grant';"#,
r#"REVOKE usage ON warehouse a FROM role 'test-grant';"#,
r#"REVOKE all ON warehouse a FROM role 'test-grant';"#,
r#"SHOW GRANTS ON TABLE db1.tb1;"#,
r#"SHOW GRANTS ON DATABASE db;"#,
r#"UPDATE db1.tb1 set a = a + 1, b = 2 WHERE c > 3;"#,
Expand Down
88 changes: 88 additions & 0 deletions src/query/ast/tests/it/testdata/stmt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16075,6 +16075,94 @@ Revoke(
)


---------- Input ----------
GRANT all ON warehouse a TO role 'test-grant';
---------- Output ---------
GRANT USAGE ON WAREHOUSE a TO ROLE 'test-grant'
---------- AST ------------
Grant(
GrantStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
GRANT usage ON warehouse a TO role 'test-grant';
---------- Output ---------
GRANT USAGE ON WAREHOUSE a TO ROLE 'test-grant'
---------- AST ------------
Grant(
GrantStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
REVOKE usage ON warehouse a FROM role 'test-grant';
---------- Output ---------
REVOKE USAGE ON WAREHOUSE a FROM ROLE 'test-grant'
---------- AST ------------
Revoke(
RevokeStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
REVOKE all ON warehouse a FROM role 'test-grant';
---------- Output ---------
REVOKE USAGE ON WAREHOUSE a FROM ROLE 'test-grant'
---------- AST ------------
Revoke(
RevokeStmt {
source: Privs {
privileges: [
Usage,
],
level: Warehouse(
"a",
),
},
principal: Role(
"test-grant",
),
},
)


---------- Input ----------
SHOW GRANTS ON TABLE db1.tb1;
---------- Output ---------
Expand Down
9 changes: 5 additions & 4 deletions src/query/service/src/interpreters/access/privilege_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl PrivilegeAccess {
GrantObject::UDF(name) => OwnershipObject::UDF {
name: name.to_string(),
},
GrantObject::Global => return Ok(None),
GrantObject::Global | GrantObject::Warehouse(_) => return Ok(None),
};

Ok(Some(object))
Expand Down Expand Up @@ -225,7 +225,7 @@ impl PrivilegeAccess {

return Err(ErrorCode::PermissionDenied(format!(
"Permission denied: privilege [{:?}] is required on '{}'.'{}'.* for user {} with roles [{}]. \
Note: Please ensure that your current role have the appropriate permissions to create a new Database|Table|UDF|Stage.",
Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage.",
privileges,
catalog_name,
db_name,
Expand Down Expand Up @@ -441,7 +441,7 @@ impl PrivilegeAccess {
| GrantObject::UDF(_)
| GrantObject::Stage(_)
| GrantObject::TableById(_, _, _) => true,
GrantObject::Global => false,
GrantObject::Global | GrantObject::Warehouse(_) => false,
};

if verify_ownership
Expand Down Expand Up @@ -490,11 +490,12 @@ impl PrivilegeAccess {
GrantObject::DatabaseById(_, _) => Err(ErrorCode::PermissionDenied("")),
GrantObject::Global
| GrantObject::UDF(_)
| GrantObject::Warehouse(_)
| GrantObject::Stage(_)
| GrantObject::Database(_, _)
| GrantObject::Table(_, _, _) => Err(ErrorCode::PermissionDenied(format!(
"Permission denied: privilege [{:?}] is required on {} for user {} with roles [{}]. \
Note: Please ensure that your current role have the appropriate permissions to create a new Database|Table|UDF|Stage.",
Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage.",
privilege,
grant_object,
&current_user.identity().display(),
Expand Down
Loading

0 comments on commit 042f6f7

Please sign in to comment.