-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement in_toto_run #7
Changes from 7 commits
6b9cd30
d8de0e5
fd99f0a
a6170a3
6a35d8b
2c01646
27e12d3
cb93298
6e0ddf5
fdfab27
eff788c
640b370
46d9a42
890372c
228bc35
8e6fb7d
99762ee
2e83e00
402083d
714b8c1
2805e39
7eae8c9
02d44c1
6639884
59e2b14
f8531f1
26023c9
f5e6479
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,196 @@ | ||
//! A tool that functionaries can use to create link metadata about a step. | ||
|
||
use std::collections::BTreeMap; | ||
use std::fs::{canonicalize as canonicalize_path, metadata, File}; | ||
use std::io::{self, BufReader, Write}; | ||
use std::process::Command; | ||
use walkdir::WalkDir; | ||
|
||
use crate::models::{Link, TargetDescription}; | ||
use crate::{ | ||
crypto, | ||
crypto::PrivateKey, | ||
models::{LinkMetadataBuilder, VirtualTargetPath}, | ||
}; | ||
use crate::{Error, Result}; | ||
|
||
/// record_artifacts is a function that traverses through the passed slice of paths, hashes the content of files | ||
/// encountered, and returns the path and hashed content in BTreeMap format, wrapped in Result. | ||
/// If a step in record_artifact fails, the error is returned. | ||
pub fn record_artifacts( | ||
paths: &[&str], | ||
// hash_algorithms: Option<&[&str]>, | ||
) -> Result<BTreeMap<VirtualTargetPath, TargetDescription>> { | ||
// Initialize artifacts | ||
let mut artifacts: BTreeMap<VirtualTargetPath, TargetDescription> = BTreeMap::new(); | ||
|
||
// For each path provided, walk the directory and add all files to artifacts | ||
for path in paths { | ||
for entry in WalkDir::new(path) { | ||
let entry = match entry { | ||
Ok(content) => content, | ||
Err(error) => { | ||
return Err(Error::from(io::Error::new( | ||
std::io::ErrorKind::Other, | ||
format!("Walkdir Error: {}", error), | ||
))) | ||
} | ||
}; | ||
let entry_path = entry.path(); | ||
|
||
// TODO: Handle soft/symbolic links, by default is they are ignored, but we should visit them just once | ||
// TODO: Handle hidden files & directories | ||
|
||
// If entry is a file, open and hash the file | ||
let md = metadata(entry_path)?; | ||
if md.is_file() { | ||
let file = File::open(entry_path)?; | ||
let mut reader = BufReader::new(file); | ||
// TODO: handle optional hash_algorithms input | ||
let (_length, hashes) = | ||
crypto::calculate_hashes(&mut reader, &[crypto::HashAlgorithm::Sha256])?; | ||
let path = entry_path.to_str().unwrap().to_string().replace("./", ""); | ||
artifacts.insert(VirtualTargetPath::new(path)?, hashes); | ||
} | ||
} | ||
} | ||
Ok(artifacts) | ||
} | ||
|
||
/// run_command is a function that, given command arguments, executes commands on a software supply chain step | ||
/// and returns the stdout and stderr as byproducts. | ||
/// The first element of cmd_args is used as executable and the rest as command arguments. | ||
/// If a commands in run_command fails to execute, the error is returned. | ||
pub fn run_command(cmd_args: &[&str], run_dir: Option<&str>) -> Result<BTreeMap<String, String>> { | ||
let executable = cmd_args[0]; | ||
let args = (&cmd_args[1..]) | ||
.iter() | ||
.map(|arg| { | ||
if VirtualTargetPath::new((*arg).into()).is_ok() { | ||
let absolute_path = canonicalize_path(*arg); | ||
match absolute_path { | ||
Ok(path_buf) => match path_buf.to_str() { | ||
Some(p) => p, | ||
None => *arg, | ||
}, | ||
Err(_) => *arg, | ||
}; | ||
} | ||
*arg | ||
}) | ||
.collect::<Vec<&str>>(); | ||
|
||
let mut cmd = Command::new(executable); | ||
let mut cmd = cmd.args(args); | ||
|
||
if let Some(dir) = run_dir { cmd = cmd.current_dir(dir) } | ||
|
||
let output = cmd.output()?; | ||
|
||
// Emit stdout, stderror | ||
io::stdout().write_all(&output.stdout)?; | ||
io::stderr().write_all(&output.stderr)?; | ||
|
||
// Format output into Byproduct | ||
let mut byproducts: BTreeMap<String, String> = BTreeMap::new(); | ||
// Write to byproducts | ||
let stdout = match String::from_utf8(output.stdout) { | ||
Ok(output) => output, | ||
Err(error) => { | ||
return Err(Error::from(io::Error::new( | ||
std::io::ErrorKind::Other, | ||
format!("Utf8Error: {}", error), | ||
))) | ||
} | ||
}; | ||
let stderr = match String::from_utf8(output.stderr) { | ||
Ok(output) => output, | ||
Err(error) => { | ||
return Err(Error::from(io::Error::new( | ||
std::io::ErrorKind::Other, | ||
format!("Utf8Error: {}", error), | ||
))) | ||
} | ||
}; | ||
let status = match output.status.code() { | ||
Some(code) => code.to_string(), | ||
None => "Process terminated by signal".to_string(), | ||
}; | ||
|
||
byproducts.insert("stdout".to_string(), stdout); | ||
byproducts.insert("stderr".to_string(), stderr); | ||
byproducts.insert("return-value".to_string(), status); | ||
|
||
Ok(byproducts) | ||
} | ||
|
||
// TODO: implement default trait for in_toto_run's parameters | ||
|
||
/// in_toto_run is a function that executes commands on a software supply chain step | ||
/// (layout inspection coming soon), then generates and returns its corresponding Link metadata. | ||
pub fn in_toto_run( | ||
name: &str, | ||
// run_dir: Option<&str>, | ||
material_paths: &[&str], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make the path arguments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After a discussion with @adityasaky, we decided to stick with more accessible types like string slices as opposed to custom types such as |
||
product_paths: &[&str], | ||
cmd_args: &[&str], | ||
key: Option<PrivateKey>, | ||
// env: Option<BTreeMap<String, String>> | ||
// hash_algorithms: Option<&[&str]>, | ||
) -> Result<Link> { | ||
// Record Materials: Given the material_paths, recursively traverse and record files in given path(s) | ||
let materials = record_artifacts(material_paths)?; | ||
|
||
// Execute commands provided in cmd_args | ||
let byproducts = run_command(cmd_args, None)?; | ||
|
||
// Record Products: Given the product_paths, recursively traverse and record files in given path(s) | ||
let products = record_artifacts(product_paths)?; | ||
|
||
// Create link based on values collected above | ||
let link_metadata_builder = LinkMetadataBuilder::new() | ||
.name(name.to_string()) | ||
.materials(materials) | ||
.byproducts(byproducts) | ||
.products(products); | ||
let link_metadata = link_metadata_builder.build()?; | ||
|
||
// TODO Sign the link with key param supplied. If no key param supplied, build & return link | ||
/* match key { | ||
Some(k) => { | ||
// TODO: SignedMetadata and Link are different types. Need to consolidate | ||
let signed_link = link_metadata_builder.signed::<Json>(&k).unwrap(); | ||
let json = serde_json::to_value(&signed_link).unwrap(); | ||
}, | ||
None => { | ||
} | ||
} */ | ||
Link::from(&link_metadata) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_record_artifacts() { | ||
assert_eq!(record_artifacts(&["tests"]).is_ok(), true); | ||
assert_eq!(record_artifacts(&["file-does-not-exist"]).is_err(), true); | ||
} | ||
|
||
#[test] | ||
fn test_run_command() { | ||
let byproducts = run_command(&["sh", "-c", "printf hello"], Some("tests")).unwrap(); | ||
let mut expected = BTreeMap::new(); | ||
expected.insert("stdout".to_string(), "hello".to_string()); | ||
expected.insert("stderr".to_string(), "".to_string()); | ||
expected.insert("return-value".to_string(), "0".to_string()); | ||
joyliu-q marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
assert_eq!(byproducts, expected); | ||
|
||
assert_eq!( | ||
run_command(&["command-does-not-exist", "true"], None).is_err(), | ||
true | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized the usage of
VirtualTargetPath
to check that the argument is a path is not necessarily valid. Should we check with 3rd party or manually (e.g. starts with "./")