Skip to content

Commit c893860

Browse files
authored
feat(rbac): User only support select or drop system_history (#18281)
* feat(rbac): User only support select or drop system_history * add select privilege test
1 parent e582dde commit c893860

File tree

11 files changed

+180
-25
lines changed

11 files changed

+180
-25
lines changed

src/meta/app/src/principal/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub use user_defined_function::UserDefinedFunction;
103103
pub use user_grant::GrantEntry;
104104
pub use user_grant::GrantObject;
105105
pub use user_grant::UserGrantSet;
106+
pub use user_grant::SENSITIVE_SYSTEM_RESOURCE;
106107
pub use user_grant::SYSTEM_TABLES_ALLOW_LIST;
107108
pub use user_identity::UserIdentity;
108109
pub use user_info::UserInfo;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub const SYSTEM_TABLES_ALLOW_LIST: [&str; 21] = [
4848
"indexes",
4949
];
5050

51+
pub const SENSITIVE_SYSTEM_RESOURCE: &str = "system_history";
52+
5153
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
5254
pub enum GrantObject {
5355
Global,

src/query/service/src/history_tables/alter_table.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ mod tests {
178178

179179
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
180180
async fn test_get_alter_table_sql() -> Result<(), ErrorCode> {
181-
let test_fixture = TestFixture::setup().await?;
181+
let test_fixture = TestFixture::setup_with_history_log().await?;
182182
let ctx = test_fixture.new_query_ctx().await?;
183183
let create_db = "CREATE DATABASE system_history";
184184
let create_table = "CREATE TABLE system_history.test_table_alter (id INT, name String)";
@@ -214,7 +214,7 @@ mod tests {
214214
// - The node with the older schema version is restarted.
215215
// - Upon restart, the older node should not attempt to alter the table again.
216216

217-
let test_fixture = TestFixture::setup().await?;
217+
let test_fixture = TestFixture::setup_with_history_log().await?;
218218
let ctx = test_fixture.new_query_ctx().await?;
219219
let create_db = "CREATE DATABASE system_history";
220220
let create_table =
@@ -246,7 +246,7 @@ mod tests {
246246
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
247247
async fn test_should_reset_table_not_exists() -> Result<(), ErrorCode> {
248248
// Test: When log_history table doesn't exist, should_reset should return false
249-
let test_fixture = TestFixture::setup().await?;
249+
let test_fixture = TestFixture::setup_with_history_log().await?;
250250
let ctx = test_fixture.new_query_ctx().await?;
251251

252252
// Don't create the table, it should not exist
@@ -260,7 +260,7 @@ mod tests {
260260
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
261261
async fn test_should_reset_both_inner() -> Result<(), ErrorCode> {
262262
// Test: When log_history table is internal, config also internal, should_reset should return false
263-
let test_fixture = TestFixture::setup().await?;
263+
let test_fixture = TestFixture::setup_with_history_log().await?;
264264
let ctx = test_fixture.new_query_ctx().await?;
265265

266266
// Create system_history database and internal log_history table
@@ -279,7 +279,7 @@ mod tests {
279279
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
280280
async fn test_should_reset_inner_to_external() -> Result<(), ErrorCode> {
281281
// Test Condition: Internal -> External conversion should trigger reset
282-
let test_fixture = TestFixture::setup().await?;
282+
let test_fixture = TestFixture::setup_with_history_log().await?;
283283
let ctx = test_fixture.new_query_ctx().await?;
284284

285285
// Create system_history database and internal log_history table

src/query/service/src/history_tables/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ mod alter_table;
1616
mod external;
1717
mod global_history_log;
1818
mod meta;
19-
mod session;
19+
pub mod session;
2020

2121
pub use global_history_log::GlobalHistoryLog;

src/query/service/src/history_tables/session.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,18 @@ use std::sync::Arc;
1616

1717
use databend_common_catalog::session_type::SessionType;
1818
use databend_common_exception::Result;
19-
use databend_common_meta_app::principal::GrantObject;
2019
use databend_common_meta_app::principal::UserInfo;
21-
use databend_common_meta_app::principal::UserPrivilegeType;
2220
use databend_common_users::BUILTIN_ROLE_ACCOUNT_ADMIN;
2321

2422
use crate::sessions::Session;
2523
use crate::sessions::SessionManager;
2624

2725
/// Create a user for history log with necessary privileges
2826
pub fn get_history_log_user(tenant_id: &str, cluster_id: &str) -> UserInfo {
29-
let mut user = UserInfo::new_no_auth(
27+
UserInfo::new_no_auth(
3028
format!("{}-{}-history-log", tenant_id, cluster_id).as_str(),
3129
"0.0.0.0",
32-
);
33-
user.grants.grant_privileges(
34-
&GrantObject::Global,
35-
UserPrivilegeType::CreateDatabase.into(),
36-
);
37-
user
30+
)
3831
}
3932

4033
/// Create a dummy session for history log

src/query/service/src/interpreters/access/privilege_access.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use std::sync::Arc;
1717

1818
use databend_common_base::base::GlobalInstance;
1919
use databend_common_catalog::catalog::Catalog;
20+
use databend_common_catalog::catalog::CATALOG_DEFAULT;
2021
use databend_common_catalog::plan::DataSourceInfo;
2122
use databend_common_catalog::table_context::TableContext;
23+
use databend_common_config::GlobalConfig;
2224
use databend_common_exception::ErrorCode;
2325
use databend_common_exception::Result;
2426
use databend_common_management::RoleApi;
@@ -31,6 +33,7 @@ use databend_common_meta_app::principal::StageType;
3133
use databend_common_meta_app::principal::UserGrantSet;
3234
use databend_common_meta_app::principal::UserPrivilegeSet;
3335
use databend_common_meta_app::principal::UserPrivilegeType;
36+
use databend_common_meta_app::principal::SENSITIVE_SYSTEM_RESOURCE;
3437
use databend_common_meta_app::principal::SYSTEM_TABLES_ALLOW_LIST;
3538
use databend_common_meta_app::tenant::Tenant;
3639
use databend_common_meta_types::seq_value::SeqV;
@@ -46,6 +49,7 @@ use databend_common_users::UserApiProvider;
4649
use databend_enterprise_resources_management::ResourcesManagement;
4750
use databend_storages_common_table_meta::table::OPT_KEY_TEMP_PREFIX;
4851

52+
use crate::history_tables::session::get_history_log_user;
4953
use crate::interpreters::access::AccessChecker;
5054
use crate::sessions::QueryContext;
5155
use crate::sessions::Session;
@@ -159,13 +163,44 @@ impl PrivilegeAccess {
159163
Ok(Some(object))
160164
}
161165

166+
fn access_system_history(
167+
&self,
168+
catalog_name: &str,
169+
db_name: &str,
170+
privilege: UserPrivilegeType,
171+
) -> Result<()> {
172+
let cluster_id = GlobalConfig::instance().query.cluster_id.clone();
173+
let tenant_id = GlobalConfig::instance().query.tenant_id.clone();
174+
if get_history_log_user(tenant_id.tenant_name(), &cluster_id).identity()
175+
== self.ctx.get_current_user()?.identity()
176+
{
177+
return Ok(());
178+
}
179+
if catalog_name == CATALOG_DEFAULT
180+
&& db_name.eq_ignore_ascii_case(SENSITIVE_SYSTEM_RESOURCE)
181+
&& !matches!(
182+
privilege,
183+
UserPrivilegeType::Select | UserPrivilegeType::Drop
184+
)
185+
{
186+
return Err(ErrorCode::PermissionDenied(
187+
format!(
188+
"Permission Denied: Operation '{:?}' on database 'default.system_history' is not allowed. This sensitive system resource only supports 'SELECT' and 'DROP'",
189+
privilege
190+
),
191+
));
192+
}
193+
Ok(())
194+
}
195+
162196
async fn validate_db_access(
163197
&self,
164198
catalog_name: &str,
165199
db_name: &str,
166200
privileges: UserPrivilegeType,
167201
if_exists: bool,
168202
) -> Result<()> {
203+
self.access_system_history(catalog_name, db_name, privileges)?;
169204
let tenant = self.ctx.get_tenant();
170205
let check_current_role_only = match privileges {
171206
// create table/stream need check db's Create Privilege
@@ -270,6 +305,8 @@ impl PrivilegeAccess {
270305
return Ok(());
271306
}
272307

308+
self.access_system_history(catalog_name, db_name, privilege)?;
309+
273310
if self.ctx.is_temp_table(catalog_name, db_name, table_name) {
274311
return Ok(());
275312
}

src/query/service/src/interpreters/interpreter_privilege_grant.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use log::error;
3232
use log::info;
3333

3434
use crate::interpreters::common::validate_grant_object_exists;
35+
use crate::interpreters::util::check_system_history;
3536
use crate::interpreters::Interpreter;
3637
use crate::pipelines::PipelineBuildResult;
3738
use crate::sessions::QueryContext;
@@ -58,6 +59,7 @@ impl GrantPrivilegeInterpreter {
5859
GrantObject::Database(_, db_name) => {
5960
let catalog_name = catalog_name.unwrap();
6061
let catalog = self.ctx.get_catalog(&catalog_name).await?;
62+
check_system_history(&catalog, db_name)?;
6163
let db_id = catalog
6264
.get_database(tenant, db_name)
6365
.await?
@@ -72,6 +74,7 @@ impl GrantPrivilegeInterpreter {
7274
GrantObject::Table(_, db_name, table_name) => {
7375
let catalog_name = catalog_name.unwrap();
7476
let catalog = self.ctx.get_catalog(&catalog_name).await?;
77+
check_system_history(&catalog, db_name)?;
7578
let db_id = catalog
7679
.get_database(tenant, db_name)
7780
.await?
@@ -88,15 +91,25 @@ impl GrantPrivilegeInterpreter {
8891
table_id,
8992
})
9093
}
91-
GrantObject::TableById(_, db_id, table_id) => Ok(OwnershipObject::Table {
92-
catalog_name: catalog_name.unwrap(),
93-
db_id: *db_id,
94-
table_id: *table_id,
95-
}),
96-
GrantObject::DatabaseById(_, db_id) => Ok(OwnershipObject::Database {
97-
catalog_name: catalog_name.unwrap(),
98-
db_id: *db_id,
99-
}),
94+
GrantObject::TableById(_, db_id, table_id) => {
95+
let catalog_name = catalog_name.unwrap();
96+
let catalog = self.ctx.get_catalog(&catalog_name).await?;
97+
check_system_history(&catalog, &catalog.get_db_name_by_id(*db_id).await?)?;
98+
Ok(OwnershipObject::Table {
99+
catalog_name,
100+
db_id: *db_id,
101+
table_id: *table_id,
102+
})
103+
},
104+
GrantObject::DatabaseById(_, db_id) => {
105+
let catalog_name = catalog_name.unwrap();
106+
let catalog = self.ctx.get_catalog(&catalog_name).await?;
107+
check_system_history(&catalog, &catalog.get_db_name_by_id(*db_id).await?)?;
108+
Ok(OwnershipObject::Database {
109+
catalog_name,
110+
db_id: *db_id,
111+
})
112+
},
100113
GrantObject::Stage(name) => Ok(OwnershipObject::Stage {
101114
name: name.to_string(),
102115
}),

src/query/service/src/interpreters/interpreter_privilege_revoke.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl Interpreter for RevokePrivilegeInterpreter {
8787
.revoke_privileges_from_role(&tenant, &role, object, plan.priv_types)
8888
.await?;
8989
}
90-
// grant_ownership and grant_privileges_to_role will modify the kv in meta.
90+
// revoke_privileges_from_role will modify the kv in meta.
9191
// So we need invalidate the role cache.
9292
RoleCacheManager::instance().invalidate_cache(&tenant);
9393
}

src/query/service/src/interpreters/util.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ use databend_common_ast::ast::Expr;
1818
use databend_common_ast::parser::parse_expr;
1919
use databend_common_ast::parser::tokenize_sql;
2020
use databend_common_ast::parser::Dialect;
21+
use databend_common_catalog::catalog::Catalog;
2122
use databend_common_exception::ErrorCode;
2223
use databend_common_expression::ComputedExpr;
2324
use databend_common_expression::DataBlock;
2425
use databend_common_expression::DataSchemaRef;
2526
use databend_common_expression::Scalar;
2627
use databend_common_expression::TableSchemaRef;
2728
use databend_common_meta_app::principal::UserInfo;
29+
use databend_common_meta_app::principal::SENSITIVE_SYSTEM_RESOURCE;
2830
use databend_common_script::ir::ColumnAccess;
2931
use databend_common_script::Client;
3032
use databend_common_sql::Planner;
@@ -34,6 +36,18 @@ use itertools::Itertools;
3436
use crate::interpreters::InterpreterFactory;
3537
use crate::sessions::QueryContext;
3638

39+
pub fn check_system_history(
40+
catalog: &Arc<dyn Catalog>,
41+
db_name: &str,
42+
) -> databend_common_exception::Result<()> {
43+
if !catalog.is_external() && db_name.eq_ignore_ascii_case(SENSITIVE_SYSTEM_RESOURCE) {
44+
return Err(ErrorCode::IllegalGrant(
45+
"Can not modify system_history ownership",
46+
));
47+
}
48+
Ok(())
49+
}
50+
3751
#[allow(clippy::type_complexity)]
3852
pub fn generate_desc_schema(
3953
schema: TableSchemaRef,

src/query/service/src/test_kits/fixture.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,28 @@ impl TestFixture {
147147
Self::setup_with_custom(OSSSetup { config }).await
148148
}
149149

150+
/// Create a new TestFixture with default config.
151+
pub async fn setup_with_history_log() -> Result<TestFixture> {
152+
let config = ConfigBuilder::create().config();
153+
let conf = OSSSetup { config }.setup().await?;
154+
155+
use crate::history_tables::session::create_session;
156+
let default_session =
157+
create_session(conf.query.tenant_id.tenant_name(), &conf.query.cluster_id).await?;
158+
let default_ctx = default_session.create_query_context().await?;
159+
160+
let random_prefix: String = Uuid::new_v4().simple().to_string();
161+
let thread_name = std::thread::current().name().unwrap().to_string();
162+
let guard = TestGuard::new(thread_name.clone());
163+
Ok(Self {
164+
default_ctx,
165+
default_session,
166+
conf,
167+
prefix: random_prefix,
168+
_guard: guard,
169+
})
170+
}
171+
150172
pub async fn setup_with_segment_cache_bytes(segment_bytes: u64) -> Result<TestFixture> {
151173
let config = ConfigBuilder::create()
152174
.enable_table_meta_cache()

0 commit comments

Comments
 (0)