diff --git a/src/bootstrap_entries.rs b/src/bootstrap_entries.rs index f0931a3748d8ee8c0e0097becae55e1c75168ead..a0c57e9e908bde0afaf2af1cfd329f157ca8b189 100644 --- a/src/bootstrap_entries.rs +++ b/src/bootstrap_entries.rs @@ -6,6 +6,7 @@ use tarantool::auth::AuthMethod; use crate::cli::args; use crate::instance::Instance; +use crate::schema; use crate::schema::PrivilegeDef; use crate::schema::RoleDef; use crate::schema::TableDef; @@ -163,6 +164,12 @@ pub(super) fn prepare(args: &args::Run, instance: &Instance, tiers: &[Tier]) -> ADMIN_ID, )); + init_entries_push_op(op::Dml::insert( + ClusterwideTable::User, + schema::pico_service_user_def(), + ADMIN_ID, + )); + // equivalent SQL expression: CREATE ROLE 'public' init_entries_push_op(op::Dml::insert( ClusterwideTable::Role, @@ -200,6 +207,15 @@ pub(super) fn prepare(args: &args::Run, instance: &Instance, tiers: &[Tier]) -> )); } + // Grant all privileges on "universe" to "pico_service". + for priv_def in schema::pico_service_privilege_defs() { + init_entries_push_op(op::Dml::insert( + ClusterwideTable::Privilege, + priv_def, + ADMIN_ID, + )); + } + // Builtin global table definitions for table_def in TableDef::system_tables() { init_entries_push_op(op::Dml::insert( diff --git a/src/cli/test.rs b/src/cli/test.rs index 50e29ec2a7d780690efb5ff847d47639e3906923..750c2e98d8f0560f7b93978b6778f93f644eb990 100644 --- a/src/cli/test.rs +++ b/src/cli/test.rs @@ -141,6 +141,8 @@ fn test_one(test: &TestCase) { }; tarantool::set_cfg(&cfg); + + crate::schema::init_user_pico_service(); tarantool::exec( r#" box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists = true}) diff --git a/src/lib.rs b/src/lib.rs index 760b511a31f43ded3fad23fe4b2306c96d5d781d..a2b75c51a906d21478385b4d86cb4be6a0e221af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -500,6 +500,8 @@ fn init_common(args: &args::Run, cfg: &tarantool::Cfg) -> (Clusterwide, RaftSpac init_handlers(); let storage = Clusterwide::try_get(true).expect("storage initialization should never fail"); + schema::init_user_pico_service(); + set_login_attempts_check(storage.clone()); set_on_access_denied_audit_trigger(); let raft_storage = diff --git a/src/schema.rs b/src/schema.rs index 13589b46c89423045c5b83d1e557ad45f573ea7d..333080844dc8b4fe9d74a31f0ad7ccc272c536bb 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::time::Duration; use tarantool::auth::AuthDef; +use tarantool::auth::AuthMethod; use tarantool::error::TarantoolError; use tarantool::error::TarantoolErrorCode; use tarantool::fiber; @@ -26,6 +27,7 @@ use tarantool::{ use serde::{Deserialize, Serialize}; use crate::cas::{self, compare_and_swap}; +use crate::storage; use crate::storage::{Clusterwide, SPACE_ID_INTERNAL_MAX}; use crate::storage::{ClusterwideTable, PropertyName}; use crate::traft::error::Error; @@ -376,6 +378,13 @@ pub const ADMIN_ID: UserId = 1; /// See also <https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/_user/#box-space-user> pub const PUBLIC_ID: UserId = 2; +/// User id of the builtin role "replication". +/// +/// Role "replication" has the following grants: +/// - Read access to the "universe" +/// - Write access to the space "_cluster" +pub const ROLE_REPLICATION_ID: i64 = 3; + /// User id of the builtin role "super". /// /// Users with this role have access to everything. @@ -383,6 +392,13 @@ pub const PUBLIC_ID: UserId = 2; /// See also <https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/_user/#box-space-user> pub const SUPER_ID: UserId = 31; +/// User id of the builtin user "pico_service". +/// +/// A special user for internal communication between instances of picodata. +/// It is equivalent in it's privileges to "admin". The only difference is that +/// only the automated rpc calls are performed as "pico_service". +pub const PICO_SERVICE_ID: UserId = 32; + /// Object id of the special builtin object "universe". /// /// Object "universe" is basically an alias to the "whole database". @@ -679,6 +695,113 @@ impl PrivilegeDef { } } +//////////////////////////////////////////////////////////////////////////////// +// init_pico_service +//////////////////////////////////////////////////////////////////////////////// + +/// Name of the special builtin user for internal communication between +/// instances. It's id is [`PICO_SERVICE_ID`]. +/// +/// Use this constant instead of literal "pico_service" so that it's easier to +/// find all the places where we refer to "pico_service". +pub const PICO_SERVICE_USER_NAME: &'static str = "pico_service"; + +#[inline] +pub fn pico_service_user_def() -> &'static UserDef { + static PICO_SERVICE_USER_DEF: OnceCell<UserDef> = OnceCell::new(); + PICO_SERVICE_USER_DEF.get_or_init(|| UserDef { + id: PICO_SERVICE_ID, + name: PICO_SERVICE_USER_NAME.into(), + // This means the local schema is already up to date and main loop doesn't need to do anything + schema_version: INITIAL_SCHEMA_VERSION, + auth: AuthDef::new( + AuthMethod::ChapSha1, + tarantool::auth::AuthData::new(&AuthMethod::ChapSha1, PICO_SERVICE_USER_NAME, "") + .into_string(), + ), + owner: ADMIN_ID, + }) +} + +#[inline] +pub fn pico_service_privilege_defs() -> &'static [PrivilegeDef] { + static PICO_SERVICE_PRIVILEGE_DEFS: OnceCell<Vec<PrivilegeDef>> = OnceCell::new(); + PICO_SERVICE_PRIVILEGE_DEFS.get_or_init(|| { + let mut res = Vec::with_capacity(PrivilegeType::VARIANTS.len()); + + for &privilege in PrivilegeType::VARIANTS { + res.push(PrivilegeDef { + privilege, + object_type: SchemaObjectType::Universe, + object_id: UNIVERSE_ID, + grantee_id: PICO_SERVICE_ID, + grantor_id: ADMIN_ID, + // This means the local schema is already up to date and main loop doesn't need to do anything + schema_version: INITIAL_SCHEMA_VERSION, + }); + } + + // TODO: explain + res.push(PrivilegeDef { + privilege: PrivilegeType::Execute, + object_type: SchemaObjectType::Role, + object_id: ROLE_REPLICATION_ID, + grantee_id: PICO_SERVICE_ID, + grantor_id: ADMIN_ID, + // This means the local schema is already up to date and main loop doesn't need to do anything + schema_version: INITIAL_SCHEMA_VERSION, + }); + + res + }) +} + +pub fn init_user_pico_service() { + let sys_user = SystemSpace::User.as_space(); + let sys_priv = SystemSpace::Priv.as_space(); + + let t = sys_user + .get(&[PICO_SERVICE_ID]) + .expect("reading from _user shouldn't fail"); + if t.is_some() { + // Already exists (instance restarted) + return; + } + + let user_def = pico_service_user_def(); + let res = storage::acl::on_master_create_user(user_def, false); + if let Err(e) = res { + panic!("failed creating user '{PICO_SERVICE_USER_NAME}': {e}"); + } + + // Grant ALL privileges to "every object of every type". + const PRIVILEGE_ALL: u32 = 0xffff_ffff; + let res = sys_priv.insert(&( + ADMIN_ID, + PICO_SERVICE_ID, + "universe", + UNIVERSE_ID, + PRIVILEGE_ALL, + )); + if let Err(e) = res { + panic!("failed creating user '{PICO_SERVICE_USER_NAME}': {e}"); + } + + // Also grant role "replication", because all privileges to "every object of + // every type" is not enough. + const PRIVILEGE_EXECUTE: u32 = 4; + let res = sys_priv.insert(&( + ADMIN_ID, + PICO_SERVICE_ID, + "role", + ROLE_REPLICATION_ID, + PRIVILEGE_EXECUTE, + )); + if let Err(e) = res { + panic!("failed creating user '{PICO_SERVICE_USER_NAME}': {e}"); + } +} + //////////////////////////////////////////////////////////////////////////////// // ... //////////////////////////////////////////////////////////////////////////////// diff --git a/test/int/test_basics.py b/test/int/test_basics.py index f84e05e985b07e8dfda2ef586882de13e57fb396..76e3ad36335e104b23f71b4ba76beabd8b97b398 100644 --- a/test/int/test_basics.py +++ b/test/int/test_basics.py @@ -266,11 +266,20 @@ def test_raft_log(instance: Instance): | 0 | 1 |Insert({_pico_property}, ["snapshot_read_view_close_timeout",86400.0])| | 0 | 1 |Insert({_pico_user}, [0,"guest",0,["chap-sha1","vhvewKp0tNyweZQ+cFKAlsyphfg="],1])| | 0 | 1 |Insert({_pico_user}, [1,"admin",0,["chap-sha1",""],1])| +| 0 | 1 |Insert({_pico_user}, [32,"pico_service",0,["chap-sha1","vhvewKp0tNyweZQ+cFKAlsyphfg="],1])| | 0 | 1 |Insert({_pico_role}, [2,"public",0,1])| | 0 | 1 |Insert({_pico_role}, [31,"super",0,1])| | 0 | 1 |Insert({_pico_privilege}, ["login","universe",0,0,1,0])| | 0 | 1 |Insert({_pico_privilege}, ["login","universe",0,1,1,0])| | 0 | 1 |Insert({_pico_privilege}, ["execute","role",2,0,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["read","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["write","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["execute","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["login","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["create","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["drop","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["alter","universe",0,32,1,0])| +| 0 | 1 |Insert({_pico_privilege}, ["execute","role",3,32,1,0])| | 0 | 1 |Insert({_pico_table}, [{_pico_table},"_pico_table",["global"],[["id","unsigned",false],["name","string",false],["distribution","array",false],["format","array",false],["schema_version","unsigned",false],["operable","boolean",false],["engine","string",false],["owner","unsigned",false]],0,true,"memtx",1])| | 0 | 1 |Insert({_pico_table}, [{_pico_index},"_pico_index",["global"],[["table_id","unsigned",false],["id","unsigned",false],["name","string",false],["local","boolean",false],["parts","array",false],["schema_version","unsigned",false],["operable","boolean",false],["unique","boolean",false]],0,true,"memtx",1])| | 0 | 1 |Insert({_pico_table}, [{_pico_peer_address},"_pico_peer_address",["global"],[["raft_id","unsigned",false],["address","string",false]],0,true,"memtx",1])| @@ -287,8 +296,8 @@ def test_raft_log(instance: Instance): | 0 | 2 |Insert({_pico_replicaset}, ["r1","e0df68c5-e7f9-395f-86b3-30ad9e1b7b07","i1","i1","default",0.0,"auto","not-ready"])| | 0 | 2 |Replace({_pico_instance}, ["i1","68d4a766-4144-3248-aeb4-e212356716e4",1,"r1","e0df68c5-e7f9-395f-86b3-30ad9e1b7b07",["Replicated",1],["Online",1],{b},"default"])| | 0 | 2 |Update({_pico_replicaset}, ["r1"], [["=","weight",1.0], ["=","state","ready"]])| -| 0 | 2 |Replace({_pico_property}, ["target_vshard_config",[{{"e0df68c5-e7f9-395f-86b3-30ad9e1b7b07":[{{"68d4a766-4144-3248-aeb4-e212356716e4":["guest:@127.0.0.1:{p}","i1",true]}},1.0]}},"on"]])| -| 0 | 2 |Replace({_pico_property}, ["current_vshard_config",[{{"e0df68c5-e7f9-395f-86b3-30ad9e1b7b07":[{{"68d4a766-4144-3248-aeb4-e212356716e4":["guest:@127.0.0.1:{p}","i1",true]}},1.0]}},"on"]])| +| 0 | 2 |Replace({_pico_property}, ["target_vshard_config",[{{"e0df68c5-e7f9-395f-86b3-30ad9e1b7b07":[{{"68d4a766-4144-3248-aeb4-e212356716e4":["pico_service:@127.0.0.1:{p}","i1",true]}},1.0]}},"on"]])| +| 0 | 2 |Replace({_pico_property}, ["current_vshard_config",[{{"e0df68c5-e7f9-395f-86b3-30ad9e1b7b07":[{{"68d4a766-4144-3248-aeb4-e212356716e4":["pico_service:@127.0.0.1:{p}","i1",true]}},1.0]}},"on"]])| | 0 | 2 |Replace({_pico_property}, ["vshard_bootstrapped",true])| | 0 | 2 |Replace({_pico_instance}, ["i1","68d4a766-4144-3248-aeb4-e212356716e4",1,"r1","e0df68c5-e7f9-395f-86b3-30ad9e1b7b07",["Online",1],["Online",1],{b},"default"])| +-----+----+--------+