Skip to content

Commit

Permalink
feat: implement PolicyFile --> AnalysisTree conversion, remove use of…
Browse files Browse the repository at this point in the history
… WeightTree in scoring
  • Loading branch information
j-lanson authored and mchernicoff committed Sep 5, 2024
1 parent cf60542 commit 6a9b3e6
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 66 deletions.
36 changes: 18 additions & 18 deletions hipcheck/src/analysis/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ use crate::analysis::result::*;
use crate::analysis::AnalysisOutcome;
use crate::analysis::AnalysisProvider;
use crate::config::{
visit_leaves, Analysis, AnalysisTree, WeightTree, WeightTreeProvider, LEGACY_PLUGIN,
MITRE_PUBLISHER,
visit_leaves, Analysis, AnalysisTree, WeightTreeProvider, LEGACY_PLUGIN, MITRE_PUBLISHER,
};
use crate::engine::HcEngine;
use crate::error::Result;
use crate::hc_error;
use crate::policy::PolicyFile;
use crate::report::Concern;
use crate::shell::spinner_phase::SpinnerPhase;
use crate::F64;
use num_traits::identities::Zero;
use serde_json::Value;
use std::cmp::Ordering;
Expand Down Expand Up @@ -209,7 +210,7 @@ impl ScoreTree {
.get(analysis_root)
.ok_or(hc_error!("AnalysisTree root not in tree, invalid state"))?
.get()
.augment(&scores.table),
.augment_plugin(&scores.table),
);

let mut scope: Vec<NodeId> = vec![score_root];
Expand All @@ -220,9 +221,9 @@ impl ScoreTree {
analysis_tree
.tree
.get(n)
.ok_or(hc_error!("WeightTree root not in tree, invalid state"))?
.ok_or(hc_error!("AnalaysisTree node not in tree, invalid state"))?
.get()
.augment(&scores.table),
.augment_plugin(&scores.table),
);
scope
.last()
Expand All @@ -242,33 +243,33 @@ impl ScoreTree {
})
}

