use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tracing::info;
use sentinel_agent_sentinelsec::{SentinelSecAgent, SentinelSecConfig};
use sentinel_agent_protocol::AgentServer;
#[derive(Parser, Debug)]
#[command(name = "sentinel-sentinelsec-agent")]
#[command(about = "Pure Rust ModSecurity-compatible WAF agent for Sentinel - full OWASP CRS support without C dependencies")]
struct Args {
#[arg(
long,
default_value = "/tmp/sentinel-sentinelsec.sock",
env = "AGENT_SOCKET"
)]
socket: PathBuf,
#[arg(long = "rules", env = "SENTINELSEC_RULES", value_delimiter = ',')]
rules_paths: Vec<String>,
#[arg(long, default_value = "true", env = "SENTINELSEC_BLOCK_MODE")]
block_mode: bool,
#[arg(long, env = "SENTINELSEC_EXCLUDE_PATHS")]
exclude_paths: Option<String>,
#[arg(long, default_value = "true", env = "SENTINELSEC_BODY_INSPECTION")]
body_inspection: bool,
#[arg(long, default_value = "1048576", env = "SENTINELSEC_MAX_BODY_SIZE")]
max_body_size: usize,
#[arg(long, default_value = "false", env = "SENTINELSEC_RESPONSE_INSPECTION")]
response_inspection: bool,
#[arg(short, long, env = "SENTINELSEC_VERBOSE")]
verbose: bool,
}
impl Args {
fn to_config(&self) -> SentinelSecConfig {
let exclude_paths = self
.exclude_paths
.as_ref()
.map(|p| p.split(',').map(|s| s.trim().to_string()).collect())
.unwrap_or_default();
SentinelSecConfig {
rules_paths: self.rules_paths.clone(),
block_mode: self.block_mode,
exclude_paths,
body_inspection_enabled: self.body_inspection,
max_body_size: self.max_body_size,
response_inspection_enabled: self.response_inspection,
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let log_level = if args.verbose { "debug" } else { "info" };
tracing_subscriber::fmt()
.with_env_filter(format!(
"{}={},sentinel_agent_protocol=info",
env!("CARGO_CRATE_NAME"),
log_level
))
.json()
.init();
info!("Starting Sentinel SentinelSec Agent (pure Rust ModSecurity)");
let config = args.to_config();
info!(
rules_count = config.rules_paths.len(),
block_mode = config.block_mode,
body_inspection = config.body_inspection_enabled,
response_inspection = config.response_inspection_enabled,
max_body_size = config.max_body_size,
"Configuration loaded"
);
if config.rules_paths.is_empty() {
tracing::warn!("No rules paths configured - SentinelSec will not block any requests");
tracing::warn!(
"Use --rules to specify rule files, e.g.: --rules /etc/modsecurity/crs/rules/*.conf"
);
}
let agent = SentinelSecAgent::new(config)?;
info!(socket = ?args.socket, "Starting agent server");
let server = AgentServer::new("sentinel-sentinelsec-agent", args.socket, Box::new(agent));
server.run().await.map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(())
}