Skip to content

Commit

Permalink
Webserver for dynamic analysis
Browse files Browse the repository at this point in the history
Create a webserver to perform a synchronous analysis of the cluster. The
idea is to allow perform an ad-hoc analysis on each request to the
`/evaluate` endopint.

On the future, we may emit kubernetes events for each (new) found issues
on the cluster.
  • Loading branch information
gnieto committed Oct 5, 2019
1 parent 104db35 commit e49cec9
Show file tree
Hide file tree
Showing 17 changed files with 645 additions and 193 deletions.
536 changes: 411 additions & 125 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ members = [
"korrecte-autogen",
"korrecte-cli",
"korrecte-lib",
"korrecte-web",
]
1 change: 0 additions & 1 deletion korrecte-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ edition = "2018"
[dependencies]
clap = {version = "2.33", features = ["yaml"]}
colored = "1.8.0"
toml = "0.5"

korrecte = { path = '../korrecte-lib' }
8 changes: 1 addition & 7 deletions korrecte-cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
#[derive(Debug)]
pub enum CliError {
ConfigLoadError,
MissingPath,
KorrecteError(korrecte::error::KorrecteError),
Io(std::io::Error),
}

impl From<toml::de::Error> for CliError {
fn from(_e: toml::de::Error) -> Self {
CliError::ConfigLoadError
}
}

impl From<korrecte::error::KorrecteError> for CliError {
fn from(e: korrecte::error::KorrecteError) -> Self {
CliError::KorrecteError(e)
Expand Down
64 changes: 17 additions & 47 deletions korrecte-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,40 @@ mod view;
use crate::error::CliError;
use clap::load_yaml;
use clap::{App, ArgMatches};
use korrecte::config::Config;
use korrecte::error::KorrecteError;
use korrecte::kube::api::{ApiObjectRepository, FrozenObjectRepository};
use korrecte::kube::file::FileObjectRepository;
use korrecte::kube::ObjectRepository;
use korrecte::linters::LintCollection;
use korrecte::executor::{ExecutionContextBuilder, ExecutionMode, Executor};
use korrecte::reporting::Reporter;
use korrecte::reporting::SingleThreadedReporter;
use korrecte::view::View;
use std::borrow::Borrow;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use toml;

use crate::view::Cli;
use korrecte::linters::evaluator::{Evaluator, SingleEvaluator};

fn main() -> Result<(), CliError> {
let yaml = load_yaml!("../cli.yaml");
let matches = App::from_yaml(yaml).get_matches();

let cfg_path = matches.value_of("config").unwrap_or("korrecte.toml");
let cfg: Config = load_config(cfg_path).unwrap_or_else(|_| {
println!("Could not load config file");
Config::default()
});
let ctx = ExecutionContextBuilder::default()
.configuration_from_path(&Path::new(
matches.value_of("config").unwrap_or("korrecte.toml"),
))
.unwrap()
.execution_mode(get_execution_mode(&matches).ok_or(CliError::MissingPath)?)
.build();

let reporter = SingleThreadedReporter::default();
let object_repository = build_object_repository(&matches)?;
let executor = Executor::with_context(ctx);
let reporter = executor.execute()?;

let list = LintCollection::all(cfg, &*object_repository);
Cli::render(&reporter.findings());

let evaluator = SingleEvaluator;
evaluator.evaluate(&reporter, &list, object_repository.borrow());

let cli = Cli {};
cli.render(&reporter.findings());
Ok(())
}

fn build_object_repository(
matches: &ArgMatches,
) -> Result<Box<dyn ObjectRepository>, KorrecteError> {
fn get_execution_mode<'a>(matches: &'a ArgMatches) -> Option<ExecutionMode<'a>> {
match matches.value_of("source") {
Some("api") | None => Ok(Box::new(FrozenObjectRepository::from(
ApiObjectRepository::new()?,
))),
Some("api") | None => Some(ExecutionMode::Api),
Some("file") => {
let path = matches
.value_of("path")
.ok_or_else(|| KorrecteError::Generic("Missing file path".into()))?;
Ok(Box::new(FileObjectRepository::new(Path::new(path))?))
let path = matches.value_of("path")?;

Some(ExecutionMode::FileSystem(Path::new(path)))
}
_ => Err(KorrecteError::Generic(
"Could not build an object repository".into(),
)),
_ => None,
}
}

fn load_config(path: &str) -> Result<Config, CliError> {
let mut file = File::open(path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;

Ok(toml::from_str(&buffer)?)
}
5 changes: 2 additions & 3 deletions korrecte-cli/src/view.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use colored::*;
use korrecte::reporting::Finding;
use korrecte::view::View;

pub struct Cli;

impl View for Cli {
fn render(&self, findings: &[Finding]) {
impl Cli {
pub fn render(findings: &[Finding]) {
for finding in findings {
println!(
"{} on {} [{}]. Metadata: {:?}",
Expand Down
1 change: 1 addition & 0 deletions korrecte-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
serde_json = "1.0"
yaml-rust = "0.4"
toml = "0.5"
kube = { version = "0.16.1", features = ["openapi"] }
18 changes: 17 additions & 1 deletion korrecte-lib/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt::{Display, Error, Formatter};

#[derive(Debug)]
#[allow(unused)]
pub enum KorrecteError {
Io(std::io::Error),
KubeConfig(::kube::Error),
Expand All @@ -24,3 +25,18 @@ impl From<::kube::Error> for KorrecteError {
KorrecteError::KubeConfig(e)
}
}

impl Display for KorrecteError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
KorrecteError::Io(e) => write!(f, "Error performing an IO operation: {}", e),
KorrecteError::KubeConfig(e) => write!(f, "Error loading kubeconfig: {}", e),
KorrecteError::Generic(e) => write!(f, "{}", e),
KorrecteError::FailedToLoadYamlFile => write!(f, "Could not load YAML file"),
KorrecteError::YamlDecodeError { ty, version, kind } => {
write!(f, "Could not decode YAML file: {} {} {}", ty, version, kind)
}
KorrecteError::UnrecognizedObject => write!(f, "Unrecognized Kubernetes object"),
}
}
}
98 changes: 98 additions & 0 deletions korrecte-lib/src/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::config::Config;
use crate::error::KorrecteError;
use crate::kube::api::{ApiObjectRepository, FrozenObjectRepository};
use crate::kube::file::FileObjectRepository;
use crate::kube::ObjectRepository;
use crate::linters::evaluator::{Evaluator, SingleEvaluator};
use crate::linters::LintCollection;
use crate::reporting::{Reporter, SingleThreadedReporter};
use std::fs::File;
use std::io::Read;
use std::path::Path;

#[derive(Debug)]
pub enum ConfigError {
CouldNotLoadError,
CouldNotParseError,
}

pub enum ExecutionMode<'a> {
Api,
FileSystem(&'a Path),
}

#[derive(Default)]
pub struct ExecutionContextBuilder<'a> {
mode: Option<ExecutionMode<'a>>,
configuration: Option<Config>,
}

impl<'a> ExecutionContextBuilder<'a> {
pub fn configuration_from_path(
mut self,
path: &Path,
) -> Result<ExecutionContextBuilder<'a>, ConfigError> {
let config = Self::load_config_from_filesystem(path)?;
self.configuration = Some(config);

Ok(self)
}

pub fn execution_mode(mut self, mode: ExecutionMode<'a>) -> ExecutionContextBuilder<'a> {
self.mode = Some(mode);

self
}

pub fn build(self) -> ExecutionContext<'a> {
ExecutionContext {
mode: self.mode.unwrap_or(ExecutionMode::Api),
configuration: self.configuration.unwrap_or_default(),
}
}

fn load_config_from_filesystem(path: &Path) -> Result<Config, ConfigError> {
let mut file = File::open(path).map_err(|_| ConfigError::CouldNotLoadError)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)
.map_err(|_| ConfigError::CouldNotLoadError)?;

Ok(toml::from_str(&buffer).map_err(|_| ConfigError::CouldNotParseError)?)
}
}

pub struct ExecutionContext<'a> {
mode: ExecutionMode<'a>,
configuration: Config,
}

pub struct Executor<'a> {
context: ExecutionContext<'a>,
}

