-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
226 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
use std::fs::File; | ||
use std::io::{self, Read, Write}; | ||
use std::path::Path; | ||
|
||
use anyhow::{Context, Result}; | ||
use chrono::Utc; | ||
use clap::{crate_name, crate_version}; | ||
use indoc::{formatdoc, indoc}; | ||
use locator::Locator; | ||
use non_empty_string::NonEmptyString; | ||
use serde::de::Error; | ||
use serde::{Deserialize, Serialize}; | ||
use tap::TapFallible; | ||
|
||
use crate::migration::MigrationVulnComponentEntry; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct VulnComponentBatch { | ||
pub entries: Vec<VulnComponentEntry>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct VulnComponentEntry { | ||
pub cve: NonEmptyString, | ||
pub dependency_revision_id: Locator, | ||
pub function: SymbolTarget, | ||
pub researcher: NonEmptyString, | ||
pub evidence_notes: Option<String>, | ||
pub file_path: Option<String>, | ||
pub line_start: Option<u32>, | ||
} | ||
|
||
impl TryFrom<VulnComponentEntry> for MigrationVulnComponentEntry { | ||
type Error = serde_json::Error; | ||
fn try_from(v: VulnComponentEntry) -> Result<Self, Self::Error> { | ||
Ok(Self { | ||
cve: v.cve, | ||
dependency_revision_id: v.dependency_revision_id, | ||
function: serde_json::to_string(&v.function)?, | ||
researcher: v.researcher, | ||
evidence_notes: v.evidence_notes, | ||
file_path: v.file_path, | ||
line_start: v.line_start, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, Serialize, PartialEq)] | ||
pub struct JavaSymbols(Vec<SymbolJava>); | ||
|
||
// TODO: This should be using same type as reachability lib | ||
#[derive(Debug, Serialize, Deserialize, PartialEq)] | ||
#[serde(tag = "kind", rename_all = "snake_case")] | ||
pub enum SymbolTarget { | ||
Java { symbol: JavaSymbols }, | ||
} | ||
|
||
// TODO: This should be using same type as reachability lib | ||
#[derive(Debug, Serialize, PartialEq)] | ||
#[serde(rename_all = "snake_case", tag = "kind")] | ||
pub enum SymbolJava { | ||
Package { label: String }, | ||
Class { label: String }, | ||
ClassMethod { label: String }, | ||
Constructor { label: String }, | ||
} | ||
|
||
impl<'de> Deserialize<'de> for JavaSymbols { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: serde::Deserializer<'de>, | ||
{ | ||
let s = String::deserialize(deserializer)?; | ||
if !s.is_empty() { | ||
let symbols: Vec<SymbolJava> = s.split("::").filter_map(parse_symbol_java).collect(); | ||
if !symbols.is_empty() { | ||
return Ok(JavaSymbols(symbols)); | ||
} | ||
} | ||
|
||
Err(Error::invalid_value( | ||
serde::de::Unexpected::Str(s.as_str()), | ||
&"fully qualified symbol path: Class(SomeClassName)::ClassMethod(SomeClassMethod)", | ||
)) | ||
} | ||
} | ||
|
||
impl VulnComponentBatch { | ||
pub fn make_example(out_file: &Path) -> Result<()> { | ||
let content = indoc! {" | ||
entries: | ||
- cve: 'CVE-2022-37865' # ID of the CVE | ||
dependency_revision_id: 'mvn+org.apache.ivy:ivy' # FOSSA locator: <ecosystem>+<package>$<version> | ||
function: # Vulnerable Function | ||
kind: java # Kind of function. Possible choices: 'java'. | ||
symbol: 'Class(ZipPacking)::ClassMethod(unpack)' # Qualified Function Path. Possible choices: Class(), ClassMethod(), Constructor(). `::` denotes sub scope. | ||
researcher: 'Name of the person' # Name of the researcher | ||
notes: 'some notes' # Any notes (Optional) | ||
"}; | ||
|
||
File::create(out_file) | ||
.context("creating file")? | ||
.write_all(content.as_bytes()) | ||
.context("writing file") | ||
.tap_ok(|_| println!("Wrote file at: {:?}", out_file)) | ||
} | ||
|
||
pub fn make_migration(batch_file: &Path) -> Result<()> { | ||
// 1. Read file | ||
let file = | ||
File::open(batch_file).with_context(|| format!("open file `{:?}`", batch_file))?; | ||
let mut content = String::new(); | ||
let mut reader = io::BufReader::new(file); | ||
|
||
reader | ||
.read_to_string(&mut content) | ||
.with_context(|| format!("read content of: `{:?}`", batch_file))?; | ||
|
||
let batch: VulnComponentBatch = | ||
serde_yaml::from_str(&content).with_context(|| format!("parse: `{:?}`", batch_file))?; | ||
let entries = batch | ||
.entries | ||
.into_iter() | ||
.map(MigrationVulnComponentEntry::try_from) | ||
.collect::<Result<Vec<_>, _>>() | ||
.context("convert to output format")?; | ||
|
||
// 2. Create migration file's content | ||
let entries: String = | ||
serde_json::to_string_pretty(&entries).context("serialize entries")?; | ||
let content = formatdoc! {" | ||
// Automatically generated by: {app_name} {app_version} | ||
/* eslint-disable quotes */ | ||
/* eslint-disable quote-props */ | ||
/* eslint-disable comma-dangle */ | ||
const entries = {entries}; | ||
module.exports = {{ | ||
up: async (queryInterface, _sequelize) => {{ | ||
queryInterface.bulkInsert('ReachabilityPrivateVulnComponents', entries, {{ | ||
updateOnDuplicate: ['evidence_notes', 'file_path', 'line_start', 'researcher', 'updated_at'], | ||
upsertKeys: ['cve', 'dependency_revision_id', 'function'], | ||
}}); | ||
}}, | ||
down: async (_queryInterface, _) => {{}}, | ||
}}; | ||
", | ||
app_name = crate_name!(), | ||
app_version = crate_version!(), | ||
}; | ||
|
||
// 3. Write migration file! | ||
let out_file = &format!( | ||
"{}-vulnComponent-automated.js", | ||
Utc::now().format("%Y%m%d%H%M%S") | ||
); | ||
File::create(out_file) | ||
.context("creating file")? | ||
.write_all(content.as_bytes()) | ||
.context("writing file") | ||
.tap_ok(|_| println!("Wrote file at: {:?}", out_file)) | ||
} | ||
} | ||
|
||
fn parse_symbol_java(input: &str) -> Option<SymbolJava> { | ||
let mut parts = input.split("::"); | ||
let kind_label_pair = parts.next()?; | ||
let Some((kind, label)) = kind_label_pair.split_once('(') else { | ||
return None; | ||
}; | ||
|
||
let kind = kind.trim(); | ||
let label = label.trim_end_matches(')'); | ||
|
||
match kind { | ||
"Package" => Some(SymbolJava::Package { | ||
label: label.to_string(), | ||
}), | ||
"Class" => Some(SymbolJava::Class { | ||
label: label.to_string(), | ||
}), | ||
"ClassMethod" => Some(SymbolJava::ClassMethod { | ||
label: label.to_string(), | ||
}), | ||
"Constructor" => Some(SymbolJava::Constructor { | ||
label: label.to_string(), | ||
}), | ||
_ => None, | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use test_case::test_case; | ||
|
||
#[ | ||
test_case("Class(Logger)::ClassMethod(log)", | ||
vec![ | ||
SymbolJava::Class {label: "Logger".to_string() }, | ||
SymbolJava::ClassMethod { label: "log".to_string() } | ||
] | ||
)] | ||
#[ | ||
test_case("Class(Logger)", | ||
vec![ | ||
SymbolJava::Class {label: "Logger".to_string() }, | ||
] | ||
)] | ||
fn parse_java_symbols_works(arg: &str, expected: Vec<SymbolJava>) { | ||
let json_string = format!(r#"{{ "kind": "java", "symbol": "{arg}" }}"#); | ||
let symbol: SymbolTarget = | ||
serde_json::from_str(&json_string).expect("to serialize into json string"); | ||
assert_eq!( | ||
symbol, | ||
SymbolTarget::Java { | ||
symbol: JavaSymbols(expected) | ||
} | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod ingestion; | ||
pub mod migration; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.