Enable sysdb init

next
Sayan Nandan 12 months ago
parent ef3d71f593
commit 0d16776541
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -197,16 +197,16 @@ impl ConfigSystem {
config auth
*/
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, PartialEq, Deserialize, Clone, Copy)]
pub enum AuthDriver {
#[serde(rename = "pwd")]
Pwd,
}
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct ConfigAuth {
plugin: AuthDriver,
root_key: String,
pub plugin: AuthDriver,
pub root_key: String,
}
impl ConfigAuth {

@ -90,6 +90,16 @@ impl Datacell {
pub fn uint(&self) -> u64 {
self.try_uint().unwrap()
}
pub fn into_uint(self) -> Option<u64> {
if self.kind() != TagClass::UnsignedInt {
return None;
}
unsafe {
// UNSAFE(@ohsayan): +tagck
let md = ManuallyDrop::new(self);
Some(md.data.word.dwordnn_load_qw())
}
}
// sint
pub fn new_sint(i: i64) -> Self {
unsafe {
@ -158,6 +168,21 @@ impl Datacell {
pub fn bin(&self) -> &[u8] {
self.try_bin().unwrap()
}
pub fn into_bin(self) -> Option<Vec<u8>> {
if self.kind() != TagClass::Bin {
return None;
}
unsafe {
// UNSAFE(@ohsayan): no double free + tagck
let md = ManuallyDrop::new(self);
let (a, b) = md.data.word.dwordqn_load_qw_nw();
Some(Vec::from_raw_parts(
b as *const u8 as *mut u8,
a as usize,
a as usize,
))
}
}
// str
pub fn new_str(s: Box<str>) -> Self {
let mut md = ManuallyDrop::new(s.into_boxed_bytes());
@ -201,6 +226,17 @@ impl Datacell {
pub fn list(&self) -> &RwLock<Vec<Self>> {
self.try_list().unwrap()
}
pub fn into_list(self) -> Option<Vec<Datacell>> {
if self.kind() != TagClass::List {
return None;
}
unsafe {
// UNSAFE(@ohsayan): +tagck +avoid double free
let md = ManuallyDrop::new(self);
let rwl = core::ptr::read(&md.data.rwl);
Some(ManuallyDrop::into_inner(rwl).into_inner())
}
}
pub unsafe fn new_qw(qw: u64, tag: CUTag) -> Datacell {
Self::new(
tag,

@ -52,6 +52,18 @@ impl DictEntryGeneric {
_ => None,
}
}
pub fn into_dict(self) -> Option<DictGeneric> {
match self {
Self::Map(m) => Some(m),
_ => None,
}
}
pub fn into_data(self) -> Option<Datacell> {
match self {
Self::Data(d) => Some(d),
_ => None,
}
}
}
/*

@ -24,6 +24,8 @@
*
*/
use crate::engine::config::ConfigAuth;
use {
crate::engine::error::{Error, QueryResult},
parking_lot::RwLock,
@ -33,28 +35,53 @@ use {
#[derive(Debug)]
/// The global system configuration
pub struct SysConfig {
auth_data: RwLock<Option<SysAuth>>,
auth_data: Option<RwLock<SysAuth>>,
host_data: SysHostData,
}
impl PartialEq for SysConfig {
fn eq(&self, other: &Self) -> bool {
self.host_data == other.host_data
&& match (self.auth_data.as_ref(), other.auth_data.as_ref()) {
(None, None) => true,
(Some(a), Some(b)) => &*a.read() == &*b.read(),
_ => false,
}
}
}
impl SysConfig {
/// Initialize a new system config
pub fn new(auth_data: RwLock<Option<SysAuth>>, host_data: SysHostData) -> Self {
pub fn new(auth_data: Option<RwLock<SysAuth>>, host_data: SysHostData) -> Self {
Self {
auth_data,
host_data,
}
}
pub fn new_auth(new_auth: Option<ConfigAuth>, host_data: SysHostData) -> Self {
match new_auth {
Some(ConfigAuth { root_key, .. }) => Self::new(
Some(RwLock::new(SysAuth::new(
rcrypt::hash(root_key, rcrypt::DEFAULT_COST)
.unwrap()
.into_boxed_slice(),
Default::default(),
))),
host_data,
),
None => Self::new(None, host_data),
}
}
#[cfg(test)]
/// A test-mode default setting with auth disabled
pub(super) fn test_default() -> Self {
Self {
auth_data: RwLock::new(None),
auth_data: None,
host_data: SysHostData::new(0, 0),
}
}
/// Returns a handle to the authentication data
pub fn auth_data(&self) -> &RwLock<Option<SysAuth>> {
pub fn auth_data(&self) -> &Option<RwLock<SysAuth>> {
&self.auth_data
}
/// Returns a reference to host data
@ -78,9 +105,20 @@ impl SysHostData {
settings_version,
}
}
/// Returns the startup counter
///
/// Note:
/// - If this is `0` -> this is the first boot
/// - If this is `1` -> this is the second boot (... and so on)
pub fn startup_counter(&self) -> u64 {
self.startup_counter
}
/// Returns the settings version
///
/// Note:
/// - If this is `0` -> this is the initial setting (first boot)
///
/// If it stays at 0, this means that the settings were never changed
pub fn settings_version(&self) -> u32 {
self.settings_version
}
@ -118,6 +156,13 @@ impl SysAuth {
}
/// Verify the user with the given details
pub fn verify_user(&self, username: &str, password: &str) -> QueryResult<()> {
if username == "root" {
if rcrypt::verify(password, self.root_key()).unwrap() {
return Ok(());
} else {
return Err(Error::SysAuthError);
}
}
match self.users.get(username) {
Some(user) if rcrypt::verify(password, user.key()).unwrap() => Ok(()),
Some(_) | None => Err(Error::SysAuthError),
@ -139,7 +184,7 @@ pub struct SysAuthUser {
impl SysAuthUser {
/// Create a new [`SysAuthUser`]
fn new(key: Box<[u8]>) -> Self {
pub fn new(key: Box<[u8]>) -> Self {
Self { key }
}
/// Get the key

@ -39,14 +39,13 @@ use {
tokio::sync::mpsc::unbounded_channel,
};
mod config;
pub mod config;
mod drivers;
mod mgr;
#[cfg(test)]
pub mod test_utils;
mod util;
pub use {
config::SysConfig,
drivers::FractalModelDriver,
mgr::{CriticalTask, GenericTask, Task},
util::FractalToken,

@ -26,8 +26,8 @@
use {
super::{
CriticalTask, FractalModelDriver, GenericTask, GlobalInstanceLike, ModelUniqueID,
SysConfig, Task,
config::SysConfig, CriticalTask, FractalModelDriver, GenericTask, GlobalInstanceLike,
ModelUniqueID, Task,
},
crate::engine::{
core::GlobalNS,
@ -53,7 +53,7 @@ pub struct TestGlobal<Fs: RawFSInterface = VirtualFS> {
max_delta_size: usize,
txn_driver: Mutex<GNSTransactionDriverAnyFS<Fs>>,
model_drivers: RwLock<HashMap<ModelUniqueID, FractalModelDriver<Fs>>>,
sys_cfg: super::SysConfig,
sys_cfg: SysConfig,
}
impl<Fs: RawFSInterface> TestGlobal<Fs> {

@ -93,14 +93,10 @@ pub enum SDSSError {
// process errors
OtherError(&'static str),
// header
/// version mismatch
HeaderDecodeVersionMismatch,
/// The entire header is corrupted
HeaderDecodeCorruptedHeader,
/// The header versions don't match
HeaderDecodeHeaderVersionMismatch,
/// The driver versions don't match
HeaderDecodeDriverVersionMismatch,
/// The server versions don't match
HeaderDecodeServerVersionMismatch,
/// Expected header values were not matched with the current header
HeaderDecodeDataMismatch,
/// The time in the [header/dynrec/rtsig] is in the future
@ -134,7 +130,7 @@ pub enum SDSSError {
/// An error with more context
// TODO(@ohsayan): avoid the box; we'll clean this up soon
Extra(Box<Self>, String),
HeaderDecodeVersionMismatch,
SysDBCorrupted,
}
impl From<TransactionError> for SDSSError {

@ -458,4 +458,10 @@ impl<Fs: RawFSInterface> SDSSFileIO<Fs> {
let mut r = [0; 1];
self.read_to_buffer(&mut r).map(|_| r[0])
}
pub fn load_remaining_into_buffer(&mut self) -> SDSSResult<Vec<u8>> {
let len = self.file_length()? - self.retrieve_cursor()?;
let mut buf = vec![0; len as usize];
self.read_to_buffer(&mut buf)?;
Ok(buf)
}
}

@ -24,38 +24,144 @@
*
*/
use crate::engine::{
data::{cell::Datacell, DictEntryGeneric, DictGeneric},
fractal::SysConfig,
storage::v1::{spec, RawFSInterface, SDSSError, SDSSFileIO, SDSSResult},
use {
super::{rw::FileOpen, SDSSError},
crate::engine::{
config::ConfigAuth,
data::{cell::Datacell, DictEntryGeneric, DictGeneric},
fractal::config::{SysAuth, SysAuthUser, SysConfig, SysHostData},
storage::v1::{inf, spec, RawFSInterface, SDSSFileIO, SDSSResult},
},
parking_lot::RwLock,
std::collections::HashMap,
};
const SYSDB_PATH: &str = "sys.db";
const SYSDB_COW_PATH: &str = "sys.db.cow";
const SYS_KEY_AUTH: &str = "auth";
const SYS_KEY_AUTH_ROOT: &str = "root";
const SYS_KEY_AUTH_USERS: &str = "users";
const SYS_KEY_SYS: &str = "sys";
const SYS_KEY_SYS_STARTUP_COUNTER: &str = "sc";
const SYS_KEY_SYS_SETTINGS_VERSION: &str = "sv";
pub fn sync_system_database<Fs: RawFSInterface>(cfg: &SysConfig) -> SDSSResult<()> {
// get auth data
let auth_data = cfg.auth_data().read();
#[derive(Debug, PartialEq)]
/// The system store init state
pub enum SystemStoreInitState {
/// No system store was present. it was created
Created,
/// The system store was present, but no new changes were applied
Unchanged,
/// The system store was present, root settings were updated
UpdatedRoot,
/// the system store was present, auth was previously enabled but is now disabled
UpdatedAuthDisabled,
/// the system store was present, auth was previously disabled but is now enabled
UpdatedAuthEnabled,
}
#[derive(Debug, PartialEq)]
/// Result of initializing the system store (sysdb)
pub struct SystemStoreInit {
pub store: SysConfig,
pub state: SystemStoreInitState,
}
impl SystemStoreInit {
pub fn new(store: SysConfig, state: SystemStoreInitState) -> Self {
Self { store, state }
}
}
/// Open the system database
///
/// - If it doesn't exist, create it
/// - If it exists, look for config changes and sync them
pub fn open_system_database<Fs: RawFSInterface>(
auth: Option<ConfigAuth>,
) -> SDSSResult<SystemStoreInit> {
open_or_reinit_system_database::<Fs>(auth, SYSDB_PATH, SYSDB_COW_PATH)
}
/// Open or re-initialize the system database
pub fn open_or_reinit_system_database<Fs: RawFSInterface>(
auth: Option<ConfigAuth>,
sysdb_path: &str,
sysdb_path_cow: &str,
) -> SDSSResult<SystemStoreInit> {
let (ex, _) = match SDSSFileIO::<Fs>::open_or_create_perm_rw::<spec::SysDBV1>(sysdb_path)? {
FileOpen::Created(new_sysdb) => {
let syscfg = SysConfig::new_auth(auth, SysHostData::new(0, 0));
sync_system_database_to(&syscfg, new_sysdb)?;
return Ok(SystemStoreInit::new(syscfg, SystemStoreInitState::Created));
}
FileOpen::Existing(ex) => ex,
};
let last_syscfg = decode_system_database(ex)?;
let mut state = SystemStoreInitState::Unchanged;
match (last_syscfg.auth_data(), &auth) {
(Some(last_auth), Some(new_auth)) => {
let last_auth = last_auth.read();
if last_auth.verify_user("root", &new_auth.root_key).is_err() {
// the root password was changed
state = SystemStoreInitState::UpdatedRoot;
}
}
(Some(_), None) => {
state = SystemStoreInitState::UpdatedAuthDisabled;
}
(None, Some(_)) => {
state = SystemStoreInitState::UpdatedAuthEnabled;
}
(None, None) => {}
}
let new_syscfg = SysConfig::new_auth(
auth,
SysHostData::new(
last_syscfg.host_data().startup_counter() + 1,
last_syscfg.host_data().settings_version()
+ !matches!(state, SystemStoreInitState::Unchanged) as u32,
),
);
// sync
let cow_file = SDSSFileIO::<Fs>::create::<spec::SysDBV1>(sysdb_path_cow)?;
sync_system_database_to(&new_syscfg, cow_file)?;
// replace
Fs::fs_rename_file(sysdb_path_cow, sysdb_path)?;
Ok(SystemStoreInit::new(new_syscfg, state))
}
/// Sync the system database to the given file
pub fn sync_system_database_to<Fs: RawFSInterface>(
cfg: &SysConfig,
mut f: SDSSFileIO<Fs>,
) -> SDSSResult<()> {
// prepare our flat file
let mut map: DictGeneric = into_dict!(
"host" => DictEntryGeneric::Map(into_dict!(
"settings_version" => Datacell::new_uint(cfg.host_data().settings_version() as _),
"startup_counter" => Datacell::new_uint(cfg.host_data().startup_counter() as _),
SYS_KEY_SYS => DictEntryGeneric::Map(into_dict!(
SYS_KEY_SYS_SETTINGS_VERSION => Datacell::new_uint(cfg.host_data().settings_version() as _),
SYS_KEY_SYS_STARTUP_COUNTER => Datacell::new_uint(cfg.host_data().startup_counter() as _),
)),
"auth" => DictGeneric::new(),
SYS_KEY_AUTH => DictGeneric::new(),
);
let auth_key = map.get_mut("auth").unwrap();
match &*auth_data {
None => *auth_key = Datacell::null().into(),
let auth_key = map.get_mut(SYS_KEY_AUTH).unwrap();
match cfg.auth_data() {
None => {
*auth_key = DictEntryGeneric::Map(
into_dict!(SYS_KEY_AUTH_ROOT => Datacell::null(), SYS_KEY_AUTH_USERS => Datacell::null()),
)
}
Some(auth) => {
let auth = auth.read();
let auth_key = auth_key.as_dict_mut().unwrap();
auth_key.insert(
"root".into(),
SYS_KEY_AUTH_ROOT.into(),
DictEntryGeneric::Data(Datacell::new_bin(auth.root_key().into())),
);
auth_key.insert(
"users".into(),
SYS_KEY_AUTH_USERS.into(),
DictEntryGeneric::Map(
// username -> [..settings]
auth.users()
.iter()
.map(|(username, user)| {
@ -71,15 +177,77 @@ pub fn sync_system_database<Fs: RawFSInterface>(cfg: &SysConfig) -> SDSSResult<(
);
}
}
// open file
let mut file = SDSSFileIO::<Fs>::open_or_create_perm_rw::<spec::SysDBV1>(SYSDB_COW_PATH)?
.into_created()
.ok_or(SDSSError::OtherError(
"sys.db.cow already exists. please remove this file.",
))?;
// write
let buf = super::inf::enc::enc_dict_full::<super::inf::map::GenericDictSpec>(&map);
file.fsynced_write(&buf)?;
// replace
Fs::fs_rename_file(SYSDB_COW_PATH, SYSDB_PATH)
f.fsynced_write(&buf)
}
fn rkey<T>(
d: &mut DictGeneric,
key: &str,
transform: impl Fn(DictEntryGeneric) -> Option<T>,
) -> SDSSResult<T> {
match d.remove(key).map(transform) {
Some(Some(k)) => Ok(k),
_ => Err(SDSSError::SysDBCorrupted),
}
}
/// Decode the system database
pub fn decode_system_database<Fs: RawFSInterface>(mut f: SDSSFileIO<Fs>) -> SDSSResult<SysConfig> {
let rem = f.load_remaining_into_buffer()?;
let mut store = inf::dec::dec_dict_full::<inf::map::GenericDictSpec>(&rem)?;
// find auth and sys stores
let mut auth_store = rkey(&mut store, SYS_KEY_AUTH, DictEntryGeneric::into_dict)?;
let mut sys_store = rkey(&mut store, SYS_KEY_SYS, DictEntryGeneric::into_dict)?;
// get our auth
let auth_root = rkey(&mut auth_store, SYS_KEY_AUTH_ROOT, |dict| {
let data = dict.into_data()?;
match data.kind() {
_ if data.is_null() => Some(None),
_ => data.into_bin().map(Some),
}
})?;
let auth_users = rkey(&mut auth_store, SYS_KEY_AUTH_USERS, |dict| match dict {
DictEntryGeneric::Data(dc) if dc.is_null() => Some(None),
DictEntryGeneric::Map(m) => Some(Some(m)),
_ => None,
})?;
let sys_auth = match (auth_root, auth_users) {
(Some(root_pass), Some(users)) => {
let mut usermap = HashMap::new();
for (user_name, user) in users {
let mut user_data = user
.into_data()
.and_then(|d| d.into_list())
.ok_or(SDSSError::SysDBCorrupted)?;
if user_data.len() != 1 {
return Err(SDSSError::SysDBCorrupted);
}
let password = user_data
.remove(0)
.into_bin()
.ok_or(SDSSError::SysDBCorrupted)?;
usermap.insert(user_name, SysAuthUser::new(password.into_boxed_slice()));
}
Some(RwLock::new(SysAuth::new(
root_pass.into_boxed_slice(),
usermap,
)))
}
(None, None) => None,
_ => return Err(SDSSError::SysDBCorrupted),
};
// get our sys
let sv = rkey(&mut sys_store, SYS_KEY_SYS_SETTINGS_VERSION, |de| {
de.into_data()?.into_uint()
})?;
let sc = rkey(&mut sys_store, SYS_KEY_SYS_STARTUP_COUNTER, |de| {
de.into_data()?.into_uint()
})?;
if !(sys_store.is_empty() & auth_store.is_empty() & store.is_empty()) {
// the stores have more keys than we expected. something is wrong here
return Err(SDSSError::SysDBCorrupted);
}
Ok(SysConfig::new(sys_auth, SysHostData::new(sc, sv as u32)))
}

@ -29,3 +29,147 @@ type VirtualFS = super::memfs::VirtualFS;
mod batch;
mod rw;
mod tx;
mod sysdb {
use {
super::{
super::sysdb::{self, SystemStoreInitState},
VirtualFS as VFS,
},
crate::engine::config::{AuthDriver, ConfigAuth},
};
#[test]
fn simple_open_close() {
{
let syscfg_new = sysdb::open_or_reinit_system_database::<VFS>(
None,
"sysdb_test_1.db",
"sysdb_test_1.cow.db",
)
.unwrap();
assert_eq!(syscfg_new.state, SystemStoreInitState::Created);
assert!(syscfg_new.store.auth_data().is_none());
assert_eq!(syscfg_new.store.host_data().settings_version(), 0);
assert_eq!(syscfg_new.store.host_data().startup_counter(), 0);
}
let syscfg_restore = sysdb::open_or_reinit_system_database::<VFS>(
None,
"sysdb_test_1.db",
"sysdb_test_1.cow.db",
)
.unwrap();
assert_eq!(syscfg_restore.state, SystemStoreInitState::Unchanged);
assert!(syscfg_restore.store.auth_data().is_none());
assert_eq!(syscfg_restore.store.host_data().settings_version(), 0);
assert_eq!(syscfg_restore.store.host_data().startup_counter(), 1);
}
#[test]
fn with_auth_nochange() {
let auth = ConfigAuth::new(AuthDriver::Pwd, "password12345678".to_string());
{
let syscfg_new = sysdb::open_or_reinit_system_database::<VFS>(
Some(auth.clone()),
"sysdb_test_2.db",
"sysdb_test_2.cow.db",
)
.unwrap();
assert_eq!(syscfg_new.state, SystemStoreInitState::Created);
assert!(syscfg_new
.store
.auth_data()
.as_ref()
.unwrap()
.read()
.verify_user("root", "password12345678")
.is_ok());
assert_eq!(syscfg_new.store.host_data().startup_counter(), 0);
assert_eq!(syscfg_new.store.host_data().settings_version(), 0);
}
// now reboot
let syscfg_new = sysdb::open_or_reinit_system_database::<VFS>(
Some(auth),
"sysdb_test_2.db",
"sysdb_test_2.cow.db",
)
.unwrap();
assert_eq!(syscfg_new.state, SystemStoreInitState::Unchanged);
assert!(syscfg_new
.store
.auth_data()
.as_ref()
.unwrap()
.read()
.verify_user("root", "password12345678")
.is_ok());
assert_eq!(syscfg_new.store.host_data().startup_counter(), 1);
assert_eq!(syscfg_new.store.host_data().settings_version(), 0);
}
#[test]
fn disable_auth() {
{
let auth = ConfigAuth::new(AuthDriver::Pwd, "password12345678".to_string());
let syscfg_new = sysdb::open_or_reinit_system_database::<VFS>(
Some(auth),
"sysdb_test_3.db",
"sysdb_test_3.cow.db",
)
.unwrap();
assert_eq!(syscfg_new.state, SystemStoreInitState::Created);
assert!(syscfg_new
.store
.auth_data()
.as_ref()
.unwrap()
.read()
.verify_user("root", "password12345678")
.is_ok());
assert_eq!(syscfg_new.store.host_data().startup_counter(), 0);
assert_eq!(syscfg_new.store.host_data().settings_version(), 0);
}
// reboot
let sysdb_cfg = sysdb::open_or_reinit_system_database::<VFS>(
None,
"sysdb_test_3.db",
"sysdb_test_3.cow.db",
)
.unwrap();
assert_eq!(sysdb_cfg.state, SystemStoreInitState::UpdatedAuthDisabled);
assert!(sysdb_cfg.store.auth_data().is_none());
assert_eq!(sysdb_cfg.store.host_data().startup_counter(), 1);
assert_eq!(sysdb_cfg.store.host_data().settings_version(), 1);
}
#[test]
fn enable_auth() {
{
let sysdb_cfg = sysdb::open_or_reinit_system_database::<VFS>(
None,
"sysdb_test_4.db",
"sysdb_test_4.cow.db",
)
.unwrap();
assert_eq!(sysdb_cfg.state, SystemStoreInitState::Created);
assert!(sysdb_cfg.store.auth_data().is_none());
assert_eq!(sysdb_cfg.store.host_data().startup_counter(), 0);
assert_eq!(sysdb_cfg.store.host_data().settings_version(), 0);
}
// reboot
let auth = ConfigAuth::new(AuthDriver::Pwd, "password12345678".to_string());
let syscfg_new = sysdb::open_or_reinit_system_database::<VFS>(
Some(auth),
"sysdb_test_4.db",
"sysdb_test_4.cow.db",
)
.unwrap();
assert_eq!(syscfg_new.state, SystemStoreInitState::UpdatedAuthEnabled);
assert!(syscfg_new
.store
.auth_data()
.as_ref()
.unwrap()
.read()
.verify_user("root", "password12345678")
.is_ok());
assert_eq!(syscfg_new.store.host_data().startup_counter(), 1);
assert_eq!(syscfg_new.store.host_data().settings_version(), 1);
}
}

Loading…
Cancel
Save