Skip to content
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

Merged
merged 28 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6b9cd30
:new: Add Skeleton for InTotoRun
joyliu-q Jun 29, 2021
d8de0e5
:new: Fill-in skeleton, add recordartifacts & runcommand
joyliu-q Jun 30, 2021
fd99f0a
:broom: Clean code, propagate errors
joyliu-q Jul 1, 2021
a6170a3
:shirt: Remove Unnecessary Return Statement
joyliu-q Jul 1, 2021
6a35d8b
:broom: Return status code, correct doc, cosmetics
joyliu-q Jul 2, 2021
2c01646
:heavy_check_mark: Make tests pass
joyliu-q Jul 2, 2021
27e12d3
:new: Add run_dir parameter, canonicalize path
joyliu-q Jul 2, 2021
cb93298
:zap: Better path handling & test_record_artifacts
joyliu-q Jul 4, 2021
6e0ddf5
:new: Add example usage for in_toto_run
joyliu-q Jul 4, 2021
fdfab27
:tada: Handle symbolic links & record_artifacts breakdown
joyliu-q Jul 6, 2021
eff788c
:new: Add hash_algorithms parameter
joyliu-q Jul 6, 2021
640b370
:heavy_check_mark: Add hash_algorithms to example
joyliu-q Jul 8, 2021
46d9a42
:tada: Add path normalization using path_clean
joyliu-q Jul 8, 2021
890372c
:new: Add Link Generation, Rename Metablock
joyliu-q Jul 11, 2021
228bc35
Merge branch 'master' of https://github.com/in-toto/in-toto-rs into a…
joyliu-q Jul 11, 2021
8e6fb7d
:books: Add to Documentation
joyliu-q Jul 12, 2021
99762ee
:bug: Add path-clean as dependency
joyliu-q Jul 19, 2021
2e83e00
:bug: Make Keys a Reference
joyliu-q Jul 19, 2021
402083d
:new: Add runlib tests & placeholders
joyliu-q Jul 26, 2021
714b8c1
:new: Clean-up & Add tests
joyliu-q Jul 30, 2021
2805e39
:bug: More contextual err handling in run_command
joyliu-q Aug 2, 2021
7eae8c9
:art: Add run_command check for empty slice passed
joyliu-q Aug 10, 2021
02d44c1
:new: Add back typ field to preserve type & name
joyliu-q Aug 11, 2021
6639884
:heavy_check_mark: Pass tests for absolute paths
joyliu-q Aug 11, 2021
59e2b14
:heavy_check_mark: Remove symdir
joyliu-q Aug 12, 2021
f8531f1
:bug: Address comments & make test pass on Github
joyliu-q Aug 12, 2021
26023c9
:broom: Delete symbolic link to file
joyliu-q Aug 12, 2021
f5e6479
:broom: Delete symbolic link to folder
joyliu-q Aug 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tempfile = "3"
untrusted = "0.7"
url = "2"
thiserror = "1.0"
walkdir = "2"

[dev-dependencies]
lazy_static = "1"
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
clippy::too_many_arguments
)]

pub mod runlib;
pub mod crypto;
pub mod error;
pub mod interchange;
pub mod models;
pub mod runlib;
pub mod verifylib;

mod format_hex;
Expand Down
195 changes: 195 additions & 0 deletions src/runlib.rs
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() {
Copy link
Contributor Author

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 "./")

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],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the path arguments &[&str] or Vec<String> (or maybe Vec<VirtualTargetPath>?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with name and other params (&str vs String)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 VirtualTargetPath for function parameters (similar to other in-toto implementations) and handle those conversions internally.

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
);
}
}