diff --git a/server/src/handlers/http/middleware.rs b/server/src/handlers/http/middleware.rs index e9f3f9c07..d65699c89 100644 --- a/server/src/handlers/http/middleware.rs +++ b/server/src/handlers/http/middleware.rs @@ -27,10 +27,7 @@ use actix_web::{ use actix_web_httpauth::extractors::basic::BasicAuth; use futures_util::future::LocalBoxFuture; -use crate::{ - option::CONFIG, - rbac::{role::Action, Users}, -}; +use crate::{option::CONFIG, rbac::role::Action, rbac::Users}; pub struct Auth { pub action: Action, @@ -77,7 +74,7 @@ where forward_ready!(service); fn call(&self, mut req: ServiceRequest) -> Self::Future { - // Extract username and password using basic auth extractor. + // Extract username and password from the request using basic auth extractor. let creds = req.extract::().into_inner(); let creds = creds.map_err(Into::into).map(|creds| { let username = creds.user_id().trim().to_owned(); diff --git a/server/src/handlers/http/rbac.rs b/server/src/handlers/http/rbac.rs index 8e295e9b5..c50ff12de 100644 --- a/server/src/handlers/http/rbac.rs +++ b/server/src/handlers/http/rbac.rs @@ -96,7 +96,7 @@ pub async fn delete_user(username: web::Path) -> Result Result { - // get new password for this user + // generate new password for this user let PassCode { password, hash } = User::gen_new_password(); // update parseable.json first let mut metadata = get_metadata().await?; @@ -145,7 +145,7 @@ pub async fn put_role( put_metadata(&metadata).await?; // update in mem table Users.put_role(&username, role); - Ok(format!("Roles updated successfully for {}", username)) + Ok(format!("Roles updated successfully for {username}")) } async fn get_metadata() -> Result { diff --git a/server/src/main.rs b/server/src/main.rs index 51ac95ac3..75ab8287a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -64,7 +64,7 @@ async fn main() -> anyhow::Result<()> { migration::run_metadata_migration(&CONFIG).await?; let metadata = storage::resolve_parseable_metadata().await?; banner::print(&CONFIG, &metadata).await; - rbac::set_user_map(metadata.users.clone()); + rbac::map::init_auth_maps(metadata.users.clone()); metadata.set_global(); let prometheus = metrics::build_metrics_handler(); CONFIG.storage().register_store_metrics(&prometheus); diff --git a/server/src/rbac.rs b/server/src/rbac.rs index 0fbb59ce7..a6001ce86 100644 --- a/server/src/rbac.rs +++ b/server/src/rbac.rs @@ -16,57 +16,16 @@ * */ -use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -use once_cell::sync::OnceCell; - -use crate::option::CONFIG; - -use self::{ - auth::AuthMap, - role::{model::DefaultPrivilege, Action}, - user::{get_admin_user, User, UserMap}, -}; - -pub mod auth; +pub mod map; pub mod role; pub mod user; -pub static USER_MAP: OnceCell> = OnceCell::new(); -pub static AUTH_MAP: OnceCell> = OnceCell::new(); - -fn user_map() -> RwLockReadGuard<'static, UserMap> { - USER_MAP - .get() - .expect("map is set") - .read() - .expect("not poisoned") -} - -fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> { - USER_MAP - .get() - .expect("map is set") - .write() - .expect("not poisoned") -} - -fn auth_map() -> RwLockReadGuard<'static, AuthMap> { - AUTH_MAP - .get() - .expect("map is set") - .read() - .expect("not poisoned") -} - -fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> { - AUTH_MAP - .get() - .expect("map is set") - .write() - .expect("not poisoned") -} +use crate::rbac::map::{auth_map, mut_auth_map, mut_user_map, user_map}; +use crate::rbac::role::{model::DefaultPrivilege, Action}; +use crate::rbac::user::User; +// This type encapsulates both the user_map and auth_map +// so other entities deal with only this type pub struct Users; impl Users { @@ -122,13 +81,15 @@ impl Users { return res; } + // if not found in auth map, look into user map let (username, password) = key; - // verify pass and add this user's perm to auth map if let Some(user) = user_map().get(&username) { + // if user exists and password matches + // add this user to auth map if user.verify_password(&password) { let mut auth_map = mut_auth_map(); auth_map.add_user(username.clone(), password.clone(), user.permissions()); - // verify auth and return + // verify from auth map and return let key = (username, password); return auth_map .check_auth(&key, action, stream) @@ -139,23 +100,3 @@ impl Users { false } } - -pub fn set_user_map(users: Vec) { - let mut user_map = UserMap::from(users); - let mut auth_map = AuthMap::default(); - let admin = get_admin_user(); - let admin_permissions = admin.permissions(); - user_map.insert(admin); - auth_map.add_user( - CONFIG.parseable.username.clone(), - CONFIG.parseable.password.clone(), - admin_permissions, - ); - - USER_MAP - .set(RwLock::new(user_map)) - .expect("map is only set once"); - AUTH_MAP - .set(RwLock::new(auth_map)) - .expect("map is only set once"); -} diff --git a/server/src/rbac/auth.rs b/server/src/rbac/auth.rs deleted file mode 100644 index 52d33eab3..000000000 --- a/server/src/rbac/auth.rs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Parseable Server (C) 2022 - 2023 Parseable, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -use std::collections::HashMap; - -use super::role::{Action, Permission}; - -// quicker way to authenticate user based on username and password -// All users in this map are always authenticated -#[derive(Debug, Default)] -pub struct AuthMap { - inner: HashMap<(String, String), Vec>, -} - -impl AuthMap { - pub fn add_user(&mut self, username: String, password: String, permissions: Vec) { - self.inner.insert((username, password), permissions); - } - - pub fn remove(&mut self, username: &str) { - self.inner.retain(|(x, _), _| x != username) - } - - // returns None if user is not in the map - // Otherwise returns Some(is_authenticated) - pub fn check_auth( - &self, - key: &(String, String), - required_action: Action, - on_stream: Option<&str>, - ) -> Option { - self.inner.get(key).map(|perms| { - perms.iter().any(|user_perm| { - match *user_perm { - // if any action is ALL then we we authorize - Permission::Unit(action) => action == required_action || action == Action::All, - Permission::Stream(action, ref stream) => { - let ok_stream = if let Some(on_stream) = on_stream { - stream == on_stream || stream == "*" - } else { - // if no stream to match then stream check is not needed - true - }; - (action == required_action || action == Action::All) && ok_stream - } - } - }) - }) - } -} diff --git a/server/src/rbac/map.rs b/server/src/rbac/map.rs new file mode 100644 index 000000000..6d742fc0a --- /dev/null +++ b/server/src/rbac/map.rs @@ -0,0 +1,155 @@ +/* + * Parseable Server (C) 2022 - 2023 Parseable, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +use crate::option::CONFIG; +use crate::rbac::user::User; +use std::collections::HashMap; + +use super::{ + role::{Action, Permission}, + user, +}; +use once_cell::sync::OnceCell; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +pub static USER_MAP: OnceCell> = OnceCell::new(); +pub static AUTH_MAP: OnceCell> = OnceCell::new(); + +pub fn user_map() -> RwLockReadGuard<'static, UserMap> { + USER_MAP + .get() + .expect("map is set") + .read() + .expect("not poisoned") +} + +pub fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> { + USER_MAP + .get() + .expect("map is set") + .write() + .expect("not poisoned") +} + +pub fn auth_map() -> RwLockReadGuard<'static, AuthMap> { + AUTH_MAP + .get() + .expect("map is set") + .read() + .expect("not poisoned") +} + +pub fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> { + AUTH_MAP + .get() + .expect("map is set") + .write() + .expect("not poisoned") +} + +// initialize the user and auth maps +// the user_map is initialized from the config file and has a list of all users +// the auth_map is initialized with admin user only and then gets lazily populated +// as users authenticate +pub fn init_auth_maps(users: Vec) { + let mut user_map = UserMap::from(users); + let mut auth_map = AuthMap::default(); + let admin = user::get_admin_user(); + let admin_permissions = admin.permissions(); + user_map.insert(admin); + auth_map.add_user( + CONFIG.parseable.username.clone(), + CONFIG.parseable.password.clone(), + admin_permissions, + ); + + USER_MAP + .set(RwLock::new(user_map)) + .expect("map is only set once"); + AUTH_MAP + .set(RwLock::new(auth_map)) + .expect("map is only set once"); +} + +// AuthMap is a map of [(username, password) --> permissions] +// This map is populated lazily as users send auth requests. +// First auth request for a user will populate the map with +// the user info (password and permissions) and subsequent +// requests will check the map for the user. +// If user is present in the map then we use this map for both +// authentication and authorization. +#[derive(Debug, Default)] +pub struct AuthMap { + inner: HashMap<(String, String), Vec>, +} + +impl AuthMap { + pub fn add_user(&mut self, username: String, password: String, permissions: Vec) { + self.inner.insert((username, password), permissions); + } + + pub fn remove(&mut self, username: &str) { + self.inner.retain(|(x, _), _| x != username) + } + + // returns None if user is not in the map + // Otherwise returns Some(is_authenticated) + pub fn check_auth( + &self, + key: &(String, String), + required_action: Action, + on_stream: Option<&str>, + ) -> Option { + self.inner.get(key).map(|perms| { + perms.iter().any(|user_perm| { + match *user_perm { + // if any action is ALL then we we authorize + Permission::Unit(action) => action == required_action || action == Action::All, + Permission::Stream(action, ref stream) => { + let ok_stream = if let Some(on_stream) = on_stream { + stream == on_stream || stream == "*" + } else { + // if no stream to match then stream check is not needed + true + }; + (action == required_action || action == Action::All) && ok_stream + } + } + }) + }) + } +} + +// UserMap is a map of [username --> User] +// This map is populated at startup with the list of users from parseable.json file +#[derive(Debug, Default, Clone, derive_more::Deref, derive_more::DerefMut)] +pub struct UserMap(HashMap); + +impl UserMap { + pub fn insert(&mut self, user: User) { + self.0.insert(user.username.clone(), user); + } +} + +impl From> for UserMap { + fn from(users: Vec) -> Self { + let mut map = Self::default(); + map.extend(users.into_iter().map(|user| (user.username.clone(), user))); + map + } +} diff --git a/server/src/rbac/user.rs b/server/src/rbac/user.rs index aeb32af8d..a0a83e4a0 100644 --- a/server/src/rbac/user.rs +++ b/server/src/rbac/user.rs @@ -16,7 +16,7 @@ * */ -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, @@ -27,8 +27,11 @@ use rand::distributions::{Alphanumeric, DistString}; use crate::option::CONFIG; -use super::role::{model::DefaultPrivilege, Permission, RoleBuilder}; +use crate::rbac::role::{model::DefaultPrivilege, Permission, RoleBuilder}; +// Represents a User in the system +// can be the root admin user (set with env vars at startup / restart) +// or user(s) created by the root user #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct User { pub username: String, @@ -110,20 +113,3 @@ pub fn get_admin_user() -> User { role: vec![DefaultPrivilege::Admin], } } - -#[derive(Debug, Default, Clone, derive_more::Deref, derive_more::DerefMut)] -pub struct UserMap(HashMap); - -impl UserMap { - pub fn insert(&mut self, user: User) { - self.0.insert(user.username.clone(), user); - } -} - -impl From> for UserMap { - fn from(users: Vec) -> Self { - let mut map = Self::default(); - map.extend(users.into_iter().map(|user| (user.username.clone(), user))); - map - } -}