blob: 23b49cebf21669aeb464e4971d818dcb6bc63bcf [file] [log] [blame]
// 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());
}
}