diff --git a/src/meta/app/src/principal/ownership_object.rs b/src/meta/app/src/principal/ownership_object.rs index d3d55858bcbe5..6b2bf44d2db6e 100644 --- a/src/meta/app/src/principal/ownership_object.rs +++ b/src/meta/app/src/principal/ownership_object.rs @@ -56,6 +56,10 @@ pub enum OwnershipObject { Warehouse { id: String, }, + + Connection { + name: String, + }, } impl OwnershipObject { @@ -84,7 +88,8 @@ impl fmt::Display for OwnershipObject { } OwnershipObject::UDF { name } => write!(f, "UDF {name}"), OwnershipObject::Stage { name } => write!(f, "STAGE {name}"), - OwnershipObject::Warehouse { id } => write!(f, "Warehouse {id}"), + OwnershipObject::Warehouse { id } => write!(f, "WAREHOUSE {id}"), + OwnershipObject::Connection { name } => write!(f, "CONNECTION {name}"), } } } @@ -125,6 +130,7 @@ impl KeyCodec for OwnershipObject { OwnershipObject::Stage { name } => b.push_raw("stage-by-name").push_str(name), OwnershipObject::UDF { name } => b.push_raw("udf-by-name").push_str(name), OwnershipObject::Warehouse { id } => b.push_raw("warehouse-by-id").push_str(id), + OwnershipObject::Connection { name } => b.push_raw("connection-by-name").push_str(name), } } @@ -175,9 +181,13 @@ impl KeyCodec for OwnershipObject { let id = p.next_str()?; Ok(OwnershipObject::Warehouse { id }) } + "connection-by-name" => { + let name = p.next_str()?; + Ok(OwnershipObject::Connection { name }) + } _ => Err(kvapi::KeyError::InvalidSegment { i: p.index(), - expect: "database-by-id|database-by-catalog-id|table-by-id|table-by-catalog-id|stage-by-name|udf-by-name|warehouse-by-id" + 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" .to_string(), got: q.to_string(), }), diff --git a/src/meta/app/src/principal/user_grant.rs b/src/meta/app/src/principal/user_grant.rs index 3e3bc659fa022..22e4c915f1f42 100644 --- a/src/meta/app/src/principal/user_grant.rs +++ b/src/meta/app/src/principal/user_grant.rs @@ -60,6 +60,7 @@ pub enum GrantObject { UDF(String), Stage(String), Warehouse(String), + Connection(String), } impl GrantObject { @@ -93,6 +94,7 @@ impl GrantObject { (GrantObject::Stage(lstage), GrantObject::Stage(rstage)) => lstage == rstage, (GrantObject::UDF(udf), GrantObject::UDF(rudf)) => udf == rudf, (GrantObject::Warehouse(w), GrantObject::Warehouse(rw)) => w == rw, + (GrantObject::Connection(c), GrantObject::Connection(rc)) => c == rc, _ => false, } } @@ -116,6 +118,9 @@ impl GrantObject { GrantObject::Warehouse(_) => { UserPrivilegeSet::available_privileges_on_warehouse(available_ownership) } + GrantObject::Connection(_) => { + UserPrivilegeSet::available_privileges_on_connection(available_ownership) + } } } @@ -124,7 +129,8 @@ impl GrantObject { GrantObject::Global | GrantObject::Stage(_) | GrantObject::UDF(_) - | GrantObject::Warehouse(_) => None, + | GrantObject::Warehouse(_) + | GrantObject::Connection(_) => None, GrantObject::Database(cat, _) | GrantObject::DatabaseById(cat, _) => Some(cat.clone()), GrantObject::Table(cat, _, _) | GrantObject::TableById(cat, _, _) => Some(cat.clone()), } @@ -146,6 +152,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}"), + GrantObject::Connection(c) => write!(f, "CONNECTION {c}"), } } } diff --git a/src/meta/app/src/principal/user_privilege.rs b/src/meta/app/src/principal/user_privilege.rs index d00bd481943a7..be32415c8afbe 100644 --- a/src/meta/app/src/principal/user_privilege.rs +++ b/src/meta/app/src/principal/user_privilege.rs @@ -78,6 +78,10 @@ pub enum UserPrivilegeType { CreateDatabase = 1 << 20, // Privilege to Create warehouse CreateWarehouse = 1 << 21, + // Privilege to Create Connection + CreateConnection = 1 << 22, + // Privilege to Access Connection + AccessConnection = 1 << 23, // Discard Privilege Type Set = 1 << 4, } @@ -105,6 +109,8 @@ const ALL_PRIVILEGES: BitFlags = make_bitflags!( | Write | CreateDatabase | CreateWarehouse + | CreateConnection + | AccessConnection } ); @@ -133,6 +139,8 @@ impl Display for UserPrivilegeType { UserPrivilegeType::Write => "Write", UserPrivilegeType::CreateDatabase => "CREATE DATABASE", UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE", + UserPrivilegeType::CreateConnection => "CREATE CONNECTION", + UserPrivilegeType::AccessConnection => "ACCESS CONNECTION", }) } } @@ -173,6 +181,12 @@ impl From for UserPrivilegeType { databend_common_ast::ast::UserPrivilegeType::CreateWarehouse => { UserPrivilegeType::CreateWarehouse } + databend_common_ast::ast::UserPrivilegeType::CreateConnection => { + UserPrivilegeType::CreateConnection + } + databend_common_ast::ast::UserPrivilegeType::AccessConnection => { + UserPrivilegeType::AccessConnection + } databend_common_ast::ast::UserPrivilegeType::Set => UserPrivilegeType::Set, } } @@ -207,11 +221,13 @@ impl UserPrivilegeSet { 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(false); - let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask | CreateWarehouse }); + let connection_privs_without_ownership = Self::available_privileges_on_connection(false); + let privs = make_bitflags!(UserPrivilegeType::{ Usage | Super | CreateUser | DropUser | CreateRole | DropRole | CreateDatabase | Grant | CreateDataMask | CreateWarehouse | CreateConnection }); (database_privs.privileges | privs | stage_privs_without_ownership.privileges | wh_privs_without_ownership.privileges + | connection_privs_without_ownership.privileges | udf_privs_without_ownership.privileges) .into() } @@ -251,6 +267,14 @@ impl UserPrivilegeSet { } } + pub fn available_privileges_on_connection(available_ownership: bool) -> Self { + if available_ownership { + make_bitflags!(UserPrivilegeType::{ AccessConnection | Ownership }).into() + } else { + make_bitflags!(UserPrivilegeType::{ AccessConnection }).into() + } + } + pub fn available_privileges_on_udf(available_ownership: bool) -> Self { if available_ownership { make_bitflags!(UserPrivilegeType::{ Usage | Ownership }).into() diff --git a/src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs b/src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs index f711d7041e96d..6c8cd1d5c0d86 100644 --- a/src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs +++ b/src/meta/proto-conv/src/ownership_from_to_protobuf_impl.rs @@ -91,6 +91,9 @@ impl FromToProto for mt::principal::OwnershipObject { pb::ownership_object::Object::Warehouse( pb::ownership_object::OwnershipWarehouseObject { id }, ) => Ok(mt::principal::OwnershipObject::Warehouse { id }), + pb::ownership_object::Object::Connection( + pb::ownership_object::OwnershipConnectionObject { connection }, + ) => Ok(mt::principal::OwnershipObject::Connection { name: connection }), } } @@ -131,6 +134,13 @@ impl FromToProto for mt::principal::OwnershipObject { pb::ownership_object::OwnershipWarehouseObject { id: id.clone() }, )) } + mt::principal::OwnershipObject::Connection { name } => { + Some(pb::ownership_object::Object::Connection( + pb::ownership_object::OwnershipConnectionObject { + connection: name.clone(), + }, + )) + } }; Ok(pb::OwnershipObject { ver: VER, diff --git a/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs b/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs index 175b0122b9f2f..24c677f4105da 100644 --- a/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs +++ b/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs @@ -194,6 +194,9 @@ impl FromToProto for mt::principal::GrantObject { pb::grant_object::Object::Warehouse(pb::grant_object::GrantWarehouseObject { warehouse, }) => Ok(mt::principal::GrantObject::Warehouse(warehouse)), + pb::grant_object::Object::Connection(pb::grant_object::GrantConnectionObject { + connection, + }) => Ok(mt::principal::GrantObject::Connection(connection)), } } @@ -241,6 +244,11 @@ impl FromToProto for mt::principal::GrantObject { warehouse: w.clone(), }, )), + mt::principal::GrantObject::Connection(c) => Some( + pb::grant_object::Object::Connection(pb::grant_object::GrantConnectionObject { + connection: c.clone(), + }), + ), }; Ok(pb::GrantObject { ver: VER, diff --git a/src/meta/proto-conv/src/util.rs b/src/meta/proto-conv/src/util.rs index 29d6dfa907a03..c3939cdf17e30 100644 --- a/src/meta/proto-conv/src/util.rs +++ b/src/meta/proto-conv/src/util.rs @@ -166,6 +166,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[ (134, "2025-06-27: Add: SequenceMeta.storage_version"), (135, "2025-07-16: Add: UDFServer.immutable, UDFScript.immutable"), (136, "2025-07-17: Add: Task"), + (137, "2025-07-22: Add: GrantConnectionObject and UserPrivilegeType AccessConnection, AccessConnection"), // 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`) diff --git a/src/meta/proto-conv/tests/it/main.rs b/src/meta/proto-conv/tests/it/main.rs index 8d227caabe162..a642a3e4377a8 100644 --- a/src/meta/proto-conv/tests/it/main.rs +++ b/src/meta/proto-conv/tests/it/main.rs @@ -128,3 +128,4 @@ mod v133_stage_file_compression; mod v134_add_sequence_meta_storage_version; mod v135_udf_immutable; mod v136_add_task; +mod v137_add_grant_object_connection; diff --git a/src/meta/proto-conv/tests/it/v137_add_grant_object_connection.rs b/src/meta/proto-conv/tests/it/v137_add_grant_object_connection.rs new file mode 100644 index 0000000000000..b78dae5bb2443 --- /dev/null +++ b/src/meta/proto-conv/tests/it/v137_add_grant_object_connection.rs @@ -0,0 +1,96 @@ +// 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 std::collections::HashSet; + +use chrono::DateTime; +use chrono::Utc; +use databend_common_meta_app as mt; +use databend_common_meta_app::principal::OwnershipObject; +use databend_common_meta_app::principal::UserGrantSet; +use databend_common_meta_app::principal::UserPrivilegeType; +use enumflags2::make_bitflags; +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_v137_grant_object() -> anyhow::Result<()> { + let role_info_v137 = vec![ + 10, 2, 114, 49, 18, 86, 10, 23, 10, 9, 10, 0, 160, 6, 137, 1, 168, 6, 24, 16, 128, 128, + 128, 2, 160, 6, 137, 1, 168, 6, 24, 10, 27, 10, 13, 74, 4, 10, 2, 99, 49, 160, 6, 137, 1, + 168, 6, 24, 16, 128, 128, 128, 4, 160, 6, 137, 1, 168, 6, 24, 10, 23, 10, 9, 10, 0, 160, 6, + 137, 1, 168, 6, 24, 16, 254, 255, 191, 7, 160, 6, 137, 1, 168, 6, 24, 160, 6, 137, 1, 168, + 6, 24, 26, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, 48, 48, + 32, 85, 84, 67, 34, 23, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48, 58, + 48, 48, 32, 85, 84, 67, 160, 6, 137, 1, 168, 6, 24, + ]; + let want = || mt::principal::RoleInfo { + name: "r1".to_string(), + grants: UserGrantSet::new( + vec![ + mt::principal::GrantEntry::new( + mt::principal::GrantObject::Global, + make_bitflags!(UserPrivilegeType::{CreateConnection}), + ), + mt::principal::GrantEntry::new( + mt::principal::GrantObject::Connection("c1".to_string()), + make_bitflags!(UserPrivilegeType::{AccessConnection}), + ), + // test new global privilege CreateConneciton, AccessConnection + mt::principal::GrantEntry::new( + mt::principal::GrantObject::Global, + 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 }), + ), + ], + HashSet::new(), + ), + created_on: DateTime::::default(), + update_on: DateTime::::default(), + }; + + common::test_pb_from_to(func_name!(), want())?; + common::test_load_old(func_name!(), role_info_v137.as_slice(), 137, want())?; + + Ok(()) +} + +#[test] +fn test_decode_v137_ownership() -> anyhow::Result<()> { + let ownership_info_v137 = vec![ + 10, 2, 114, 49, 18, 13, 50, 4, 10, 2, 99, 49, 160, 6, 137, 1, 168, 6, 24, 160, 6, 137, 1, + 168, 6, 24, + ]; + + let want = || mt::principal::OwnershipInfo { + role: "r1".to_string(), + object: OwnershipObject::Connection { + name: "c1".to_string(), + }, + }; + common::test_pb_from_to(func_name!(), want())?; + common::test_load_old(func_name!(), ownership_info_v137.as_slice(), 137, want())?; + + Ok(()) +} diff --git a/src/meta/protos/proto/ownership.proto b/src/meta/protos/proto/ownership.proto index e21e6300b48cd..a07d8e7e5d65f 100644 --- a/src/meta/protos/proto/ownership.proto +++ b/src/meta/protos/proto/ownership.proto @@ -50,11 +50,16 @@ message OwnershipObject { string id = 1; } + message OwnershipConnectionObject { + string connection = 1; + } + oneof object { OwnershipDatabaseObject database = 1; OwnershipTableObject table = 2; OwnershipUdfObject udf = 3; OwnershipStageObject stage = 4; OwnershipWarehouseObject warehouse = 5; + OwnershipConnectionObject connection = 6; } } diff --git a/src/meta/protos/proto/user.proto b/src/meta/protos/proto/user.proto index 0815743ddc243..b6f2e303c824f 100644 --- a/src/meta/protos/proto/user.proto +++ b/src/meta/protos/proto/user.proto @@ -81,6 +81,10 @@ message GrantObject { string warehouse = 1; } + message GrantConnectionObject { + string connection = 1; + } + oneof object { GrantGlobalObject global = 1; GrantDatabaseObject database = 2; @@ -90,6 +94,7 @@ message GrantObject { GrantDatabaseIdObject databasebyid = 6; GrantTableIdObject tablebyid = 7; GrantWarehouseObject warehouse = 8; + GrantConnectionObject connection = 9; } } diff --git a/src/query/ast/src/ast/statements/principal.rs b/src/query/ast/src/ast/statements/principal.rs index 10112185d6408..c7be03ef21f88 100644 --- a/src/query/ast/src/ast/statements/principal.rs +++ b/src/query/ast/src/ast/statements/principal.rs @@ -152,6 +152,10 @@ pub enum UserPrivilegeType { CreateDatabase, // Privilege to Create warehouse CreateWarehouse, + // Privilege to Access connection + AccessConnection, + // Privilege to Create connection + CreateConnection, // Discard Privilege Type Set, } @@ -181,6 +185,8 @@ impl Display for UserPrivilegeType { UserPrivilegeType::Write => "Write", UserPrivilegeType::CreateDatabase => "CREATE DATABASE", UserPrivilegeType::CreateWarehouse => "CREATE WAREHOUSE", + UserPrivilegeType::CreateConnection => "CREATE CONNECTION", + UserPrivilegeType::AccessConnection => "ACCESS CONNECTION", }) } } diff --git a/src/query/ast/src/ast/statements/user.rs b/src/query/ast/src/ast/statements/user.rs index ddccad6e8f484..7c1c6f475bd2a 100644 --- a/src/query/ast/src/ast/statements/user.rs +++ b/src/query/ast/src/ast/statements/user.rs @@ -165,6 +165,7 @@ pub enum GrantObjectName { UDF(String), Stage(String), Warehouse(String), + Connection(String), } impl Display for GrantObjectName { @@ -183,6 +184,7 @@ impl Display for GrantObjectName { GrantObjectName::UDF(udf) => write!(f, " UDF {udf}"), GrantObjectName::Stage(stage) => write!(f, " STAGE {stage}"), GrantObjectName::Warehouse(w) => write!(f, " WAREHOUSE {w}"), + GrantObjectName::Connection(c) => write!(f, " CONNECTION {c}"), } } } @@ -240,6 +242,7 @@ pub enum AccountMgrLevel { UDF(String), Stage(String), Warehouse(String), + Connection(String), } impl Display for AccountMgrLevel { @@ -263,6 +266,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}"), + AccountMgrLevel::Connection(w) => write!(f, " CONNECTION {w}"), } } } diff --git a/src/query/ast/src/parser/statement.rs b/src/query/ast/src/parser/statement.rs index cef4e684a80b9..5d5fc71e5d859 100644 --- a/src/query/ast/src/parser/statement.rs +++ b/src/query/ast/src/parser/statement.rs @@ -3398,11 +3398,33 @@ pub fn grant_source(i: Input) -> IResult { }, ); + let connection_privs = map( + rule! { + ACCESS ~ CONNECTION ~ ON ~ CONNECTION ~ #ident + }, + |(_, _, _, _, c)| AccountMgrSource::Privs { + privileges: vec![UserPrivilegeType::AccessConnection], + level: AccountMgrLevel::Connection(c.to_string()), + }, + ); + + let connection_all_privs = map( + rule! { + ALL ~ PRIVILEGES? ~ ON ~ CONNECTION ~ #ident + }, + |(_, _, _, _, w)| AccountMgrSource::Privs { + privileges: vec![UserPrivilegeType::AccessConnection], + level: AccountMgrLevel::Connection(w.to_string()), + }, + ); + rule!( #role : "ROLE " | #warehouse_all_privs: "ALL [ PRIVILEGES ] ON WAREHOUSE " + | #connection_all_privs: "ALL [ PRIVILEGES ] ON CONNECTION " | #udf_privs: "USAGE ON UDF " | #warehouse_privs: "USAGE ON WAREHOUSE " + | #connection_privs: "USAGE ON CONNECTION " | #privs : " ON " | #stage_privs : " ON STAGE " | #udf_all_privs: "ALL [ PRIVILEGES ] ON UDF " @@ -3411,31 +3433,64 @@ pub fn grant_source(i: Input) -> IResult { } pub fn priv_type(i: Input) -> IResult { + let usage = value(UserPrivilegeType::Usage, rule! { USAGE }); + let select = value(UserPrivilegeType::Select, rule! { SELECT }); + let insert = value(UserPrivilegeType::Insert, rule! { INSERT }); + let update = value(UserPrivilegeType::Update, rule! { UPDATE }); + let delete = value(UserPrivilegeType::Delete, rule! { DELETE }); + let alter = value(UserPrivilegeType::Alter, rule! { ALTER }); + let super_priv = value(UserPrivilegeType::Super, rule! { SUPER }); + let create_user = value(UserPrivilegeType::CreateUser, rule! { CREATE ~ USER }); + let create_database = value( + UserPrivilegeType::CreateDatabase, + rule! { CREATE ~ DATABASE }, + ); + let create_warehouse = value( + UserPrivilegeType::CreateWarehouse, + rule! { CREATE ~ WAREHOUSE }, + ); + let create_connection = value( + UserPrivilegeType::CreateConnection, + rule! { CREATE ~ CONNECTION }, + ); + let access_connection = value( + UserPrivilegeType::AccessConnection, + rule! { ACCESS ~ CONNECTION }, + ); + let drop_user = value(UserPrivilegeType::DropUser, rule! { DROP ~ USER }); + let create_role = value(UserPrivilegeType::CreateRole, rule! { CREATE ~ ROLE }); + let drop_role = value(UserPrivilegeType::DropRole, rule! { DROP ~ ROLE }); + let grant = value(UserPrivilegeType::Grant, rule! { GRANT }); + let create_stage = value(UserPrivilegeType::CreateStage, rule! { CREATE ~ STAGE }); + let set = value(UserPrivilegeType::Set, rule! { SET }); + let drop = value(UserPrivilegeType::Drop, rule! { DROP }); + let create = value(UserPrivilegeType::Create, rule! { CREATE }); + alt(( - value(UserPrivilegeType::Usage, rule! { USAGE }), - value(UserPrivilegeType::Select, rule! { SELECT }), - value(UserPrivilegeType::Insert, rule! { INSERT }), - value(UserPrivilegeType::Update, rule! { UPDATE }), - value(UserPrivilegeType::Delete, rule! { DELETE }), - value(UserPrivilegeType::Alter, rule! { ALTER }), - value(UserPrivilegeType::Super, rule! { SUPER }), - value(UserPrivilegeType::CreateUser, rule! { CREATE ~ USER }), - value( - UserPrivilegeType::CreateDatabase, - rule! { CREATE ~ DATABASE }, + rule!( + #usage + | #select + | #insert + | #update + | #delete + | #alter + | #super_priv + | #create_user + | #create_database + | #create_warehouse ), - value( - UserPrivilegeType::CreateWarehouse, - rule! { CREATE ~ WAREHOUSE }, + rule!( + #create_connection + | #access_connection + | #drop_user + | #create_role + | #drop_role + | #grant + | #create_stage + | #set + | #drop + | #create ), - value(UserPrivilegeType::DropUser, rule! { DROP ~ USER }), - value(UserPrivilegeType::CreateRole, rule! { CREATE ~ ROLE }), - value(UserPrivilegeType::DropRole, rule! { DROP ~ ROLE }), - value(UserPrivilegeType::Grant, rule! { GRANT }), - value(UserPrivilegeType::CreateStage, rule! { CREATE ~ STAGE }), - value(UserPrivilegeType::Set, rule! { SET }), - value(UserPrivilegeType::Drop, rule! { DROP }), - value(UserPrivilegeType::Create, rule! { CREATE }), ))(i) } @@ -3491,12 +3546,17 @@ pub fn on_object_name(i: Input) -> IResult { GrantObjectName::Warehouse(w.to_string()) }); + let connection = map(rule! { CONNECTION ~ #ident}, |(_, w)| { + GrantObjectName::Connection(w.to_string()) + }); + rule!( #database : "DATABASE " | #table : "TABLE ." | #stage : "STAGE " | #udf : "UDF " | #warehouse : "WAREHOUSE " + | #connection : "CONNECTION " )(i) } @@ -3592,11 +3652,13 @@ pub fn grant_ownership_level(i: Input) -> IResult { Stage, Udf, Warehouse, + Connection, } let object = alt(( value(Object::Udf, rule! { UDF }), value(Object::Stage, rule! { STAGE }), value(Object::Warehouse, rule! { WAREHOUSE }), + value(Object::Connection, rule! { CONNECTION }), )); // Object object_name @@ -3606,13 +3668,14 @@ pub fn grant_ownership_level(i: Input) -> IResult { Object::Stage => AccountMgrLevel::Stage(object_name.to_string()), Object::Udf => AccountMgrLevel::UDF(object_name.to_string()), Object::Warehouse => AccountMgrLevel::Warehouse(object_name.to_string()), + Object::Connection => AccountMgrLevel::Connection(object_name.to_string()), }, ); rule!( #db : ".*" | #table : ".
" - | #object : "STAGE | UDF | WAREHOUSE " + | #object : "STAGE | UDF | WAREHOUSE | CONNECTION " )(i) } diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index 12989c36a8581..accbe1c1119ab 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -1136,6 +1136,8 @@ pub enum TokenKind { SYNTAX, #[token("USAGE", ignore(ascii_case))] USAGE, + #[token("ACCESS", ignore(ascii_case))] + ACCESS, #[token("USE_LOGIC_TYPE", ignore(ascii_case))] USE_LOGIC_TYPE, #[token("USE_RAW_PATH", ignore(ascii_case))] diff --git a/src/query/ast/tests/it/parser.rs b/src/query/ast/tests/it/parser.rs index e2740d4e8c9fc..7e95cb1222443 100644 --- a/src/query/ast/tests/it/parser.rs +++ b/src/query/ast/tests/it/parser.rs @@ -334,6 +334,10 @@ SELECT * from s;"#, r#"COMMENT ON password policy p1 IS 'test'"#, r#"CREATE TEMPORARY TABLE t (a INT COMMENT 'col comment')"#, r#"GRANT CREATE, CREATE USER ON * TO 'test-grant';"#, + r#"GRANT access connection, create connection ON *.* TO 'test-grant';"#, + r#"GRANT access connection on connection c1 TO 'test-grant';"#, + r#"GRANT all on connection c1 TO 'test-grant';"#, + r#"GRANT OWNERSHIP on connection c1 TO role r1;"#, r#"GRANT SELECT, CREATE ON * TO 'test-grant';"#, r#"GRANT SELECT, CREATE ON *.* TO 'test-grant';"#, r#"GRANT SELECT, CREATE ON * TO USER 'test-grant';"#, @@ -603,6 +607,7 @@ SELECT * from s;"#, r#"REVOKE all ON warehouse a FROM role 'test-grant';"#, r#"SHOW GRANTS ON TABLE db1.tb1;"#, r#"SHOW GRANTS ON DATABASE db;"#, + r#"SHOW GRANTS ON CONNECTION c1;"#, r#"UPDATE db1.tb1 set a = a + 1, b = 2 WHERE c > 3;"#, r#"select $abc + 3"#, r#"select IDENTIFIER($abc)"#, diff --git a/src/query/ast/tests/it/testdata/stmt-error.txt b/src/query/ast/tests/it/testdata/stmt-error.txt index 94ef706eb9710..6706271a9901d 100644 --- a/src/query/ast/tests/it/testdata/stmt-error.txt +++ b/src/query/ast/tests/it/testdata/stmt-error.txt @@ -361,7 +361,7 @@ error: --> SQL:1:15 | 1 | GRANT SELECT, ALL PRIVILEGES, CREATE ON * TO 'test-grant'; - | ----- ------ ^^^ unexpected `ALL`, expecting `ALTER`, `SELECT`, `DELETE`, `USAGE`, `INSERT`, `UPDATE`, `SUPER`, `CREATE`, `DROP`, `GRANT`, or `SET` + | ----- ------ ^^^ unexpected `ALL`, expecting `ALTER`, `SELECT`, `DELETE`, `ACCESS`, `USAGE`, `INSERT`, `UPDATE`, `SUPER`, `CREATE`, `DROP`, `GRANT`, or `SET` | | | | | while parsing ON | while parsing `GRANT { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } TO { [ROLE ] | [USER] }` @@ -399,7 +399,7 @@ error: --> SQL:1:24 | 1 | REVOKE SELECT, CREATE, ALL PRIVILEGES ON * FROM 'test-grant'; - | ------ ------ ^^^ unexpected `ALL`, expecting `ALTER`, `SELECT`, `DELETE`, `USAGE`, `INSERT`, `UPDATE`, `SUPER`, `CREATE`, `DROP`, `GRANT`, or `SET` + | ------ ------ ^^^ unexpected `ALL`, expecting `ALTER`, `SELECT`, `DELETE`, `ACCESS`, `USAGE`, `INSERT`, `UPDATE`, `SUPER`, `CREATE`, `DROP`, `GRANT`, or `SET` | | | | | while parsing ON | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` @@ -930,7 +930,7 @@ error: --> SQL:1:8 | 1 | REVOKE OWNERSHIP, SELECT ON d20_0014.* FROM ROLE 'd20_0015_owner'; - | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` + | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `ACCESS`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` | | | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` @@ -942,7 +942,7 @@ error: --> SQL:1:8 | 1 | REVOKE OWNERSHIP ON d20_0014.* FROM USER A; - | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` + | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `ACCESS`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` | | | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` @@ -954,7 +954,7 @@ error: --> SQL:1:8 | 1 | REVOKE OWNERSHIP ON d20_0014.* FROM ROLE A; - | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` + | ------ ^^^^^^^^^ unexpected `OWNERSHIP`, expecting `INSERT`, `ALTER`, `SUPER`, `ROLE`, `ACCESS`, `WRITE`, `SET`, `SELECT`, `UPDATE`, `DELETE`, `DROP`, `READ`, `USAGE`, `GRANT`, `CREATE`, or `ALL` | | | while parsing `REVOKE { ROLE | schemaObjectPrivileges | ALL [ PRIVILEGES ] ON } FROM { [ROLE ] | [USER] }` @@ -1055,7 +1055,7 @@ error: --> SQL:1:16 | 1 | SHOW GRANTS ON task t1; - | ^^^^ unexpected `task`, expecting `STAGE`, `TABLE`, `WAREHOUSE`, `DATABASE`, or `UDF` + | ^^^^ unexpected `task`, expecting `STAGE`, `TABLE`, `WAREHOUSE`, `DATABASE`, `UDF`, or `CONNECTION` ---------- Input ---------- diff --git a/src/query/ast/tests/it/testdata/stmt.txt b/src/query/ast/tests/it/testdata/stmt.txt index 8af78e1a5094f..a7676cd884d02 100644 --- a/src/query/ast/tests/it/testdata/stmt.txt +++ b/src/query/ast/tests/it/testdata/stmt.txt @@ -15111,6 +15111,102 @@ Grant( ) +---------- Input ---------- +GRANT access connection, create connection ON *.* TO 'test-grant'; +---------- Output --------- +GRANT ACCESS CONNECTION, CREATE CONNECTION ON *.* TO USER 'test-grant'@'%' +---------- AST ------------ +Grant( + GrantStmt { + source: Privs { + privileges: [ + AccessConnection, + CreateConnection, + ], + level: Global, + }, + principal: User( + UserIdentity { + username: "test-grant", + hostname: "%", + }, + ), + }, +) + + +---------- Input ---------- +GRANT access connection on connection c1 TO 'test-grant'; +---------- Output --------- +GRANT ACCESS CONNECTION ON CONNECTION c1 TO USER 'test-grant'@'%' +---------- AST ------------ +Grant( + GrantStmt { + source: Privs { + privileges: [ + AccessConnection, + ], + level: Connection( + "c1", + ), + }, + principal: User( + UserIdentity { + username: "test-grant", + hostname: "%", + }, + ), + }, +) + + +---------- Input ---------- +GRANT all on connection c1 TO 'test-grant'; +---------- Output --------- +GRANT ACCESS CONNECTION ON CONNECTION c1 TO USER 'test-grant'@'%' +---------- AST ------------ +Grant( + GrantStmt { + source: Privs { + privileges: [ + AccessConnection, + ], + level: Connection( + "c1", + ), + }, + principal: User( + UserIdentity { + username: "test-grant", + hostname: "%", + }, + ), + }, +) + + +---------- Input ---------- +GRANT OWNERSHIP on connection c1 TO role r1; +---------- Output --------- +GRANT OWNERSHIP ON CONNECTION c1 TO ROLE 'r1' +---------- AST ------------ +Grant( + GrantStmt { + source: Privs { + privileges: [ + Ownership, + ], + level: Connection( + "c1", + ), + }, + principal: Role( + "r1", + ), + }, +) + + ---------- Input ---------- GRANT SELECT, CREATE ON * TO 'test-grant'; ---------- Output --------- @@ -18148,6 +18244,26 @@ ShowObjectPrivileges( ) +---------- Input ---------- +SHOW GRANTS ON CONNECTION c1; +---------- Output --------- +SHOW GRANTS ON CONNECTION c1 +---------- AST ------------ +ShowObjectPrivileges( + ShowObjectPrivilegesStmt { + object: Connection( + "c1", + ), + show_option: Some( + ShowOptions { + show_limit: None, + limit: None, + }, + ), + }, +) + + ---------- Input ---------- UPDATE db1.tb1 set a = a + 1, b = 2 WHERE c > 3; ---------- Output --------- diff --git a/src/query/management/src/role/role_mgr.rs b/src/query/management/src/role/role_mgr.rs index 3a19c862f710a..d053d1ce58828 100644 --- a/src/query/management/src/role/role_mgr.rs +++ b/src/query/management/src/role/role_mgr.rs @@ -566,6 +566,7 @@ fn convert_to_grant_obj(owner_obj: &OwnershipObject) -> GrantObject { OwnershipObject::Stage { name } => GrantObject::Stage(name.to_string()), OwnershipObject::UDF { name } => GrantObject::UDF(name.to_string()), OwnershipObject::Warehouse { id } => GrantObject::Warehouse(id.to_string()), + OwnershipObject::Connection { name } => GrantObject::Connection(name.to_string()), } } diff --git a/src/query/service/src/interpreters/access/privilege_access.rs b/src/query/service/src/interpreters/access/privilege_access.rs index 67940356b20c3..12fa55f0dcb4d 100644 --- a/src/query/service/src/interpreters/access/privilege_access.rs +++ b/src/query/service/src/interpreters/access/privilege_access.rs @@ -158,6 +158,9 @@ impl PrivilegeAccess { name: name.to_string(), }, GrantObject::Warehouse(id) => OwnershipObject::Warehouse { id: id.to_string() }, + GrantObject::Connection(name) => OwnershipObject::Connection { + name: name.to_string(), + }, GrantObject::Global => return Ok(None), }; @@ -294,7 +297,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 Warehouse|Database|Table|UDF|Stage.", + Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection", privileges, catalog_name, db_name, @@ -501,6 +504,38 @@ impl PrivilegeAccess { } } + async fn validate_connection_access( + &self, + connection: String, + privilege: UserPrivilegeType, + ) -> Result<()> { + if !self + .ctx + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + return self + .validate_access(&GrantObject::Global, UserPrivilegeType::Super, false, false) + .await; + } + + if self + .validate_access(&GrantObject::Global, UserPrivilegeType::Super, false, false) + .await + .is_ok() + { + return Ok(()); + } + + self.validate_access( + &GrantObject::Connection(connection), + privilege, + false, + false, + ) + .await + } + async fn has_ownership( &self, session: &Arc, @@ -565,6 +600,7 @@ impl PrivilegeAccess { | GrantObject::UDF(_) | GrantObject::Stage(_) | GrantObject::Warehouse(_) + | GrantObject::Connection(_) | GrantObject::TableById(_, _, _) => true, GrantObject::Global => false, }; @@ -616,11 +652,12 @@ impl PrivilegeAccess { GrantObject::Global | GrantObject::UDF(_) | GrantObject::Warehouse(_) + | GrantObject::Connection(_) | 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 Warehouse|Database|Table|UDF|Stage.", + Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection.", privilege, grant_object, ¤t_user.identity().display(), @@ -1324,6 +1361,31 @@ impl AccessChecker for PrivilegeAccess { Plan::RemoveStage(plan) => { self.validate_stage_access(&plan.stage, UserPrivilegeType::Write).await?; } + // Connection + Plan::CreateConnection(_) => { + let super_privilege_check_result = self + .validate_access(&GrantObject::Global, UserPrivilegeType::Super, false, false) + .await; + if !self + .ctx + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + return super_privilege_check_result; + } + if super_privilege_check_result.is_err() { + self.validate_access(&GrantObject::Global, UserPrivilegeType::CreateConnection, true, false) + .await? + } + } + Plan::DescConnection(plan) => { + self.validate_connection_access(plan.name.to_string(), UserPrivilegeType::AccessConnection) + .await?; + } + Plan::DropConnection(plan) => { + self.validate_connection_access(plan.name.to_string(), UserPrivilegeType::AccessConnection) + .await?; + } Plan::ShowCreateCatalog(_) | Plan::CreateCatalog(_) | Plan::DropCatalog(_) @@ -1340,10 +1402,6 @@ impl AccessChecker for PrivilegeAccess { | Plan::AlterPasswordPolicy(_) | Plan::DropPasswordPolicy(_) | Plan::DescPasswordPolicy(_) - | Plan::CreateConnection(_) - | Plan::ShowConnections(_) - | Plan::DescConnection(_) - | Plan::DropConnection(_) | Plan::CreateIndex(_) | Plan::CreateTableIndex(_) | Plan::CreateNotification(_) @@ -1400,6 +1458,7 @@ impl AccessChecker for PrivilegeAccess { } Plan::Commit => {} Plan::Abort => {} + Plan::ShowConnections(_) => {} Plan::ShowWarehouses => { // check privilege in interpreter } @@ -1498,7 +1557,8 @@ fn check_db_tb_ownership_access( } OwnershipObject::UDF { .. } | OwnershipObject::Stage { .. } - | OwnershipObject::Warehouse { .. } => {} + | OwnershipObject::Warehouse { .. } + | OwnershipObject::Connection { .. } => {} } } } diff --git a/src/query/service/src/interpreters/common/grant.rs b/src/query/service/src/interpreters/common/grant.rs index 75563d8afb174..c53716de4b947 100644 --- a/src/query/service/src/interpreters/common/grant.rs +++ b/src/query/service/src/interpreters/common/grant.rs @@ -117,6 +117,12 @@ pub async fn validate_grant_object_exists( )) }; } + GrantObject::Connection(c) => { + return match ctx.get_connection(c).await { + Ok(_c) => Ok(()), + Err(e) => Err(e), + } + } GrantObject::Global => (), } diff --git a/src/query/service/src/interpreters/interpreter_connection_create.rs b/src/query/service/src/interpreters/interpreter_connection_create.rs index 75a112912c8d5..43a15e6837c3b 100644 --- a/src/query/service/src/interpreters/interpreter_connection_create.rs +++ b/src/query/service/src/interpreters/interpreter_connection_create.rs @@ -15,8 +15,11 @@ use std::sync::Arc; use databend_common_exception::Result; +use databend_common_management::RoleApi; +use databend_common_meta_app::principal::OwnershipObject; use databend_common_meta_app::principal::UserDefinedConnection; use databend_common_sql::plans::CreateConnectionPlan; +use databend_common_users::RoleCacheManager; use databend_common_users::UserApiProvider; use log::debug; @@ -65,6 +68,26 @@ impl Interpreter for CreateConnectionInterpreter { .add_connection(&tenant, conn, &plan.create_option) .await?; + // Grant ownership as the current role + if self + .ctx + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + if let Some(current_role) = self.ctx.get_current_role() { + let role_api = UserApiProvider::instance().role_api(&tenant); + role_api + .grant_ownership( + &OwnershipObject::Connection { + name: self.plan.name.clone(), + }, + ¤t_role.name, + ) + .await?; + RoleCacheManager::instance().invalidate_cache(&tenant); + } + } + Ok(PipelineBuildResult::create()) } } diff --git a/src/query/service/src/interpreters/interpreter_connection_drop.rs b/src/query/service/src/interpreters/interpreter_connection_drop.rs index 0be8e7d410680..c69c1432c5554 100644 --- a/src/query/service/src/interpreters/interpreter_connection_drop.rs +++ b/src/query/service/src/interpreters/interpreter_connection_drop.rs @@ -15,7 +15,10 @@ use std::sync::Arc; use databend_common_exception::Result; +use databend_common_management::RoleApi; +use databend_common_meta_app::principal::OwnershipObject; use databend_common_sql::plans::DropConnectionPlan; +use databend_common_users::RoleCacheManager; use databend_common_users::UserApiProvider; use log::debug; @@ -55,6 +58,21 @@ impl Interpreter for DropConnectionInterpreter { let tenant = self.ctx.get_tenant(); let user_mgr = UserApiProvider::instance(); + // we should do `drop ownership` after actually drop object, and object maybe not exists. + // drop the ownership + if self + .ctx + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + let role_api = UserApiProvider::instance().role_api(&tenant); + let owner_object = OwnershipObject::Connection { + name: self.plan.name.clone(), + }; + role_api.revoke_ownership(&owner_object).await?; + RoleCacheManager::instance().invalidate_cache(&tenant); + } + user_mgr .drop_connection(&tenant, &plan.name, plan.if_exists) .await?; diff --git a/src/query/service/src/interpreters/interpreter_connection_show.rs b/src/query/service/src/interpreters/interpreter_connection_show.rs index 3240a9734dede..c5613c5f36f96 100644 --- a/src/query/service/src/interpreters/interpreter_connection_show.rs +++ b/src/query/service/src/interpreters/interpreter_connection_show.rs @@ -59,21 +59,29 @@ impl Interpreter for ShowConnectionsInterpreter { formats.sort_by(|a, b| a.name.cmp(&b.name)); - let names = formats.iter().map(|x| x.name.clone()).collect::>(); + if self + .ctx + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + let visibility_checker = self.ctx.get_visibility_checker(false).await?; + formats.retain(|c| visibility_checker.check_connection_visibility(&c.name)); + } - let types = formats - .iter() - .map(|x| x.storage_type.clone()) - .collect::>(); + // Merge three independent 'map().collect()' into one iteration. + let capacity = formats.len(); + let mut names = Vec::with_capacity(capacity); + let mut types = Vec::with_capacity(capacity); + let mut options = Vec::with_capacity(capacity); - let options = formats - .iter_mut() - .map(|x| { - let conn = Connection::new(x.storage_params.clone()).mask(); - x.storage_params = conn.conns; - x.storage_params_display().clone() - }) - .collect::>(); + for c in formats.iter_mut() { + names.push(c.name.clone()); + types.push(c.storage_type.clone()); + + let conn = Connection::new(c.storage_params.clone()).mask(); + c.storage_params = conn.conns; + options.push(c.storage_params_display().clone()); + } PipelineBuildResult::from_blocks(vec![DataBlock::new_from_columns(vec![ StringType::from_data(names), diff --git a/src/query/service/src/interpreters/interpreter_privilege_grant.rs b/src/query/service/src/interpreters/interpreter_privilege_grant.rs index 8e61e3efaabba..c83e0c8a3b1b5 100644 --- a/src/query/service/src/interpreters/interpreter_privilege_grant.rs +++ b/src/query/service/src/interpreters/interpreter_privilege_grant.rs @@ -126,6 +126,9 @@ impl GrantPrivilegeInterpreter { GrantObject::Warehouse(id) => Ok(OwnershipObject::Warehouse { id: id.to_string(), }), + GrantObject::Connection(name) => Ok(OwnershipObject::Connection { + name: name.to_string(), + }), GrantObject::Global => Err(ErrorCode::IllegalGrant( "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used", )), @@ -267,7 +270,7 @@ pub fn validate_grant_privileges(object: &GrantObject, privileges: UserPrivilege .iter() .all(|p| available_privileges.has_privilege(p)); if !ok { - return Err(databend_common_exception::ErrorCode::IllegalGrant( + return Err(ErrorCode::IllegalGrant( "Illegal GRANT/REVOKE command; please consult the manual to see which privileges can be used", )); } diff --git a/src/query/service/src/sessions/query_ctx.rs b/src/query/service/src/sessions/query_ctx.rs index e181a273e089f..25e081fb089f0 100644 --- a/src/query/service/src/sessions/query_ctx.rs +++ b/src/query/service/src/sessions/query_ctx.rs @@ -1165,6 +1165,18 @@ impl TableContext for QueryContext { } } async fn get_connection(&self, name: &str) -> Result { + if self + .get_settings() + .get_enable_experimental_connection_privilege_check()? + { + let visibility_checker = self.get_visibility_checker(false).await?; + if !visibility_checker.check_connection_visibility(name) { + return Err(ErrorCode::PermissionDenied(format!( + "Permission denied: privilege AccessConnection is required on connection {name} for user {}", + &self.get_current_user()?.identity().display(), + ))); + } + } self.shared.get_connection(name).await } diff --git a/src/query/service/src/table_functions/show_grants/show_grants_table.rs b/src/query/service/src/table_functions/show_grants/show_grants_table.rs index 2201e7b6a24c1..ea8b5e7852bb7 100644 --- a/src/query/service/src/table_functions/show_grants/show_grants_table.rs +++ b/src/query/service/src/table_functions/show_grants/show_grants_table.rs @@ -244,7 +244,7 @@ impl AsyncSource for ShowGrantsSource { "role" | "user" => { show_account_grants(self.ctx.clone(), &self.grant_type, &self.name).await? } - "table" | "database" | "udf" | "stage" | "warehouse" => { + "table" | "database" | "udf" | "stage" | "warehouse" | "connection" => { show_object_grant( self.ctx.clone(), &self.grant_type, @@ -257,7 +257,7 @@ impl AsyncSource for ShowGrantsSource { "role_grantee" => show_role_grantees(self.ctx.clone(), &self.name).await?, _ => { return Err(ErrorCode::InvalidArgument(format!( - "Expected 'user|role|table|database|udf|stage|warehouse|role_grantee', but got {:?}", + "Expected 'user|role|table|database|udf|stage|warehouse|connection|role_grantee', but got {:?}", self.grant_type ))); } @@ -536,6 +536,12 @@ async fn show_account_grants( grant_list.push(format!("{} TO {}", grant_entry, identity)); } } + GrantObject::Connection(connection) => { + object_name.push(connection.to_string()); + object_id.push(None); + privileges.push(get_priv_str(&grant_entry)); + grant_list.push(format!("{} TO {}", grant_entry, identity)); + } GrantObject::Global => { // grant all on *.* to a object_name.push("*.*".to_string()); @@ -629,6 +635,15 @@ async fn show_account_grants( )); } } + OwnershipObject::Connection { name } => { + object_name.push(name.to_string()); + object_id.push(None); + privileges.push("OWNERSHIP".to_string()); + grant_list.push(format!( + "GRANT OWNERSHIP ON CONNECTION {} TO {}", + name, identity + )); + } } } } @@ -854,9 +869,25 @@ async fn show_object_grant( name, ) } + "connection" => { + if !visibility_checker.check_connection_visibility(name) { + return Err(ErrorCode::PermissionDenied(format!( + "Permission denied: privilege ACCESS CONNECTION is required on connection {} for user {}", + name, current_user + ))); + } + ( + GrantObject::Connection(name.to_string()), + OwnershipObject::Connection { + name: name.to_string(), + }, + None, + name, + ) + } _ => { return Err(ErrorCode::InvalidArgument(format!( - "Expected 'table|database|udf|stage|warehouse', but got {:?}", + "Expected 'table|database|udf|stage|warehouse|connection', but got {:?}", grant_type ))); } diff --git a/src/query/settings/src/settings_default.rs b/src/query/settings/src/settings_default.rs index b0565b12cc945..5f488f1adfe1f 100644 --- a/src/query/settings/src/settings_default.rs +++ b/src/query/settings/src/settings_default.rs @@ -960,6 +960,13 @@ impl DefaultSettings { scope: SettingScope::Both, range: Some(SettingRange::Numeric(0..=1)), }), + ("enable_experimental_connection_privilege_check", DefaultSettingValue { + value: UserSettingValue::UInt64(0), + desc: "experiment setting disables connection object privilege check(disable by default).", + mode: SettingMode::Both, + scope: SettingScope::Both, + range: Some(SettingRange::Numeric(0..=1)), + }), ("enable_collect_column_statistics", DefaultSettingValue { value: UserSettingValue::UInt64(1), desc: "Collect column statistic in system.columns(enable by default).", diff --git a/src/query/settings/src/settings_getter_setter.rs b/src/query/settings/src/settings_getter_setter.rs index 0e9a7617a1ac9..61c8b7d4a15e0 100644 --- a/src/query/settings/src/settings_getter_setter.rs +++ b/src/query/settings/src/settings_getter_setter.rs @@ -548,6 +548,10 @@ impl Settings { Ok(self.try_get_u64("enable_experimental_rbac_check")? != 0) } + pub fn get_enable_experimental_connection_privilege_check(&self) -> Result { + Ok(self.try_get_u64("enable_experimental_connection_privilege_check")? != 0) + } + pub fn get_enable_collect_column_statistics(&self) -> Result { Ok(self.try_get_u64("enable_collect_column_statistics")? != 0) } diff --git a/src/query/sql/src/planner/binder/ddl/account.rs b/src/query/sql/src/planner/binder/ddl/account.rs index 0226dea217557..65aa547cc75f3 100644 --- a/src/query/sql/src/planner/binder/ddl/account.rs +++ b/src/query/sql/src/planner/binder/ddl/account.rs @@ -194,6 +194,7 @@ impl Binder { AccountMgrLevel::UDF(udf) => Ok(GrantObject::UDF(udf.clone())), AccountMgrLevel::Stage(stage) => Ok(GrantObject::Stage(stage.clone())), AccountMgrLevel::Warehouse(w) => Ok(GrantObject::Warehouse(w.clone())), + AccountMgrLevel::Connection(c) => Ok(GrantObject::Connection(c.clone())), } } @@ -255,6 +256,7 @@ impl Binder { AccountMgrLevel::UDF(udf) => Ok(vec![GrantObject::UDF(udf.clone())]), AccountMgrLevel::Stage(stage) => Ok(vec![GrantObject::Stage(stage.clone())]), AccountMgrLevel::Warehouse(w) => Ok(vec![GrantObject::Warehouse(w.clone())]), + AccountMgrLevel::Connection(c) => Ok(vec![GrantObject::Connection(c.clone())]), } } @@ -488,6 +490,9 @@ impl Binder { GrantObjectName::Warehouse(name) => { format!("SELECT * FROM show_grants('warehouse', '{}')", name) } + GrantObjectName::Connection(name) => { + format!("SELECT * FROM show_grants('connection', '{}')", name) + } }; let (show_limit, limit_str) = get_show_options(show_option, Some("name".to_string())); diff --git a/src/query/users/src/user_mgr.rs b/src/query/users/src/user_mgr.rs index 42b44c5ba3671..4fae0fc503661 100644 --- a/src/query/users/src/user_mgr.rs +++ b/src/query/users/src/user_mgr.rs @@ -201,9 +201,13 @@ impl UserApiProvider { user.username ))); } - if let GrantObject::Warehouse(_) = object { + + if matches!( + object, + GrantObject::Warehouse(_) | GrantObject::Connection(_) + ) { return Err(ErrorCode::IllegalUser(format!( - "Cannot grant warehouse privileges to user `{}`", + "Cannot grant warehouse|connection privileges to user `{}`", user.username ))); } diff --git a/src/query/users/src/visibility_checker.rs b/src/query/users/src/visibility_checker.rs index 63b42fee51a57..ab00e97fafa54 100644 --- a/src/query/users/src/visibility_checker.rs +++ b/src/query/users/src/visibility_checker.rs @@ -32,6 +32,7 @@ use itertools::Itertools; pub struct GrantObjectVisibilityChecker { granted_global_udf: bool, granted_global_ws: bool, + granted_global_c: bool, granted_global_db_table: bool, granted_global_stage: bool, granted_global_read_stage: bool, @@ -46,6 +47,7 @@ pub struct GrantObjectVisibilityChecker { granted_write_stages: HashSet, granted_read_stages: HashSet, granted_ws: HashSet, + granted_c: HashSet, } impl GrantObjectVisibilityChecker { @@ -56,6 +58,7 @@ impl GrantObjectVisibilityChecker { ) -> Self { let mut granted_global_udf = false; let mut granted_global_ws = false; + let mut granted_global_c = false; let mut granted_global_db_table = false; let mut granted_global_stage = false; let mut granted_global_read_stage = false; @@ -63,6 +66,7 @@ impl GrantObjectVisibilityChecker { let mut granted_tables = HashSet::new(); let mut granted_udfs = HashSet::new(); let mut granted_ws = HashSet::new(); + let mut granted_c = HashSet::new(); let mut granted_write_stages = HashSet::new(); let mut granted_read_stages = HashSet::new(); let mut extra_databases = HashSet::new(); @@ -108,6 +112,15 @@ impl GrantObjectVisibilityChecker { }, ); + check_privilege( + &mut granted_global_c, + ent.privileges().iter(), + |privilege| { + UserPrivilegeSet::available_privileges_on_connection(false) + .has_privilege(privilege) + }, + ); + check_privilege( &mut granted_global_stage, ent.privileges().iter(), @@ -181,6 +194,9 @@ impl GrantObjectVisibilityChecker { GrantObject::Warehouse(w) => { granted_ws.insert(w.to_string()); } + GrantObject::Connection(c) => { + granted_c.insert(c.to_string()); + } } } } @@ -212,12 +228,16 @@ impl GrantObjectVisibilityChecker { OwnershipObject::Warehouse { id } => { granted_ws.insert(id.to_string()); } + OwnershipObject::Connection { name } => { + granted_c.insert(name.to_string()); + } } } Self { granted_global_udf, granted_global_ws, + granted_global_c, granted_global_db_table, granted_global_stage, granted_global_read_stage, @@ -235,6 +255,7 @@ impl GrantObjectVisibilityChecker { ("default".to_string(), "system".to_string()), ]), granted_ws, + granted_c, } } @@ -282,6 +303,16 @@ impl GrantObjectVisibilityChecker { false } + pub fn check_connection_visibility(&self, name: &str) -> bool { + if self.granted_global_c { + return true; + } + if self.granted_c.contains(name) { + return true; + } + false + } + pub fn check_database_visibility(&self, catalog: &str, db: &str, db_id: u64) -> bool { // skip information_schema privilege check if db.to_lowercase() == "information_schema" || db.to_lowercase() == "system" { diff --git a/tests/compat_fuse/compat-logictest/rbac/fuse_compat_read b/tests/compat_fuse/compat-logictest/rbac/fuse_compat_read index 368b19754a146..4a99ee45146c9 100644 --- a/tests/compat_fuse/compat-logictest/rbac/fuse_compat_read +++ b/tests/compat_fuse/compat-logictest/rbac/fuse_compat_read @@ -1,3 +1,9 @@ +statement ok +GRANT access connection on connection c2 to role 'role2'; + +statement ok +GRANT ownership on connection c2 to role 'role2'; + statement ok show grants for role 'role1'; diff --git a/tests/compat_fuse/compat-logictest/rbac/fuse_compat_write b/tests/compat_fuse/compat-logictest/rbac/fuse_compat_write index fd872a358dddf..11177fdeb5a9c 100644 --- a/tests/compat_fuse/compat-logictest/rbac/fuse_compat_write +++ b/tests/compat_fuse/compat-logictest/rbac/fuse_compat_write @@ -10,6 +10,12 @@ grant select on default.* to role 'role1'; statement ok create function a as (a) -> (a+1); +statement ok +create connection c1 storage_type = 's3' access_key_id ='11' secret_access_key ='11' ENDPOINT_URL='http://127.0.0.1:9900'; + +statement ok +create connection c2 storage_type = 's3' access_key_id ='22' secret_access_key ='22' ENDPOINT_URL='http://127.0.0.1:9900'; + statement ok drop role if exists 'role2'; @@ -21,3 +27,6 @@ GRANT OWNERSHIP on udf a to role 'role1'; statement ok GRANT create warehouse on *.* to role 'role1'; + +statement ok +GRANT create connection on *.* to role 'role1'; diff --git a/tests/sqllogictests/suites/base/05_ddl/05_0017_ddl_grant_role.test b/tests/sqllogictests/suites/base/05_ddl/05_0017_ddl_grant_role.test index ab4d772a32af1..71fa7d3e6e959 100644 --- a/tests/sqllogictests/suites/base/05_ddl/05_0017_ddl_grant_role.test +++ b/tests/sqllogictests/suites/base/05_ddl/05_0017_ddl_grant_role.test @@ -256,6 +256,18 @@ create user u1 identified by '123'; statement error 2218 grant usage on warehouse a to u1; +statement ok +drop connection if exists c1; + +statement ok +CREATE CONNECTION c1 STORAGE_TYPE='azblob' ENDPOINT_URL='http://s3.amazonaws.com'; + +statement error 2218 +grant access connection on connection c1 to u1; + +statement ok +drop connection if exists c1; + statement ok GRANT create warehouse on *.* to role 'role1'; diff --git a/tests/suites/0_stateless/18_rbac/18_0001_udf_priv.result b/tests/suites/0_stateless/18_rbac/18_0001_udf_priv.result index 34b3b7f21d686..b4efe8bef392e 100644 --- a/tests/suites/0_stateless/18_rbac/18_0001_udf_priv.result +++ b/tests/suites/0_stateless/18_rbac/18_0001_udf_priv.result @@ -1,49 +1,49 @@ === test UDF priv === Only Has Privilege on f2 === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 2 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. select 1 from (select f2(f1(10))); -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. select * from system.one where f2(f1(1)); -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 NULL -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 2 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f1 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 2 === Only Has Privilege on f1 === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 2 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 NULL -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 2 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF f2 for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 2 === Has Privilege on f1, f2 === 1 @@ -69,4 +69,4 @@ Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is requ 200 2 4 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF b for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF b for user 'test-user'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. diff --git a/tests/suites/0_stateless/18_rbac/18_0002_ownership_cover.result b/tests/suites/0_stateless/18_rbac/18_0002_ownership_cover.result index 463053b458bd4..175753899df2b 100644 --- a/tests/suites/0_stateless/18_rbac/18_0002_ownership_cover.result +++ b/tests/suites/0_stateless/18_rbac/18_0002_ownership_cover.result @@ -12,8 +12,8 @@ 0 200 === test role r_0002 === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF a for user 'owner'@'%' with roles [public,r_0002]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE hello for user 'owner'@'%' with roles [public,r_0002]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Usage] is required on UDF a for user 'owner'@'%' with roles [public,r_0002]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE hello for user 'owner'@'%' with roles [public,r_0002]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. Error: APIError: QueryFailed: [1063]Permission denied: privilege [Select] is required on 'default'.'d_0002'.'t' for user 'owner'@'%' with roles [public,r_0002] === test ownership: show stmt === public 0 false false diff --git a/tests/suites/0_stateless/18_rbac/18_0007_privilege_access.result b/tests/suites/0_stateless/18_rbac/18_0007_privilege_access.result index b0ba716944acf..11325834ddc37 100644 --- a/tests/suites/0_stateless/18_rbac/18_0007_privilege_access.result +++ b/tests/suites/0_stateless/18_rbac/18_0007_privilege_access.result @@ -106,10 +106,10 @@ Error: APIError: QueryFailed: [1063]Permission denied: No privilege on table roo Error: APIError: QueryFailed: [1063]Permission denied: No privilege on table root_table for user b. 1 1 Error: APIError: QueryFailed: [1063]Permission denied: privilege [Select] is required on 'default'.'default'.'t1' for user 'b'@'%' with roles [public] -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. Error: APIError: QueryFailed: [1063]Permission denied: privilege [Select] is required on 'default'.'default'.'t' for user 'b'@'%' with roles [public] -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. Error: APIError: QueryFailed: [1063]Permission denied: privilege [Select] is required on 'default'.'default'.'t1' for user 'b'@'%' with roles [public] 0 1 diff --git a/tests/suites/0_stateless/18_rbac/18_0009_set_role.result b/tests/suites/0_stateless/18_rbac/18_0009_set_role.result index 2e2deb128356d..d839f606d4ccb 100644 --- a/tests/suites/0_stateless/18_rbac/18_0009_set_role.result +++ b/tests/suites/0_stateless/18_rbac/18_0009_set_role.result @@ -40,8 +40,8 @@ testrole1 -- test 10: set default role as nonexisting_role, will fail Error: APIError: QueryFailed: [2206]Invalid role nonexistedrole for current session, available: public,testrole1,testrole2,testrole3 -- test 11: set secondary All | None, create object only check current role -Error: APIError: QueryFailed: [1063]Permission denied: privilege [CreateDatabase] is required on *.* for user 'test_c'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. -Error: APIError: QueryFailed: [1063]Permission denied: privilege [CreateDatabase] is required on *.* for user 'test_c'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [CreateDatabase] is required on *.* for user 'test_c'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [CreateDatabase] is required on *.* for user 'test_c'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. OWNERSHIP db_c ROLE role_c GRANT OWNERSHIP ON 'default'.'db_c'.* TO ROLE `role_c` OWNERSHIP db_d ROLE role_c GRANT OWNERSHIP ON 'default'.'db_d'.* TO ROLE `role_c` OWNERSHIP db_e ROLE public GRANT OWNERSHIP ON 'default'.'db_e'.* TO ROLE `public` diff --git a/tests/suites/0_stateless/18_rbac/18_0012_temp_table.result b/tests/suites/0_stateless/18_rbac/18_0012_temp_table.result index c0989c15f9f59..cac8650b93a46 100644 --- a/tests/suites/0_stateless/18_rbac/18_0012_temp_table.result +++ b/tests/suites/0_stateless/18_rbac/18_0012_temp_table.result @@ -5,7 +5,7 @@ >>>> create or replace database test >>>> grant role role1 to owner mysql: [Warning] Using a password on the command line interface can be insecure. -ERROR 1105 (HY000) at line 1: PermissionDenied. Code: 1063, Text = Permission denied: privilege [Create] is required on 'default'.'test'.* for user 'owner'@'%' with roles [role1]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage.. +ERROR 1105 (HY000) at line 1: PermissionDenied. Code: 1063, Text = Permission denied: privilege [Create] is required on 'default'.'test'.* for user 'owner'@'%' with roles [role1]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. mysql: [Warning] Using a password on the command line interface can be insecure. 2 1 diff --git a/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.result b/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.result new file mode 100644 index 0000000000000..e6a68ae5b327b --- /dev/null +++ b/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.result @@ -0,0 +1,65 @@ +=== OLD LOGIC: user has super privileges can operator all connections with enable_experimental_connection_privilege_check=0 === +=== TEST USER A WITH SUPER PRIVILEGES === +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +=== NEW LOGIC: user has super privileges can operator all connections with enable_experimental_connection_privilege_check=1 === +=== TEST USER A WITH SUPER PRIVILEGES === +--- CREATE 3 CONNECTIONS WILL SUCCESS --- +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +=== TEST USER B, C WITH OWNERSHIP OR CREATE/ACCESS PRIVILEGES PRIVILEGES === +--- USER b failed to create conn c1 because current role is public, can not create --- +Error: APIError: QueryFailed: [1063]Permission denied: privilege [CreateConnection] is required on *.* for user 'b'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +--- success, c1,c2,c3 owner role is role1 --- +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +--- transform c2'ownership from role1 to role2 --- +--- USER failed to desc conn c2, c2 role is role2 --- +Error: APIError: QueryFailed: [1063]Permission denied: privilege [AccessConnection] is required on CONNECTION c2 for user 'b'@'%' with roles [public,role1]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +c1 azblob endpoint_url=******com +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +--- only return one row c2 --- +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +--- grant access connection c1 to role3 --- +c1 azblob endpoint_url=******com +--- grant access connection c3 to role3 --- +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +--- return three rows c1,2,3 --- +c1 azblob endpoint_url=******com +c2 s3 access_key_id=******min endpoint_url=******00/ region=******uto secret_access_key=******min +c3 s3 access_key_id=c3 endpoint_url=******00/ region=******uto secret_access_key=c3 +--- user b can not drop connection c2 --- +Error: APIError: QueryFailed: [1063]Permission denied: privilege [AccessConnection] is required on CONNECTION c2 for user 'b'@'%' with roles [public,role1]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +1 +Error: APIError: QueryFailed: [1063]Permission denied: privilege ACCESS CONNECTION is required on connection c2 for user b +--- revoke access connection from role3 , thne user c can not drop/use connection c1,3 --- +1 +1 +Error: APIError: QueryFailed: [1063]Permission denied: privilege ACCESS CONNECTION is required on connection c1 for user c +Error: APIError: QueryFailed: [1063]Permission denied: privilege ACCESS CONNECTION is required on connection c3 for user c +Error: APIError: QueryFailed: [1063]Permission denied: privilege [AccessConnection] is required on CONNECTION c1 for user 'c'@'%' with roles [public,role2,role3]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [AccessConnection] is required on CONNECTION c3 for user 'c'@'%' with roles [public,role2,role3]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +--- user b can drop/use connection c1,3 --- +invalid input parameter (protocol from connection_name=c1 (azblob) not match with uri protocol (s3).) +Permission denied: privilege [Super] is required on *.* for user 'b'@'%' with roles [role1]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +OWNERSHIP c1 NULL ROLE role1 +OWNERSHIP c3 NULL ROLE role1 +CREATE CONNECTION *.* NULL ROLE role1 GRANT CREATE CONNECTION ON *.* TO ROLE `role1` +OWNERSHIP c3 NULL ROLE role1 GRANT OWNERSHIP ON CONNECTION c3 TO ROLE `role1` +--- user c can drop/use connection c2 --- +Permission denied: privilege [Super] is required on *.* for user 'c'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. +OWNERSHIP c2 NULL ROLE role2 GRANT OWNERSHIP ON CONNECTION c2 TO ROLE `role2` +OWNERSHIP c2 NULL ROLE role2 diff --git a/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.sh b/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.sh new file mode 100755 index 0000000000000..ad9b37220cf90 --- /dev/null +++ b/tests/suites/0_stateless/18_rbac/18_0015_connection_rbac.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +. "$CURDIR"/../../../shell_env.sh + + +export USER_A_CONNECT="bendsql --user=a --password=123 --host=${QUERY_MYSQL_HANDLER_HOST} --port ${QUERY_HTTP_HANDLER_PORT}" +export USER_B_CONNECT="bendsql --user=b --password=123 --host=${QUERY_MYSQL_HANDLER_HOST} --port ${QUERY_HTTP_HANDLER_PORT}" +export USER_C_CONNECT="bendsql --user=c --password=123 --host=${QUERY_MYSQL_HANDLER_HOST} --port ${QUERY_HTTP_HANDLER_PORT}" + +echo "=== OLD LOGIC: user has super privileges can operator all connections with enable_experimental_connection_privilege_check=0 ===" +echo "=== TEST USER A WITH SUPER PRIVILEGES ===" +echo "set global enable_experimental_connection_privilege_check=0;" | $BENDSQL_CLIENT_CONNECT +echo "drop stage if exists c1;" | $BENDSQL_CLIENT_CONNECT +echo "drop stage if exists c2;" | $BENDSQL_CLIENT_CONNECT +echo "drop stage if exists c3;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role1;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role2;" | $BENDSQL_CLIENT_CONNECT +echo "drop user if exists a;" | $BENDSQL_CLIENT_CONNECT +echo "drop user if exists b;" | $BENDSQL_CLIENT_CONNECT +echo "drop user if exists c;" | $BENDSQL_CLIENT_CONNECT +echo "create user a identified by '123';" | $BENDSQL_CLIENT_CONNECT +echo "grant super on *.* to a;" | $BENDSQL_CLIENT_CONNECT +echo "drop connection if exists c1;" | $BENDSQL_CLIENT_CONNECT +echo "drop connection if exists c2;" | $BENDSQL_CLIENT_CONNECT +echo "drop connection if exists c3;" | $BENDSQL_CLIENT_CONNECT + +echo "CREATE CONNECTION c1 STORAGE_TYPE='azblob' ENDPOINT_URL='http://s3.amazonaws.com';" | $USER_A_CONNECT +echo "create CONNECTION c2 STORAGE_TYPE = 's3' access_key_id='minioadmin' secret_access_key='minioadmin' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_A_CONNECT +echo "create CONNECTION c3 STORAGE_TYPE = 's3' access_key_id='c3' secret_access_key='c3' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_A_CONNECT +echo "DESC CONNECTION c1;" | $USER_A_CONNECT +echo "DESC CONNECTION c2;" | $USER_A_CONNECT +echo "DESC CONNECTION c3;" | $USER_A_CONNECT +echo "show connections;" | $USER_A_CONNECT +echo "drop connection if exists c1;" | $USER_A_CONNECT +echo "drop connection if exists c2;" | $USER_A_CONNECT +echo "drop connection if exists c3;" | $USER_A_CONNECT + + +echo "=== NEW LOGIC: user has super privileges can operator all connections with enable_experimental_connection_privilege_check=1 ===" +echo "=== TEST USER A WITH SUPER PRIVILEGES ===" +echo "set global enable_experimental_connection_privilege_check=1;" | $USER_A_CONNECT +echo "--- CREATE 3 CONNECTIONS WILL SUCCESS ---" +echo "CREATE CONNECTION c1 STORAGE_TYPE='azblob' ENDPOINT_URL='http://s3.amazonaws.com';" | $USER_A_CONNECT +echo "create CONNECTION c2 STORAGE_TYPE = 's3' access_key_id='minioadmin' secret_access_key='minioadmin' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_A_CONNECT +echo "create CONNECTION c3 STORAGE_TYPE = 's3' access_key_id='c3' secret_access_key='c3' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_A_CONNECT +echo "DESC CONNECTION c1;" | $USER_A_CONNECT +echo "DESC CONNECTION c2;" | $USER_A_CONNECT +echo "DESC CONNECTION c3;" | $USER_A_CONNECT +echo "show connections;" | $USER_A_CONNECT +echo "drop connection if exists c1;" | $USER_A_CONNECT +echo "drop connection if exists c2;" | $USER_A_CONNECT +echo "drop connection if exists c3;" | $USER_A_CONNECT + +echo "=== TEST USER B, C WITH OWNERSHIP OR CREATE/ACCESS PRIVILEGES PRIVILEGES ===" + +echo "drop role if exists role1;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role2;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role3;" | $BENDSQL_CLIENT_CONNECT +echo "create user b identified by '123';" | $BENDSQL_CLIENT_CONNECT +echo "create role role1;" | $BENDSQL_CLIENT_CONNECT +echo "create role role2;" | $BENDSQL_CLIENT_CONNECT +echo "create role role3;" | $BENDSQL_CLIENT_CONNECT +echo "grant create connection on *.* to role role1;" | $BENDSQL_CLIENT_CONNECT +echo "grant role role1 to b;" | $BENDSQL_CLIENT_CONNECT +echo "--- USER b failed to create conn c1 because current role is public, can not create ---" +echo "CREATE CONNECTION c1 STORAGE_TYPE='azblob' ENDPOINT_URL='http://s3.amazonaws.com';" | $USER_B_CONNECT + +echo "alter user b with default_role='role1';" | $BENDSQL_CLIENT_CONNECT + +echo "--- success, c1,c2,c3 owner role is role1 ---"; +echo "CREATE CONNECTION c1 STORAGE_TYPE='azblob' ENDPOINT_URL='http://s3.amazonaws.com';" | $USER_B_CONNECT +echo "create CONNECTION c2 STORAGE_TYPE = 's3' access_key_id='minioadmin' secret_access_key='minioadmin' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_B_CONNECT +echo "create CONNECTION c3 STORAGE_TYPE = 's3' access_key_id='c3' secret_access_key='c3' endpoint_url='http://127.0.0.1:9900/' region='auto';" | $USER_B_CONNECT +echo "DESC CONNECTION c1;" | $USER_B_CONNECT +echo "DESC CONNECTION c2;" | $USER_B_CONNECT +echo "DESC CONNECTION c3;" | $USER_B_CONNECT +echo "show connections;" | $USER_B_CONNECT + +echo "--- transform c2'ownership from role1 to role2 ---" +echo "grant ownership on connection c2 to role role2;" | $BENDSQL_CLIENT_CONNECT +echo "--- USER failed to desc conn c2, c2 role is role2 ---" +echo "DESC CONNECTION c2;" | $USER_B_CONNECT +echo "show connections;" | $USER_B_CONNECT + +echo "create user c identified by '123';" | $BENDSQL_CLIENT_CONNECT +echo "grant role role2 to c;" | $BENDSQL_CLIENT_CONNECT +echo "--- only return one row c2 ---" +echo "DESC CONNECTION c2;" | $USER_C_CONNECT +echo "show connections;" | $USER_C_CONNECT +echo "--- grant access connection c1 to role3 ---" +echo "grant access connection on connection c1 to role role3;" | $BENDSQL_CLIENT_CONNECT +echo "grant role role3 to c;" | $BENDSQL_CLIENT_CONNECT +echo "DESC CONNECTION c1;" | $USER_C_CONNECT +echo "--- grant access connection c3 to role3 ---" +echo "grant access connection on connection c3 to role role3;" | $BENDSQL_CLIENT_CONNECT +echo "DESC CONNECTION c3;" | $USER_C_CONNECT +echo "--- return three rows c1,2,3 ---" +echo "show connections;" | $USER_C_CONNECT + +echo "--- user b can not drop connection c2 ---" +echo "drop connection if exists c2;" | $USER_B_CONNECT +curl -s -u "b:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c2')\"}" | jq -r '.error.message' |grep 'Permission denied: privilege AccessConnection' |wc -l +echo "show grants on connection c2;" | $USER_B_CONNECT + +echo "--- revoke access connection from role3 , thne user c can not drop/use connection c1,3 ---" +echo "revoke access connection on connection c1 from role role3;" | $BENDSQL_CLIENT_CONNECT +echo "revoke access connection on connection c3 from role role3;" | $BENDSQL_CLIENT_CONNECT +curl -s -u "c:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c1');\"}" | jq -r '.error.message' |grep 'Permission denied: privilege AccessConnection' |wc -l +curl -s -u "c:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c3')\"}" | jq -r '.error.message' |grep 'Permission denied: privilege AccessConnection' |wc -l +echo "show grants on connection c1;" | $USER_C_CONNECT +echo "show grants on connection c3;" | $USER_C_CONNECT +echo "drop connection if exists c1;" | $USER_C_CONNECT +echo "drop connection if exists c3;" | $USER_C_CONNECT + +echo "--- user b can drop/use connection c1,3 ---" +curl -s -u "b:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c1');\"}" | jq -r '.error.message' +curl -s -u "b:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c3')\"}" | jq -r '.error.message' +echo "show grants on connection c1;" | $USER_B_CONNECT +echo "show grants on connection c3;" | $USER_B_CONNECT +echo "drop connection if exists c1;" | $USER_B_CONNECT +echo "show grants for role role1;" | $USER_B_CONNECT +echo "drop connection if exists c3;" | $USER_B_CONNECT + +echo "--- user c can drop/use connection c2 ---" +curl -s -u "c:123" -XPOST "http://$QUERY_MYSQL_HANDLER_HOST:$QUERY_HTTP_HANDLER_PORT/v1/query" -H 'Content-Type: application/json' -d "{\"sql\": \"CREATE STAGE my_s3_stage URL = 's3://databend-toronto' CONNECTION = (CONNECTION_NAME = 'c2')\"}" | jq -r '.error.message' +echo "show grants for role role2;" | $USER_C_CONNECT +echo "show grants on connection c2;" | $USER_C_CONNECT +echo "drop connection if exists c2;" | $USER_C_CONNECT +echo "show grants for role role2;" | $USER_C_CONNECT + +echo "drop user if exists a;" | $BENDSQL_CLIENT_CONNECT +echo "drop user if exists b;" | $BENDSQL_CLIENT_CONNECT +echo "drop user if exists c;" | $BENDSQL_CLIENT_CONNECT + +echo "drop stage if exists c1;" | $BENDSQL_CLIENT_CONNECT +echo "drop stage if exists c2;" | $BENDSQL_CLIENT_CONNECT +echo "drop stage if exists c3;" | $BENDSQL_CLIENT_CONNECT + +echo "drop role if exists role1;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role2;" | $BENDSQL_CLIENT_CONNECT +echo "drop role if exists role3;" | $BENDSQL_CLIENT_CONNECT +echo "unset global enable_experimental_connection_privilege_check;" | $BENDSQL_CLIENT_CONNECT diff --git a/tests/suites/1_stateful/00_stage/00_0012_stage_priv.result b/tests/suites/1_stateful/00_stage/00_0012_stage_priv.result index 9ce80e069225b..b412c1c7db548 100644 --- a/tests/suites/1_stateful/00_stage/00_0012_stage_priv.result +++ b/tests/suites/1_stateful/00_stage/00_0012_stage_priv.result @@ -9,26 +9,26 @@ 2 2 ==== check internal stage write priv === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s2 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s2 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. Error: APIError: QueryFailed: [1063]Permission denied: privilege [Select] is required on 'default'.'default'.'test_table' for user 'u1'@'%' with roles [public] 20 160 1 ==== check external stage priv === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s1 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s1 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 20 160 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s1 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s1 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. csv/data_UUID_0000_00000000.csv 20 0 NULL NULL ==== check internal stage read priv === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s2 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s2 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. data_UUID_0000_00000000.csv 20 0 NULL NULL === check presign === -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE presign_stage for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE presign_stage for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 000 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE presign_stage for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE presign_stage for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 000 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s3 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Write] is required on STAGE s3 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. 1 1 -Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage. +Error: APIError: QueryFailed: [1063]Permission denied: privilege [Read] is required on STAGE s3 for user 'u1'@'%' with roles [public]. Note: Please ensure that your current role have the appropriate permissions to create a new Warehouse|Database|Table|UDF|Stage|Connection. Error: APIError: QueryFailed: [1063]Permission denied: privilege READ is required on stage s3 for user 'u1'@'%' Error: APIError: QueryFailed: [1063]Permission denied: privilege READ is required on stage s3 for user 'u1'@'%' Error: APIError: QueryFailed: [1063]Permission denied: privilege READ is required on stage s3 for user 'u1'@'%'