evectl 0.1.0-alpha.2

Controller for EveBox and Suricata
// SPDX-FileCopyrightText: (C) 2024 Jason Ish <jason@codemonkey.net>
// SPDX-License-Identifier: MIT

use clap::Parser;
use std::io::IsTerminal;

const DEFAULT_IMAGE: &str = "jasonish/suricata";
const DEFAULT_VERSION: &str = "latest";

const AFTER_HELP: &str = r#"Examples:

- omnibox tool suricata -- -i eth0
- omnibox tool suricata --version master -- -i eth0 -l /tmp

Notes:
  - If no log directory provided, the current directory will be used.
"#;

#[derive(Parser, Debug)]
#[command(after_help = AFTER_HELP)]
pub(crate) struct Args {
    /// Version of Suricata to run (the image tag)
    #[arg(long)]
    version: Option<String>,

    /// Docker image to use
    #[arg(long)]
    image: Option<String>,

    /// Suricata arguments, must follow '--'
    args: Vec<String>,
}

pub(crate) fn run(args: Args) {
    let mut docker_args: Vec<String> = vec![];
    let mut suricata_args: Vec<String> = vec![];

    let image = if let Some(image) = args.image {
        image
    } else {
        let version = if let Some(version) = args.version {
            version
        } else {
            DEFAULT_VERSION.to_string()
        };
        format!("{}:{}", DEFAULT_IMAGE, version)
    };

    let args = args.args;

    let mut has_log_directory = false;

    let mut i = 0;
    while i < args.len() {
        let arg = &args[i];

        if let Some(mut val) = arg.strip_prefix("-l") {
            if val.is_empty() {
                val = &args[i + 1];
            }
            let path = std::fs::canonicalize(val).unwrap();
            docker_args.push(format!("--volume={}:/var/log/suricata", path.display()));
            suricata_args.push("-l".to_string());
            suricata_args.push("/var/log/suricata".to_string());
            has_log_directory = true;
            i += 1;
        } else if let Some(mut val) = arg.strip_prefix("-r") {
            if val.is_empty() {
                val = &args[i + 1];
            }
            let path = std::fs::canonicalize(val).unwrap();
            docker_args.push(format!("--volume={}:/tmp/input.pcap", path.display()));
            suricata_args.push("-r".to_string());
            suricata_args.push("/tmp/input.pcap".to_string());
            i += 1;
        } else if let Some(mut val) = arg.strip_prefix("-S") {
            if val.is_empty() {
                val = &args[i + 1];
            }
            let path = std::fs::canonicalize(val).unwrap();
            docker_args.push(format!("--volume={}:/tmp/suricata.rules", path.display()));
            suricata_args.push("-S".to_string());
            suricata_args.push("/tmp/suricata.rules".to_string());
            i += 1;
        } else {
            suricata_args.push(arg.to_string());
        }

        i += 1;
    }

    // If no log directory set, use the current directory.
    if !has_log_directory {
        let path = std::env::current_dir().unwrap();
        docker_args.push(format!("--volume={}:/var/log/suricata", path.display()));
        suricata_args.push("-l".to_string());
        suricata_args.push("/var/log/suricata".to_string());
    }

    if std::io::stdin().is_terminal() {
        docker_args.push("-it".to_string());
    }

    let my_uid = nix::unistd::getuid();
    let my_gid = nix::unistd::getgid();

    docker_args.push("-e".to_string());
    docker_args.push(format!("PUID={}", my_uid));
    docker_args.push("-e".to_string());
    docker_args.push(format!("PGID={}", my_gid));

    let mut cmd = std::process::Command::new("docker");
    cmd.args([
        "run",
        "--rm",
        "--network=host",
        "--cap-add=net_raw",
        "--cap-add=net_admin",
        "--cap-add=sys_nice",
    ]);
    cmd.args(&docker_args);
    cmd.args([image]);
    cmd.args(&suricata_args);
    if cmd.status().is_err() {
        std::process::exit(1);
    }
}