impl<'a> Executor<'a> {
pub fn with_context(context: ExecutionContext<'a>) -> Executor<'a> {
Executor { context }
}

pub fn execute(self) -> Result<impl Reporter, KorrecteError> {
let reporter = SingleThreadedReporter::default();
let object_repository = self.load_object_repository()?;
let lints = LintCollection::all(self.context.configuration, &*object_repository);
let evaluator = SingleEvaluator;
evaluator.evaluate(&reporter, &lints, &*object_repository);

Ok(reporter)
}

fn load_object_repository(&self) -> Result<Box<dyn ObjectRepository>, KorrecteError> {
match self.context.mode {
ExecutionMode::Api => Ok(Box::new(FrozenObjectRepository::from(
ApiObjectRepository::new()?,
))),
ExecutionMode::FileSystem(path) => {
Ok(Box::new(FileObjectRepository::new(Path::new(path))?))
}
}
}
}
2 changes: 1 addition & 1 deletion korrecte-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pub mod config;
pub mod error;
pub mod executor;
pub mod kube;
pub mod linters;
pub mod reporting;
pub mod view;

mod visitor;

Expand Down
1 change: 1 addition & 0 deletions korrecte-lib/src/linters/evaluator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod single_evaluator;

pub use crate::linters::evaluator::single_evaluator::SingleEvaluator;

#[allow(clippy::ptr_arg)]
pub trait Evaluator {
fn evaluate(
&self,
Expand Down
5 changes: 3 additions & 2 deletions korrecte-lib/src/linters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use crate::config::Config;
use crate::kube::ObjectRepository;
use crate::linters;
use serde::Serialize;

pub mod evaluator;
mod lint;
pub(crate) mod lints;
pub use lint::{KubeObjectType, Lint};

#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
pub enum Group {
Audit,
Configuration,
Security,
}

#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(Clone, Eq, PartialEq, Debug, Serialize)]
pub struct LintSpec {
pub group: Group,
pub name: String,
Expand Down
3 changes: 2 additions & 1 deletion korrecte-lib/src/reporting/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::linters::LintSpec;
use kube::api::ObjectMeta;
use serde::Serialize;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
Expand All @@ -10,7 +11,7 @@ pub trait Reporter {
fn findings(&self) -> Vec<Finding>;
}

#[derive(Clone)]
#[derive(Clone, Serialize)]
pub struct Finding {
spec: LintSpec,
object_metadata: ObjectMeta,
Expand Down
5 changes: 0 additions & 5 deletions korrecte-lib/src/view/mod.rs

This file was deleted.

14 changes: 14 additions & 0 deletions korrecte-web/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "korrecte-web"
version = "0.1.0"
authors = ["Guillem Nieto <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
korrecte = { path="../korrecte-lib" }
gotham = "*"
hyper = "*"
mime = "*"
serde_json = "*"
Loading

0 comments on commit e49cec9

Please sign in to comment.