// Given a weight tree and set of analysis results, produce an AltScoreTree by creating
// Given an analysis tree and set of analysis results, produce an AltScoreTree by creating
// ScoreTreeNode objects for each analysis that was not skipped, and composing them into
// a tree structure matching that of the WeightTree
pub fn synthesize(weight_tree: &WeightTree, scores: &AnalysisResults) -> Result<Self> {
pub fn synthesize(analysis_tree: &AnalysisTree, scores: &AnalysisResults) -> Result<Self> {
use indextree::NodeEdge::*;
let mut tree = Arena::<ScoreTreeNode>::new();
let weight_root = weight_tree.root;
let analysis_root = analysis_tree.root;
let score_root = tree.new_node(
weight_tree
analysis_tree
.tree
.get(weight_root)
.ok_or(hc_error!("WeightTree root not in tree, invalid state"))?
.get(analysis_root)
.ok_or(hc_error!("AnalysisTree root not in tree, invalid state"))?
.get()
.augment(scores),
.augment(scores)?,
);

let mut scope: Vec<NodeId> = vec![score_root];
for edge in weight_root.traverse(&weight_tree.tree) {
for edge in analysis_root.traverse(&analysis_tree.tree) {
match edge {
Start(n) => {
let curr_node = tree.new_node(
weight_tree
analysis_tree
.tree
.get(n)
.ok_or(hc_error!("WeightTree root not in tree, invalid state"))?
.ok_or(hc_error!("AnalysisTree node not in tree, invalid state"))?
.get()
.augment(scores),
.augment(scores)?,
);
scope
.last()
Expand Down Expand Up @@ -432,7 +433,6 @@ pub fn score_results(phase: &SpinnerPhase, db: &dyn ScoringProvider) -> Result<S
let mut plug_results = PluginAnalysisResults::default();

// @FollowUp - remove this once we implement policy expr calculation
let weight_tree = db.normalized_weight_tree()?;
let mut results = AnalysisResults::default();

let mut score = Score::default();
Expand Down Expand Up @@ -601,7 +601,7 @@ pub fn score_results(phase: &SpinnerPhase, db: &dyn ScoringProvider) -> Result<S
}
}

let alt_score_tree = ScoreTree::synthesize(&weight_tree, &results)?;
let alt_score_tree = ScoreTree::synthesize(&analysis_tree, &results)?;
// let plug_score_tree = ScoreTree::synthesize_plugin(&analysis_tree, &plug_results)?;
score.total = alt_score_tree.score();

Expand Down
112 changes: 100 additions & 12 deletions hipcheck/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
use crate::analysis::score::*;
use crate::context::Context;
use crate::engine::HcEngine;
use crate::error::Result;
use crate::hc_error;
use crate::policy_exprs::{Expr, Primitive};
use crate::policy::policy_file::{PolicyAnalysis, PolicyCategory, PolicyCategoryChild};
use crate::policy::PolicyFile;
use crate::util::fs as file;
use crate::BINARY_CONFIG_FILE;
use crate::F64;
Expand Down Expand Up @@ -448,6 +450,8 @@ pub trait ConfigSource: salsa::Database {
/// Returns the directory containing the config file
#[salsa::input]
fn config_dir(&self) -> Rc<PathBuf>;
#[salsa::input]
fn policy(&self) -> Option<Rc<PolicyFile>>;
/// Returns the token set in HC_GITHUB_TOKEN env var
#[salsa::input]
fn github_api_token(&self) -> Option<Rc<String>>;
Expand Down Expand Up @@ -605,7 +609,7 @@ pub struct Analysis {
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PoliciedAnalysis(pub Analysis, pub Expr);
pub struct PoliciedAnalysis(pub Analysis, pub String);

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnalysisTreeNode {
Expand Down Expand Up @@ -641,13 +645,35 @@ impl AnalysisTreeNode {
weight,
}
}
pub fn analysis(analysis: Analysis, expr: Expr, weight: F64) -> Self {
pub fn analysis(analysis: Analysis, raw_policy: String, weight: F64) -> Self {
AnalysisTreeNode::Analysis {
analysis: PoliciedAnalysis(analysis, expr),
analysis: PoliciedAnalysis(analysis, raw_policy),
weight,
}
}
pub fn augment(&self, metrics: &HashMap<Analysis, Result<Value>>) -> ScoreTreeNode {
pub fn augment(&self, scores: &AnalysisResults) -> Result<ScoreTreeNode> {
match self {
AnalysisTreeNode::Category { label, weight } => Ok(ScoreTreeNode {
label: label.clone(),
score: 0f64,
weight: (*weight).into(),
}),
AnalysisTreeNode::Analysis { analysis, weight } => {
let label = analysis.0.query.clone();
let stored_res = scores.table.get(&label).ok_or(hc_error!(
"missing expected analysis results {}",
analysis.0.query
))?;
let score = stored_res.score().0;
Ok(ScoreTreeNode {
label,
score: score as f64,
weight: (*weight).into(),
})
}
}
}
pub fn augment_plugin(&self, metrics: &HashMap<Analysis, Result<Value>>) -> ScoreTreeNode {
match self {
AnalysisTreeNode::Category { label, weight } => ScoreTreeNode {
label: label.clone(),
Expand Down Expand Up @@ -713,13 +739,13 @@ impl AnalysisTree {
&mut self,
under: NodeId,
analysis: Analysis,
policy: Expr,
raw_policy: String,
weight: F64,
) -> Result<NodeId> {
if self.node_is_category(under)? {
let child = self
.tree
.new_node(AnalysisTreeNode::analysis(analysis, policy, weight));
.new_node(AnalysisTreeNode::analysis(analysis, raw_policy, weight));
under.append(child, &mut self.tree);
Ok(child)
} else {
Expand All @@ -738,7 +764,7 @@ impl AnalysisTree {
.get(weight_root)
.ok_or(hc_error!("WeightTree root not in tree, invalid state"))?
.get()
.with_hardcoded_expr(),
.as_category_node(),
);

let mut scope: Vec<NodeId> = vec![analysis_root];
Expand Down Expand Up @@ -786,6 +812,7 @@ impl WeightTreeNode {
weight,
}
}
#[allow(unused)]
pub fn augment(&self, scores: &AnalysisResults) -> ScoreTreeNode {
let score = match scores.table.get(&self.label) {
Some(res) => res.score().0,
Expand All @@ -806,7 +833,7 @@ impl WeightTreeNode {
// @Temporary - until policy file impl'd and integrated, we hard-code
// the policy for our analyses
pub fn with_hardcoded_expr(&self) -> AnalysisTreeNode {
let expr = Expr::Primitive(Primitive::Bool(false));
let expr = "true".to_owned();
let analysis = Analysis {
publisher: MITRE_PUBLISHER.to_owned(),
plugin: LEGACY_PLUGIN.to_owned(),
Expand Down Expand Up @@ -882,7 +909,7 @@ where

#[salsa::query_group(WeightTreeQueryStorage)]
pub trait WeightTreeProvider:
FuzzConfigQuery + PracticesConfigQuery + AttacksConfigQuery + CommitConfigQuery
FuzzConfigQuery + PracticesConfigQuery + AttacksConfigQuery + CommitConfigQuery + HcEngine
{
/// Returns the tree of raw analysis weights from the config
fn weight_tree(&self) -> Result<Rc<WeightTree>>;
Expand All @@ -895,9 +922,70 @@ pub trait WeightTreeProvider:
fn normalized_analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
}

fn add_analysis(
core: &dyn WeightTreeProvider,
tree: &mut AnalysisTree,
under: NodeId,
analysis: PolicyAnalysis,
) -> Result<NodeId> {
let publisher = analysis.name.publisher;
let plugin = analysis.name.name;
let weight = match analysis.weight {
Some(u) => F64::new(u as f64)?,
None => F64::new(1.0)?,
};
let raw_policy = match analysis.policy_expression {
Some(x) => x,
None => core.default_policy_expr(publisher.clone(), plugin.clone())?.ok_or(hc_error!("plugin {}::{} does not have a default policy, please define a policy in your policy file"))?
};
let analysis = Analysis {
publisher,
plugin,
query: "default".to_owned(),
};
tree.add_analysis(under, analysis, raw_policy, weight)
}

fn add_category(
core: &dyn WeightTreeProvider,
tree: &mut AnalysisTree,
under: NodeId,
category: &PolicyCategory,
) -> Result<NodeId> {
let weight = F64::new(match category.weight {
Some(w) => w as f64,
None => 1.0,
})
.unwrap();
let id = tree.add_category(under, category.name.as_str(), weight)?;
for c in category.children.iter() {
match c {
PolicyCategoryChild::Analysis(analysis) => {
add_analysis(core, tree, id, analysis.clone())?;
}
PolicyCategoryChild::Category(category) => {
add_category(core, tree, id, category)?;
}
}
}
Ok(id)
}

pub fn analysis_tree(db: &dyn WeightTreeProvider) -> Result<Rc<AnalysisTree>> {
let weight_tree = db.weight_tree()?;
AnalysisTree::from_weight_tree(&weight_tree).map(Rc::new)
// @Todo - once ConfigFile-->PolicyFile implemented, deprecate else block
if let Some(policy) = db.policy() {
let mut tree = AnalysisTree::new("risk");
let root = tree.root;

for c in policy.analyze.categories.iter() {
add_category(db, &mut tree, root, c)?;
}

Ok(Rc::new(tree))
} else {
let weight_tree = db.weight_tree()?;
AnalysisTree::from_weight_tree(&weight_tree).map(Rc::new)
}
}

pub fn weight_tree(db: &dyn WeightTreeProvider) -> Result<Rc<WeightTree>> {
Expand Down
5 changes: 2 additions & 3 deletions hipcheck/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::analysis::{
},
AnalysisProvider,
};
use crate::config::{visit_leaves, WeightTree, WeightTreeProvider};
use crate::metric::{review::PullReview, MetricProvider};
use crate::plugin::{ActivePlugin, PluginResponse};
pub use crate::plugin::{HcPluginCore, PluginExecutor, PluginWithConfig};
Expand All @@ -29,7 +28,7 @@ pub trait HcEngine: salsa::Database {
#[salsa::input]
fn core(&self) -> Arc<HcPluginCore>;

fn default_policy_expr(&self, publisher: String, plugin: String) -> Result<Option<Expr>>;
fn default_policy_expr(&self, publisher: String, plugin: String) -> Result<Option<String>>;

fn query(&self, publisher: String, plugin: String, query: String, key: Value) -> Result<Value>;
}
Expand All @@ -38,7 +37,7 @@ fn default_policy_expr(
db: &dyn HcEngine,
publisher: String,
plugin: String,
) -> Result<Option<Expr>> {
) -> Result<Option<String>> {
let core = db.core();
// @Todo - plugins map should be keyed on publisher too
let Some(p_handle) = core.plugins.get(&plugin) else {
Expand Down
3 changes: 1 addition & 2 deletions hipcheck/src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use crate::context::Context;
use crate::kdl_helper::{extract_data, ParseKdlNode};
pub use crate::plugin::manager::*;
pub use crate::plugin::types::*;
use crate::policy_exprs::Expr;
use crate::Result;
pub use download_manifest::{
ArchiveFormat, DownloadManifest, DownloadManifestEntry, HashAlgorithm, HashWithDigest,
Expand Down Expand Up @@ -64,7 +63,7 @@ impl ActivePlugin {
channel,
}
}
pub fn get_default_policy_expr(&self) -> Option<&Expr> {
pub fn get_default_policy_expr(&self) -> Option<&String> {
self.channel.opt_default_policy_expr.as_ref()
}
async fn get_unique_id(&self) -> usize {
Expand Down
17 changes: 7 additions & 10 deletions hipcheck/src/plugin/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::policy_exprs::{parse, Expr};
use crate::{
hc_error,
hipcheck::{
Expand Down Expand Up @@ -184,19 +183,17 @@ impl PluginContext {
res.into_inner().try_into()
}

pub async fn get_default_policy_expression(&mut self) -> Result<Option<Expr>> {
pub async fn get_default_policy_expression(&mut self) -> Result<Option<String>> {
let req = GetDefaultPolicyExpressionRequest {
empty: Some(Empty {}),
};
let mut res = self.grpc.get_default_policy_expression(req).await?;
let expr_str = res.get_ref().policy_expression.as_str();
if expr_str.is_empty() {
Ok(None)
let raw_expr = res.get_ref().policy_expression.clone();
Ok(if raw_expr.is_empty() {
None
} else {
parse(expr_str)
.map_err(|e| hc_error!("{}", e.to_string()))
.map(Some)
}
Some(raw_expr)
})
}

pub async fn initiate_query_protocol(
Expand Down Expand Up @@ -410,7 +407,7 @@ impl MultiplexedQueryReceiver {
#[derive(Debug)]
pub struct PluginTransport {
pub schemas: HashMap<String, Schema>,
pub opt_default_policy_expr: Option<Expr>,
pub opt_default_policy_expr: Option<String>,
ctx: PluginContext,
tx: mpsc::Sender<PluginQuery>,
rx: Mutex<MultiplexedQueryReceiver>,
Expand Down
2 changes: 1 addition & 1 deletion hipcheck/src/policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

//! Data types and functions for parsing policy KDL files
mod policy_file;
pub mod policy_file;
mod tests;

use crate::kdl_helper::extract_data;
Expand Down
Loading

0 comments on commit 6a9b3e6

Please sign in to comment.