diff --git a/src/access_control.rs b/src/access_control.rs index 425d0170366bb293b2669e376a40a4dd321fa8c9..3d298427d9f3cb0ae8fcfe6c428fd44909a36d56 100644 --- a/src/access_control.rs +++ b/src/access_control.rs @@ -43,7 +43,7 @@ use tarantool::{ tuple::Encode, }; -use crate::storage::ClusterwideTable; +use crate::storage::{ClusterwideTable, SPACE_ID_INTERNAL_MAX}; use crate::traft::error::Error; use crate::traft::op::Dml; use crate::{ @@ -221,8 +221,21 @@ fn box_access_check_ddl_as_user( } fn access_check_dml(dml: &Dml, as_user: UserId) -> tarantool::Result<()> { + let space_id = dml.space(); + if space_id <= SPACE_ID_INTERNAL_MAX && as_user != ADMIN_ID && as_user != PICO_SERVICE_ID { + let table_name = ClusterwideTable::try_from(space_id) + .map_or(format!("id={}", space_id), |table| table.name().to_string()); + + tarantool::set_error!( + tarantool::error::TarantoolErrorCode::AccessDenied, + "Write access to table '{}' is denied for all users", + table_name + ); + return Err(tarantool::error::TarantoolError::last().into()); + } + let _su = session::su(as_user)?; - box_access_check_space(dml.space(), PrivType::Write) + box_access_check_space(space_id, PrivType::Write) } /// This function performs access control checks that are identical to ones performed in @@ -672,33 +685,6 @@ fn access_check_acl( } } -// Non-admin users are prohibited from performing DML operations on system tables -// because of the shared namespace for user and catalog tables in Tarantool. -// Allowing write access to any table via 'GRANT WRITE TABLE TO ..' -// also grants access to catalog tables. Such changes can cause security issues, -// and without this check would also grant access to catalog tables. -// Admin users have limited write access to some system tables. -// However, most tables are allowed because Picodata runs under admin to manipulate with the catalog -// See PROHIBITED_TABLES in cas.rs for details. -pub fn check_system_tables_access(dml: &Dml, as_user: UserId) -> tarantool::Result<()> { - let space = dml.space(); - let Ok(table) = &ClusterwideTable::try_from(space) else { - return Ok(()); - }; - - let sys_user = user_by_id(as_user)?; - if as_user != ADMIN_ID && as_user != PICO_SERVICE_ID { - return Err(make_access_denied( - "Write access", - PicoSchemaObjectType::Table, - table, - sys_user.name, - )); - } - - Ok(()) -} - pub(super) fn access_check_op( storage: &Clusterwide, op: &Op, @@ -708,13 +694,11 @@ pub(super) fn access_check_op( Op::Nop => Ok(()), Op::Dml(dml) => { access_check_dml(dml, as_user)?; - check_system_tables_access(dml, as_user)?; Ok(()) } Op::BatchDml { ops } => { for op in ops { access_check_dml(op, as_user)?; - check_system_tables_access(op, as_user)?; } Ok(()) } @@ -889,7 +873,11 @@ mod tests { // space let space_name = "test_box_access_check_ddl"; - let space = Space::create(space_name, &SpaceCreateOptions::default()).unwrap(); + let opts = SpaceCreateOptions { + id: Some(1025), + ..Default::default() + }; + let space = Space::create(space_name, &opts).unwrap(); // create space { diff --git a/src/cas.rs b/src/cas.rs index 11911d99efd2c6813abae203e6ce8eb37682e01a..140bbcb1e7ce77eb833e33dfeab5427f80e722f3 100644 --- a/src/cas.rs +++ b/src/cas.rs @@ -17,7 +17,6 @@ use ::raft::Error as RaftError; use ::raft::GetEntriesContext; use ::raft::StorageError; -use crate::schema::ADMIN_ID; use tarantool::error::Error as TntError; use tarantool::error::TarantoolErrorCode; use tarantool::fiber; @@ -40,12 +39,12 @@ const PROHIBITED_TABLES: &[ClusterwideTable] = &[ ClusterwideTable::Routine, ]; -pub fn check_admin_dml_prohibited(dml: &Dml, as_user: UserId) -> traft::Result<()> { +pub fn check_dml_prohibited(dml: &Dml) -> traft::Result<()> { let space = dml.space(); let Ok(table) = &ClusterwideTable::try_from(space) else { return Ok(()); }; - if as_user == ADMIN_ID && PROHIBITED_TABLES.contains(table) { + if PROHIBITED_TABLES.contains(table) { return Err(Error::TableNotAllowed { table: table.name().into(), } @@ -252,11 +251,11 @@ fn proc_cas_local(req: &Request) -> Result<Response> { match &req.op { Op::Dml(dml) => { - check_admin_dml_prohibited(dml, req.as_user)?; + check_dml_prohibited(dml)?; } Op::BatchDml { ops: dmls } => { for dml in dmls { - check_admin_dml_prohibited(dml, req.as_user)?; + check_dml_prohibited(dml)?; } } _ => {} @@ -450,10 +449,7 @@ pub enum Error { actual_term: RaftTerm, }, - #[error( - "TableNotAllowed: table {table} cannot be modified directly, \ - please refer to available SQL commands" - )] + #[error("TableNotAllowed: table {table} cannot be modified by DML Raft Operation directly")] TableNotAllowed { table: String }, /// An error related to `key_def` operation arised from tarantool diff --git a/test/int/test_acl.py b/test/int/test_acl.py index bcda2eeb498330b7c9a82fcf5ccc8ab8f9e46ec8..7996d1b69698fa4a11786db6534cc6ed619668ed 100644 --- a/test/int/test_acl.py +++ b/test/int/test_acl.py @@ -349,7 +349,7 @@ def test_cas_permissions(cluster: Cluster): with pytest.raises( Exception, - match="Write access to space '_pico_property' is denied for user 'Steven'", + match="Write access to table '_pico_property' is denied for all users", ): cluster.cas( "replace", @@ -365,8 +365,7 @@ def test_cas_permissions(cluster: Cluster): with pytest.raises( Exception, - match="box error: " - "AccessDenied: Write access to table '_pico_property' is denied for user 'Steven'", + match="Write access to table '_pico_property' is denied for all users", ): cluster.cas( "replace", diff --git a/test/int/test_cas.py b/test/int/test_cas.py index e76cb038d02709e939d07251d2636546fbceaf90..b3bb201d7830b7788175be1ddc015a83a91189c1 100644 --- a/test/int/test_cas.py +++ b/test/int/test_cas.py @@ -85,7 +85,7 @@ def test_cas_errors(instance: Instance): instance.cas("insert", table, [0], ranges=[CasRange(eq=0)], user=1) assert e5.value.args[:2] == ( ErrorCode.CasTableNotAllowed, - f"TableNotAllowed: table {table} cannot be modified directly, please refer to available SQL commands", # noqa: E501 + f"TableNotAllowed: table {table} cannot be modified by DML Raft Operation directly", ) # Field type error