diff --git a/server/src/handlers/http.rs b/server/src/handlers/http.rs index 9e3b381cd..60a7e01c4 100644 --- a/server/src/handlers/http.rs +++ b/server/src/handlers/http.rs @@ -20,10 +20,7 @@ use std::fs::File; use std::io::BufReader; use actix_cors::Cors; -use actix_web::dev::ServiceRequest; -use actix_web::{web, App, HttpMessage, HttpServer, Route}; -use actix_web_httpauth::extractors::basic::BasicAuth; -use actix_web_httpauth::middleware::HttpAuthentication; +use actix_web::{web, App, HttpServer, Route}; use actix_web_prometheus::PrometheusMetrics; use actix_web_static_files::ResourceFiles; use rustls::{Certificate, PrivateKey, ServerConfig}; @@ -31,9 +28,8 @@ use rustls_pemfile::{certs, pkcs8_private_keys}; use crate::option::CONFIG; use crate::rbac::role::Action; -use crate::rbac::Users; -use self::middleware::{Authorization, DisAllowRootUser}; +use self::middleware::{Auth, DisAllowRootUser}; mod health_check; mod ingest; @@ -65,21 +61,6 @@ macro_rules! create_app { }; } -async fn authenticate( - req: ServiceRequest, - credentials: BasicAuth, -) -> Result { - let username = credentials.user_id().trim().to_owned(); - let password = credentials.password().unwrap().trim(); - - if Users.authenticate(&username, password) { - req.extensions_mut().insert(username); - Ok(req) - } else { - Err((actix_web::error::ErrorUnauthorized("Unauthorized"), req)) - } -} - pub async fn run_http(prometheus: PrometheusMetrics) -> anyhow::Result<()> { let ssl_acceptor = match ( &CONFIG.parseable.tls_cert_path, @@ -224,7 +205,8 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) { .service( web::resource("/{username}/role") // PUT /user/{username}/roles => Put roles for user - .route(web::put().to(rbac::put_role).authorize(Action::PutRoles)), + .route(web::put().to(rbac::put_role).authorize(Action::PutRoles)) + .route(web::get().to(rbac::get_role).authorize(Action::GetRole)), ) // Deny request if username is same as the env variable P_USERNAME. .wrap(DisAllowRootUser); @@ -266,8 +248,7 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) { logstream_api, ), ) - .service(user_api) - .wrap(HttpAuthentication::basic(authenticate)), + .service(user_api), ) // GET "/" ==> Serve the static frontend directory .service(ResourceFiles::new("/", generated)); @@ -288,14 +269,14 @@ trait RouteExt { impl RouteExt for Route { fn authorize(self, action: Action) -> Self { - self.wrap(Authorization { + self.wrap(Auth { action, stream: false, }) } fn authorize_for_stream(self, action: Action) -> Self { - self.wrap(Authorization { + self.wrap(Auth { action, stream: true, }) diff --git a/server/src/handlers/http/middleware.rs b/server/src/handlers/http/middleware.rs index 7c4194ff0..e9f3f9c07 100644 --- a/server/src/handlers/http/middleware.rs +++ b/server/src/handlers/http/middleware.rs @@ -22,8 +22,9 @@ use std::future::{ready, Ready}; use actix_web::{ dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, error::{ErrorBadRequest, ErrorUnauthorized}, - Error, HttpMessage, + Error, }; +use actix_web_httpauth::extractors::basic::BasicAuth; use futures_util::future::LocalBoxFuture; use crate::{ @@ -31,12 +32,12 @@ use crate::{ rbac::{role::Action, Users}, }; -pub struct Authorization { +pub struct Auth { pub action: Action, pub stream: bool, } -impl Transform for Authorization +impl Transform for Auth where S: Service, Error = Error>, S::Future: 'static, @@ -45,11 +46,11 @@ where type Response = ServiceResponse; type Error = Error; type InitError = (); - type Transform = AuthorizationMiddleware; + type Transform = AuthMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ready(Ok(AuthorizationMiddleware { + ready(Ok(AuthMiddleware { action: self.action, match_stream: self.stream, service, @@ -57,13 +58,13 @@ where } } -pub struct AuthorizationMiddleware { +pub struct AuthMiddleware { action: Action, match_stream: bool, service: S, } -impl Service for AuthorizationMiddleware +impl Service for AuthMiddleware where S: Service, Error = Error>, S::Future: 'static, @@ -75,25 +76,34 @@ where forward_ready!(service); - fn call(&self, req: ServiceRequest) -> Self::Future { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + // Extract username and password 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(); + // password is not mandatory by basic auth standard. + // If not provided then treat as empty string + let password = creds.password().unwrap_or("").trim().to_owned(); + (username, password) + }); + let stream = if self.match_stream { req.match_info().get("logstream") } else { None }; - let extensions = req.extensions(); - let username = extensions - .get::() - .expect("authentication layer verified username"); - let is_auth = Users.check_permission(username, self.action, stream); - drop(extensions); + + let auth_result: Result = creds.map(|(username, password)| { + Users.authenticate(username, password, self.action, stream) + }); let fut = self.service.call(req); Box::pin(async move { - if !is_auth { + if !auth_result? { return Err(ErrorUnauthorized("Not authorized")); } + fut.await }) } diff --git a/server/src/handlers/http/rbac.rs b/server/src/handlers/http/rbac.rs index 0d8eb4ed0..8e295e9b5 100644 --- a/server/src/handlers/http/rbac.rs +++ b/server/src/handlers/http/rbac.rs @@ -66,6 +66,16 @@ pub async fn put_user(username: web::Path) -> Result) -> Result { + if !Users.contains(&username) { + return Err(RBACError::UserDoesNotExist); + }; + + Ok(web::Json(Users.get_role(&username))) +} + // Handler for DELETE /api/v1/user/delete/{username} pub async fn delete_user(username: web::Path) -> Result { let username = username.into_inner(); @@ -116,7 +126,6 @@ pub async fn put_role( let role = role.into_inner(); let role: Vec = serde_json::from_value(role)?; - let permissions; if !Users.contains(&username) { return Err(RBACError::UserDoesNotExist); }; @@ -127,8 +136,7 @@ pub async fn put_role( .iter_mut() .find(|user| user.username == username) { - user.role = role; - permissions = user.permissions() + user.role.clone_from(&role); } else { // should be unreachable given state is always consistent return Err(RBACError::UserDoesNotExist); @@ -136,7 +144,7 @@ pub async fn put_role( put_metadata(&metadata).await?; // update in mem table - Users.put_permissions(&username, &permissions); + Users.put_role(&username, role); Ok(format!("Roles updated successfully for {}", username)) } diff --git a/server/src/rbac.rs b/server/src/rbac.rs index d2fb21293..0fbb59ce7 100644 --- a/server/src/rbac.rs +++ b/server/src/rbac.rs @@ -16,136 +16,146 @@ * */ -use std::sync::RwLock; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use once_cell::sync::OnceCell; +use crate::option::CONFIG; + use self::{ - role::{Action, Permission}, - user::{get_admin_user, verify, User, UserMap, UserPermMap}, + auth::AuthMap, + role::{model::DefaultPrivilege, Action}, + user::{get_admin_user, User, UserMap}, }; +pub mod auth; pub mod role; pub mod user; -pub static USER_AUTHENTICATION_MAP: OnceCell> = OnceCell::new(); -pub static USER_AUTHORIZATION_MAP: OnceCell> = OnceCell::new(); +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") +} pub struct Users; impl Users { pub fn put_user(&self, user: User) { - USER_AUTHORIZATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .insert(&user); - - USER_AUTHENTICATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .insert(user); + mut_auth_map().remove(&user.username); + mut_user_map().insert(user); } pub fn list_users(&self) -> Vec { - USER_AUTHORIZATION_MAP - .get() - .expect("map is set") - .read() - .unwrap() - .keys() - .cloned() - .collect() + user_map().keys().cloned().collect() + } + + pub fn get_role(&self, username: &str) -> Vec { + user_map() + .get(username) + .map(|user| user.role.clone()) + .unwrap_or_default() } pub fn delete_user(&self, username: &str) { - USER_AUTHORIZATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .remove(username); - - USER_AUTHENTICATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .remove(username); + mut_user_map().remove(username); + mut_auth_map().remove(username); } pub fn change_password_hash(&self, username: &str, hash: &String) { - if let Some(entry) = USER_AUTHENTICATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .get_mut(username) - { - entry.clone_from(hash) + if let Some(user) = mut_user_map().get_mut(username) { + user.password_hash.clone_from(hash) }; + mut_auth_map().remove(username); } - pub fn put_permissions(&self, username: &str, roles: &Vec) { - if let Some(entry) = USER_AUTHORIZATION_MAP - .get() - .expect("map is set") - .write() - .unwrap() - .get_mut(username) - { - entry.clone_from(roles) + pub fn put_role(&self, username: &str, roles: Vec) { + if let Some(user) = mut_user_map().get_mut(username) { + user.role = roles; + mut_auth_map().remove(username) }; } pub fn contains(&self, username: &str) -> bool { - USER_AUTHENTICATION_MAP - .get() - .expect("map is set") - .read() - .unwrap() - .contains_key(username) + user_map().contains_key(username) } - pub fn authenticate(&self, username: &str, password: &str) -> bool { - if let Some(hash) = USER_AUTHENTICATION_MAP - .get() - .expect("map is set") - .read() - .unwrap() - .get(username) - { - verify(hash, password) - } else { - false + pub fn authenticate( + &self, + username: String, + password: String, + action: Action, + stream: Option<&str>, + ) -> bool { + let key = (username, password); + // try fetch from auth map for faster auth flow + if let Some(res) = auth_map().check_auth(&key, action, stream) { + return res; + } + + 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.verify_password(&password) { + let mut auth_map = mut_auth_map(); + auth_map.add_user(username.clone(), password.clone(), user.permissions()); + // verify auth and return + let key = (username, password); + return auth_map + .check_auth(&key, action, stream) + .expect("entry for this key just added"); + } } - } - pub fn check_permission(&self, username: &str, action: Action, stream: Option<&str>) -> bool { - USER_AUTHORIZATION_MAP - .get() - .expect("map is set") - .read() - .unwrap() - .has_perm(username, action, stream) + false } } pub fn set_user_map(users: Vec) { - let mut perm_map = UserPermMap::from(&users); let mut user_map = UserMap::from(users); + let mut auth_map = AuthMap::default(); let admin = get_admin_user(); - perm_map.insert(&admin); + let admin_permissions = admin.permissions(); user_map.insert(admin); + auth_map.add_user( + CONFIG.parseable.username.clone(), + CONFIG.parseable.password.clone(), + admin_permissions, + ); - USER_AUTHENTICATION_MAP + USER_MAP .set(RwLock::new(user_map)) .expect("map is only set once"); - - USER_AUTHORIZATION_MAP - .set(RwLock::new(perm_map)) + 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 new file mode 100644 index 000000000..52d33eab3 --- /dev/null +++ b/server/src/rbac/auth.rs @@ -0,0 +1,65 @@ +/* + * 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/role.rs b/server/src/rbac/role.rs index 45e8a21c4..50d5a0c62 100644 --- a/server/src/rbac/role.rs +++ b/server/src/rbac/role.rs @@ -35,6 +35,7 @@ pub enum Action { ListUser, DeleteUser, PutRoles, + GetRole, All, } @@ -71,10 +72,10 @@ impl RoleBuilder { Action::Ingest => Permission::Stream(action, self.stream.clone().unwrap()), Action::Query => Permission::Stream(action, self.stream.clone().unwrap()), Action::CreateStream => Permission::Unit(action), + Action::DeleteStream => Permission::Unit(action), Action::ListStream => Permission::Unit(action), Action::GetSchema => Permission::Stream(action, self.stream.clone().unwrap()), Action::GetStats => Permission::Stream(action, self.stream.clone().unwrap()), - Action::DeleteStream => Permission::Stream(action, self.stream.clone().unwrap()), Action::GetRetention => Permission::Stream(action, self.stream.clone().unwrap()), Action::PutRetention => Permission::Stream(action, self.stream.clone().unwrap()), Action::PutAlert => Permission::Stream(action, self.stream.clone().unwrap()), @@ -82,6 +83,7 @@ impl RoleBuilder { Action::PutUser => Permission::Unit(action), Action::ListUser => Permission::Unit(action), Action::PutRoles => Permission::Unit(action), + Action::GetRole => Permission::Unit(action), Action::DeleteUser => Permission::Unit(action), Action::All => Permission::Stream(action, self.stream.clone().unwrap()), }; diff --git a/server/src/rbac/user.rs b/server/src/rbac/user.rs index 199e26916..aeb32af8d 100644 --- a/server/src/rbac/user.rs +++ b/server/src/rbac/user.rs @@ -27,7 +27,7 @@ use rand::distributions::{Alphanumeric, DistString}; use crate::option::CONFIG; -use super::role::{model::DefaultPrivilege, Action, Permission, RoleBuilder}; +use super::role::{model::DefaultPrivilege, Permission, RoleBuilder}; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct User { @@ -65,6 +65,10 @@ impl User { .collect(); perms.into_iter().collect() } + + pub fn verify_password(&self, password: &str) -> bool { + verify(&self.password_hash, password) + } } // Take the password and compare with the hash stored internally (PHC format ==> @@ -90,25 +94,6 @@ fn gen_hash(password: &str) -> String { hashcode } -#[derive(Debug, Default, derive_more::Deref, derive_more::DerefMut)] -pub struct UserMap(HashMap); - -impl UserMap { - pub fn insert(&mut self, user: User) { - self.0.insert(user.username, user.password_hash); - } -} - -impl From> for UserMap { - fn from(users: Vec) -> Self { - let mut user_map = Self::default(); - for user in users { - user_map.insert(user); - } - user_map - } -} - pub struct PassCode { pub password: String, pub hash: String, @@ -127,48 +112,18 @@ pub fn get_admin_user() -> User { } #[derive(Debug, Default, Clone, derive_more::Deref, derive_more::DerefMut)] -pub struct UserPermMap(HashMap>); - -impl UserPermMap { - pub fn insert(&mut self, user: &User) { - self.0.insert(user.username.clone(), user.permissions()); - } +pub struct UserMap(HashMap); - pub fn has_perm( - &self, - username: &str, - required_action: Action, - on_stream: Option<&str>, - ) -> bool { - if let Some(perms) = self.get(username) { - 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 - } - } - }) - } else { - // NO permission set for this user - false - } +impl UserMap { + pub fn insert(&mut self, user: User) { + self.0.insert(user.username.clone(), user); } } -impl From<&Vec> for UserPermMap { - fn from(users: &Vec) -> Self { +impl From> for UserMap { + fn from(users: Vec) -> Self { let mut map = Self::default(); - for user in users { - map.insert(user) - } + map.extend(users.into_iter().map(|user| (user.username.clone(), user))); map } }