diff --git a/proxmox-rest-server/src/api_config.rs b/proxmox-rest-server/src/api_config.rs index fee94e88..e09f7af9 100644 --- a/proxmox-rest-server/src/api_config.rs +++ b/proxmox-rest-server/src/api_config.rs @@ -26,6 +26,7 @@ pub struct ApiConfig { templates: RwLock>, template_files: RwLock>, request_log: Option>>, + auth_log: Option>>, pub api_auth: Arc, get_index_fn: GetIndexFn, } @@ -46,6 +47,7 @@ impl ApiConfig { templates: RwLock::new(Handlebars::new()), template_files: RwLock::new(HashMap::new()), request_log: None, + auth_log: None, api_auth, get_index_fn, }) @@ -172,7 +174,7 @@ impl ApiConfig { self.request_log = Some(Arc::clone(&request_log)); commando_sock.register_command("api-access-log-reopen".into(), move |_args| { - println!("re-opening log file"); + println!("re-opening access-log file"); request_log.lock().unwrap().reopen()?; Ok(serde_json::Value::Null) })?; @@ -180,7 +182,46 @@ impl ApiConfig { Ok(()) } - pub fn get_file_log(&self) -> Option<&Arc>> { + pub fn enable_auth_log

( + &mut self, + path: P, + dir_opts: Option, + file_opts: Option, + commando_sock: &mut CommandoSocket, + ) -> Result<(), Error> + where + P: Into + { + let path: PathBuf = path.into(); + if let Some(base) = path.parent() { + if !base.exists() { + create_path(base, None, dir_opts).map_err(|err| format_err!("{}", err))?; + } + } + + let logger_options = FileLogOptions { + append: true, + prefix_time: true, + file_opts: file_opts.unwrap_or(CreateOptions::default()), + ..Default::default() + }; + let auth_log = Arc::new(Mutex::new(FileLogger::new(&path, logger_options)?)); + self.auth_log = Some(Arc::clone(&auth_log)); + + commando_sock.register_command("api-auth-log-reopen".into(), move |_args| { + println!("re-opening auth-log file"); + auth_log.lock().unwrap().reopen()?; + Ok(serde_json::Value::Null) + })?; + + Ok(()) + } + + pub fn get_access_log(&self) -> Option<&Arc>> { self.request_log.as_ref() } + + pub fn get_auth_log(&self) -> Option<&Arc>> { + self.auth_log.as_ref() + } } diff --git a/proxmox-rest-server/src/environment.rs b/proxmox-rest-server/src/environment.rs index 0548b5bc..57d0d7d5 100644 --- a/proxmox-rest-server/src/environment.rs +++ b/proxmox-rest-server/src/environment.rs @@ -1,24 +1,65 @@ +use std::sync::Arc; +use std::net::SocketAddr; + use serde_json::{json, Value}; use proxmox::api::{RpcEnvironment, RpcEnvironmentType}; +use crate::ApiConfig; + /// Encapsulates information about the runtime environment pub struct RestEnvironment { env_type: RpcEnvironmentType, result_attributes: Value, auth_id: Option, - client_ip: Option, + client_ip: Option, + api: Arc, } impl RestEnvironment { - pub fn new(env_type: RpcEnvironmentType) -> Self { + pub fn new(env_type: RpcEnvironmentType, api: Arc) -> Self { Self { result_attributes: json!({}), auth_id: None, client_ip: None, env_type, + api, } } + + pub fn api_config(&self) -> &ApiConfig { + &self.api + } + + pub fn log_auth(&self, auth_id: &str) { + let msg = format!("successful auth for user '{}'", auth_id); + log::info!("{}", msg); + if let Some(auth_logger) = self.api.get_auth_log() { + auth_logger.lock().unwrap().log(&msg); + } + } + + pub fn log_failed_auth(&self, failed_auth_id: Option, msg: &str) { + let msg = match (self.client_ip, failed_auth_id) { + (Some(peer), Some(user)) => { + format!("authentication failure; rhost={} user={} msg={}", peer, user, msg) + } + (Some(peer), None) => { + format!("authentication failure; rhost={} msg={}", peer, msg) + } + (None, Some(user)) => { + format!("authentication failure; rhost=unknown user={} msg={}", user, msg) + } + (None, None) => { + format!("authentication failure; rhost=unknown msg={}", msg) + } + }; + log::error!("{}", msg); + if let Some(auth_logger) = self.api.get_auth_log() { + auth_logger.lock().unwrap().log(&msg); + } + } + } impl RpcEnvironment for RestEnvironment { @@ -43,11 +84,11 @@ impl RpcEnvironment for RestEnvironment { self.auth_id.clone() } - fn set_client_ip(&mut self, client_ip: Option) { + fn set_client_ip(&mut self, client_ip: Option) { self.client_ip = client_ip; } - fn get_client_ip(&self) -> Option { + fn get_client_ip(&self) -> Option { self.client_ip } } diff --git a/src/server/rest.rs b/src/server/rest.rs index 2083743b..59fbb396 100644 --- a/src/server/rest.rs +++ b/src/server/rest.rs @@ -29,12 +29,11 @@ use proxmox::api::{ RpcEnvironmentType, UserInformation, }; use proxmox::http_err; -use proxmox::tools::fs::CreateOptions; use pbs_tools::compression::{DeflateEncoder, Level}; use pbs_tools::stream::AsyncReaderStream; use proxmox_rest_server::{ - ApiConfig, FileLogger, FileLogOptions, AuthError, RestEnvironment, CompressionMethod, + ApiConfig, FileLogger, AuthError, RestEnvironment, CompressionMethod, extract_cookie, normalize_uri_path, }; use proxmox_rest_server::formatter::*; @@ -200,22 +199,6 @@ fn log_response( } } -pub fn auth_logger() -> Result { - let backup_user = pbs_config::backup_user()?; - - let file_opts = CreateOptions::new() - .owner(backup_user.uid) - .group(backup_user.gid); - - let logger_options = FileLogOptions { - append: true, - prefix_time: true, - file_opts, - ..Default::default() - }; - FileLogger::new(pbs_buildcfg::API_AUTH_LOG_FN, logger_options) -} - fn get_proxied_peer(headers: &HeaderMap) -> Option { lazy_static! { static ref RE: Regex = Regex::new(r#"for="([^"]+)""#).unwrap(); @@ -272,7 +255,7 @@ impl tower_service::Service> for ApiService { .body(err.into())? } }; - let logger = config.get_file_log(); + let logger = config.get_access_log(); log_response(logger, &peer, method, &path, &response, user_agent); Ok(response) } @@ -631,7 +614,7 @@ async fn handle_request( } let env_type = api.env_type(); - let mut rpcenv = RestEnvironment::new(env_type); + let mut rpcenv = RestEnvironment::new(env_type, Arc::clone(&api)); rpcenv.set_client_ip(Some(*peer)); @@ -675,11 +658,8 @@ async fn handle_request( format_err!("no authentication credentials provided.") } }; - let peer = peer.ip(); - auth_logger()?.log(format!( - "authentication failure; rhost={} msg={}", - peer, err - )); + // fixme: log Username?? + rpcenv.log_failed_auth(None, &err.to_string()); // always delay unauthorized calls by 3 seconds (from start of request) let err = http_err!(UNAUTHORIZED, "authentication failed - {}", err);