execpolicy/src/execpolicycheck.rs
95 lines
use std::fs;use std::path::PathBuf;use anyhow::Context;use anyhow::Result;use clap::Parser;use serde::Serialize;use crate::Decision;use crate::MatchOptions;use crate::Policy;use crate::PolicyParser;use crate::RuleMatch;/// Arguments for evaluating a command against one or more execpolicy files.#[derive(Debug, Parser, Clone)]pub struct ExecPolicyCheckCommand { /// Paths to execpolicy rule files to evaluate (repeatable). #[arg(short = 'r', long = "rules", value_name = "PATH", required = true)] pub rules: Vec<PathBuf>, /// Pretty-print the JSON output. #[arg(long)] pub pretty: bool, /// Resolve absolute program paths against basename rules, gated by any /// `host_executable()` definitions in the loaded policy files. #[arg(long)] pub resolve_host_executables: bool, /// Command tokens to check against the policy. #[arg( value_name = "COMMAND", required = true, trailing_var_arg = true, allow_hyphen_values = true )] pub command: Vec<String>,}impl ExecPolicyCheckCommand { /// Load the policies for this command, evaluate the command, and render JSON output. pub fn run(&self) -> Result<()> { let policy = load_policies(&self.rules)?; let matched_rules = policy.matches_for_command_with_options( &self.command, /*heuristics_fallback*/ None, &MatchOptions { resolve_host_executables: self.resolve_host_executables, }, ); let json = format_matches_json(&matched_rules, self.pretty)?; println!("{json}"); Ok(()) }}pub fn format_matches_json(matched_rules: &[RuleMatch], pretty: bool) -> Result<String> { let output = ExecPolicyCheckOutput { matched_rules, decision: matched_rules.iter().map(RuleMatch::decision).max(), }; if pretty { serde_json::to_string_pretty(&output).map_err(Into::into) } else { serde_json::to_string(&output).map_err(Into::into) }}pub fn load_policies(policy_paths: &[PathBuf]) -> Result<Policy> { let mut parser = PolicyParser::new(); for policy_path in policy_paths { let policy_file_contents = fs::read_to_string(policy_path) .with_context(|| format!("failed to read policy at {}", policy_path.display()))?; let policy_identifier = policy_path.to_string_lossy().to_string(); parser .parse(&policy_identifier, &policy_file_contents) .with_context(|| format!("failed to parse policy at {}", policy_path.display()))?; } Ok(parser.build())}#[derive(Serialize)]#[serde(rename_all = "camelCase")]struct ExecPolicyCheckOutput<'a> { #[serde(rename = "matchedRules")] matched_rules: &'a [RuleMatch], #[serde(skip_serializing_if = "Option::is_none")] decision: Option<Decision>,}