| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| config::{Config, LoggingVerbosity}, |
| logo, |
| rest::service::RestService, |
| rest::visualizer::Visualizer, |
| shell::Shell, |
| }, |
| anyhow::{Error, Result}, |
| clap::{App, Arg, ArgMatches}, |
| log::error, |
| scrutiny::{ |
| engine::{ |
| dispatcher::ControllerDispatcher, manager::PluginManager, plugin::Plugin, |
| scheduler::CollectorScheduler, |
| }, |
| model::model::DataModel, |
| }, |
| simplelog::{Config as SimpleLogConfig, LevelFilter, WriteLogger}, |
| std::env, |
| std::fs::File, |
| std::io::{self, BufRead, BufReader, ErrorKind}, |
| std::sync::{Arc, Mutex, RwLock}, |
| }; |
| |
| const FUCHSIA_DIR: &str = "FUCHSIA_DIR"; |
| |
| /// Holds a reference to core objects required by the application to run. |
| pub struct Scrutiny { |
| manager: Arc<Mutex<PluginManager>>, |
| dispatcher: Arc<RwLock<ControllerDispatcher>>, |
| scheduler: Arc<Mutex<CollectorScheduler>>, |
| visualizer: Option<Arc<RwLock<Visualizer>>>, |
| shell: Shell, |
| config: Config, |
| } |
| |
| impl Scrutiny { |
| /// Creates the DataModel, ControllerDispatcher, CollectorScheduler and |
| /// PluginManager. |
| pub fn new(config: Config) -> Result<Self> { |
| let log_level = match config.runtime.logging.verbosity { |
| LoggingVerbosity::Error => LevelFilter::Error, |
| LoggingVerbosity::Warn => LevelFilter::Warn, |
| LoggingVerbosity::Info => LevelFilter::Info, |
| LoggingVerbosity::Debug => LevelFilter::Debug, |
| LoggingVerbosity::Trace => LevelFilter::Trace, |
| LoggingVerbosity::Off => LevelFilter::Off, |
| }; |
| if log_level != LevelFilter::Off |
| && config.launch.command.is_none() |
| && config.launch.script_path.is_none() |
| { |
| logo::print_logo(); |
| } |
| let _ = WriteLogger::init( |
| log_level, |
| SimpleLogConfig::default(), |
| File::create(config.runtime.logging.path.clone()).unwrap(), |
| ); |
| let model = Arc::new(DataModel::connect(config.runtime.model.path.clone())?); |
| let dispatcher = Arc::new(RwLock::new(ControllerDispatcher::new(Arc::clone(&model)))); |
| let visualizer = if let Some(server_config) = &config.runtime.server { |
| Some(Arc::new(RwLock::new(Visualizer::new( |
| env::var(FUCHSIA_DIR).expect("Unable to retrieve $FUCHSIA_DIR, has it been set?"), |
| server_config.visualizer_path.clone(), |
| )))) |
| } else { |
| None |
| }; |
| let scheduler = Arc::new(Mutex::new(CollectorScheduler::new(Arc::clone(&model)))); |
| let manager = Arc::new(Mutex::new(PluginManager::new( |
| Arc::clone(&scheduler), |
| Arc::clone(&dispatcher), |
| ))); |
| let shell = Shell::new(Arc::clone(&manager), Arc::clone(&dispatcher)); |
| Ok(Self { manager, dispatcher, scheduler, visualizer, shell, config }) |
| } |
| |
| /// Declares the Scrutiny command line interface. |
| fn cmdline() -> App<'static, 'static> { |
| App::new("scrutiny") |
| .version("1.0") |
| .author("Fuchsia Authors") |
| .about("An extendable security auditing framework") |
| .arg( |
| Arg::with_name("command") |
| .short("c") |
| .help("Run a single command") |
| .value_name("command") |
| .takes_value(true), |
| ) |
| .arg( |
| Arg::with_name("model") |
| .short("m") |
| .help("The uri of the data model.") |
| .default_value("/tmp/scrutiny/"), |
| ) |
| .arg( |
| Arg::with_name("log") |
| .short("l") |
| .help("Path to output scrutiny.log") |
| .default_value("/tmp/scrutiny.log"), |
| ) |
| .arg( |
| Arg::with_name("port") |
| .short("p") |
| .help("The port to run the scrutiny service on.") |
| .default_value("8080"), |
| ) |
| .arg( |
| Arg::with_name("script") |
| .short("s") |
| .help("Run a file as a scrutiny script") |
| .value_name("script") |
| .takes_value(true), |
| ) |
| .arg( |
| Arg::with_name("verbosity") |
| .short("v") |
| .help("The verbosity level of logging") |
| .possible_values(&["off", "error", "warn", "info", "debug", "trace"]) |
| .default_value("info"), |
| ) |
| .arg( |
| Arg::with_name("visualizer") |
| .short("i") |
| .help( |
| "The root path (relative to $FUCHSIA_DIR) for the visualizer interface. \ |
| .html, .css, and .json files relative to this root path will \ |
| be served relative to the scrutiny service root.", |
| ) |
| .default_value("/scripts/scrutiny"), |
| ) |
| } |
| |
| /// Parses all the command line arguments passed in and returns a |
| /// ScrutinyConfig. |
| fn cmdline_to_config(args: ArgMatches<'static>) -> Result<Config> { |
| let mut batch_mode = false; |
| let mut config = Config::default(); |
| if let Some(command) = args.value_of("command") { |
| config.launch.command = Some(command.to_string()); |
| batch_mode = true; |
| } |
| if let Some(script_path) = args.value_of("script") { |
| config.launch.script_path = Some(script_path.to_string()); |
| batch_mode = true; |
| } |
| // If we are running a script or a command we disable the server as |
| // it isn't going to be used. |
| if batch_mode { |
| config.runtime.server = None; |
| } |
| config.runtime.logging.path = args.value_of("log").unwrap().to_string(); |
| config.runtime.logging.verbosity = match args.value_of("verbosity").unwrap() { |
| "error" => LoggingVerbosity::Error, |
| "warn" => LoggingVerbosity::Warn, |
| "info" => LoggingVerbosity::Info, |
| "debug" => LoggingVerbosity::Debug, |
| "trace" => LoggingVerbosity::Trace, |
| _ => LoggingVerbosity::Off, |
| }; |
| config.runtime.model.path = args.value_of("model").unwrap().to_string(); |
| if let Some(server_config) = &mut config.runtime.server { |
| if let Ok(port) = args.value_of("port").unwrap().parse::<u16>() { |
| server_config.port = port; |
| } else { |
| error!("Port provided was not a valid port number."); |
| return Err(Error::new(io::Error::new( |
| ErrorKind::InvalidInput, |
| "Invaild argument.", |
| ))); |
| } |
| server_config.visualizer_path = args.value_of("visualizer").unwrap().to_string(); |
| } |
| Ok(config) |
| } |
| |
| /// Parses all the command line arguments passed in from the environment |
| /// and returns a ScrutinyConfig. |
| pub fn args_from_env() -> Result<Config> { |
| let app = Scrutiny::cmdline(); |
| Scrutiny::cmdline_to_config(app.get_matches()) |
| } |
| |
| /// Parses arguments directly from the vector instead of the environmetn |
| /// and returns a ScrutinyConfig. |
| pub fn args_inline(inline_arguments: Vec<String>) -> Result<Config> { |
| let app = Scrutiny::cmdline(); |
| Scrutiny::cmdline_to_config(app.get_matches_from(inline_arguments)) |
| } |
| |
| /// Utility function to register a plugin. |
| pub fn plugin(&mut self, plugin: impl Plugin + 'static) -> Result<()> { |
| self.manager.lock().unwrap().register_and_load(Box::new(plugin)) |
| } |
| |
| /// Returns an arc to the dispatcher controller that can be exposed to |
| /// plugins that may wish to use it for managemnet. |
| pub fn dispatcher(&self) -> Arc<RwLock<ControllerDispatcher>> { |
| Arc::clone(&self.dispatcher) |
| } |
| |
| /// Returns an arc to the collector scheduler that can be exposed to plugins |
| /// that may wish to use it for management. |
| pub fn scheduler(&self) -> Arc<Mutex<CollectorScheduler>> { |
| Arc::clone(&self.scheduler) |
| } |
| |
| /// Returns an arc to the plugin manager that can be exposed to plugins that |
| /// may wish to use it for management. |
| pub fn plugin_manager(&self) -> Arc<Mutex<PluginManager>> { |
| Arc::clone(&self.manager) |
| } |
| |
| /// Schedules the DataCollectors to run and starts the REST service. |
| pub fn run(&mut self) -> Result<()> { |
| self.scheduler.lock().unwrap().schedule()?; |
| |
| if let Some(command) = &self.config.launch.command { |
| // Spin lock on the schedulers to finish. |
| while !self.scheduler.lock().unwrap().all_idle() {} |
| self.shell.execute(command.to_string()); |
| } else if let Some(script) = &self.config.launch.script_path { |
| // Spin lock on the schedulers to finish. |
| while !self.scheduler.lock().unwrap().all_idle() {} |
| let script_file = BufReader::new(File::open(script)?); |
| for line in script_file.lines() { |
| self.shell.execute(line?); |
| } |
| } else { |
| if let Some(server_config) = &self.config.runtime.server { |
| RestService::spawn( |
| self.dispatcher.clone(), |
| self.visualizer.clone().unwrap(), |
| server_config.port, |
| )?; |
| } |
| self.shell.run(); |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn scrutiny_script_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-s", "foo"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.launch.script_path.unwrap(), "foo".to_string()); |
| } |
| |
| #[test] |
| fn scrutiny_command_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-c", "bar"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.launch.command.unwrap(), "bar".to_string()); |
| } |
| |
| #[test] |
| fn scrutiny_model_path_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-m", "baz"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.runtime.model.path, "baz".to_string()); |
| } |
| |
| #[test] |
| fn scrutiny_logging_path_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-l", "test_log"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.runtime.logging.path, "test_log".to_string()); |
| } |
| |
| #[test] |
| fn scrutiny_logging_verbosity_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-v", "warn"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.runtime.logging.verbosity, LoggingVerbosity::Warn); |
| } |
| |
| #[test] |
| fn scrutiny_port_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-p", "1234"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.runtime.server.unwrap().port, 1234); |
| } |
| |
| #[test] |
| fn scrutiny_visualizer_args() { |
| let result = Scrutiny::args_inline( |
| vec!["scrutiny", "-i", "test_viz"].into_iter().map(String::from).collect(), |
| ) |
| .unwrap(); |
| assert_eq!(result.runtime.server.unwrap().visualizer_path, "test_viz".to_string()); |
| } |
| } |