Skip to content

Commit

Permalink
Merge pull request #94 from nikstur/improve-reproducibility
Browse files Browse the repository at this point in the history
Make it reproducible
  • Loading branch information
nikstur committed May 8, 2024
2 parents c7555bb + 5180605 commit a8083c9
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 12 deletions.
3 changes: 2 additions & 1 deletion nix/build-bom.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ runCommand "${drv.name}.cdx.json" { buildInputs = [ transformer ]; } ''
bombon-transformer ${drv} \
${toString flags} \
${buildtimeDependencies drv extraPaths} \
${runtimeDependencies drv extraPaths} > $out
${runtimeDependencies drv extraPaths} \
$out
''

73 changes: 73 additions & 0 deletions rust/transformer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rust/transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ cyclonedx-bom = "0.5"
itertools = "0.12"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
sha2 = "0.10"
uuid = "1.8"

[lints.rust]
unsafe_code = "forbid"
Expand Down
6 changes: 3 additions & 3 deletions rust/transformer/src/buildtime_input.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

Expand All @@ -7,15 +7,15 @@ use anyhow::{Context, Result};
use crate::derivation::Derivation;

#[derive(Clone)]
pub struct BuildtimeInput(pub HashMap<String, Derivation>);
pub struct BuildtimeInput(pub BTreeMap<String, Derivation>);

impl BuildtimeInput {
pub fn from_file(path: &Path) -> Result<Self> {
let buildtime_input_json: Vec<Derivation> = serde_json::from_reader(
fs::File::open(path).with_context(|| format!("Failed to open {path:?}"))?,
)
.context("Failed to parse buildtime input")?;
let mut m = HashMap::new();
let mut m = BTreeMap::new();
for derivation in buildtime_input_json {
m.insert(derivation.path.clone(), derivation);
}
Expand Down
4 changes: 4 additions & 0 deletions rust/transformer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub struct Cli {

/// Path to a newline separated .txt file containing the runtime input
runtime_input: PathBuf,

/// Path to write the SBOM to
output: PathBuf,
}

impl Cli {
Expand All @@ -28,6 +31,7 @@ impl Cli {
&self.target,
&self.buildtime_input,
&self.runtime_input,
&self.output,
)
}
}
25 changes: 23 additions & 2 deletions rust/transformer/src/cyclonedx.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::path::Path;

use anyhow::Result;
use cyclonedx_bom::external_models::uri::Purl;
use cyclonedx_bom::models::bom::Bom;
use cyclonedx_bom::models::bom::{Bom, UrnUuid};
use cyclonedx_bom::models::component::{Classification, Component, Components};
use cyclonedx_bom::models::external_reference::{
ExternalReference, ExternalReferenceType, ExternalReferences,
};
use cyclonedx_bom::models::license::{License, LicenseChoice, Licenses};
use cyclonedx_bom::models::metadata::Metadata;
use cyclonedx_bom::models::tool::{Tool, Tools};
use sha2::{Digest, Sha256};

use crate::derivation::{self, Derivation, Meta};

Expand All @@ -22,15 +25,33 @@ impl CycloneDXBom {
Ok(output)
}

pub fn build(target: Derivation, components: CycloneDXComponents) -> Self {
pub fn build(target: Derivation, components: CycloneDXComponents, output: &Path) -> Self {
Self(Bom {
components: Some(components.into()),
metadata: Some(metadata_from_derivation(target)),
// Derive a reproducible serial number from the output path. This works because the Nix
// outPath of the derivation is input addressed and thus reproducible.
serial_number: Some(derive_serial_number(output.as_os_str().as_encoded_bytes())),
..Bom::default()
})
}
}

/// Derive a serial number from some arbitrary data.
///
/// This data is hashed with SHA256 and the first 16 bytes are used to create a UUID to serve as a
/// serial number.
fn derive_serial_number(data: &[u8]) -> UrnUuid {
let hash = Sha256::digest(data);
let array: [u8; 32] = hash.into();
#[allow(clippy::expect_used)]
let bytes = array[..16]
.try_into()
.expect("Failed to extract 16 bytes from SHA256 hash");
let uuid = uuid::Builder::from_bytes(bytes).into_uuid();
UrnUuid::from(uuid)
}

pub struct CycloneDXComponents(Components);

impl CycloneDXComponents {
Expand Down
6 changes: 3 additions & 3 deletions rust/transformer/src/runtime_input.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use std::borrow::ToOwned;
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;

use anyhow::{Context, Result};

pub struct RuntimeInput(pub HashSet<String>);
pub struct RuntimeInput(pub BTreeSet<String>);

impl RuntimeInput {
pub fn from_file(path: &Path) -> Result<Self> {
let file_content =
fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
let set: HashSet<String> = file_content.lines().map(ToOwned::to_owned).collect();
let set: BTreeSet<String> = file_content.lines().map(ToOwned::to_owned).collect();
Ok(Self(set))
}
}
10 changes: 7 additions & 3 deletions rust/transformer/src/transform.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io::{self, Write};
use std::fs::File;
use std::io::Write;
use std::path::Path;

use anyhow::{Context, Result};
Expand All @@ -14,6 +15,7 @@ pub fn transform(
target_path: &str,
buildtime_input_path: &Path,
runtime_input_path: &Path,
output: &Path,
) -> Result<()> {
let mut buildtime_input = BuildtimeInput::from_file(buildtime_input_path)?;
let target_derivation = buildtime_input.0.remove(target_path).with_context(|| {
Expand Down Expand Up @@ -48,8 +50,10 @@ pub fn transform(
CycloneDXComponents::new(runtime_derivations)
};

let bom = CycloneDXBom::build(target_derivation, components);
io::stdout().write_all(&bom.serialize()?)?;
let bom = CycloneDXBom::build(target_derivation, components, output);
let mut file =
File::create(output).with_context(|| format!("Failed to create file {output:?}"))?;
file.write_all(&bom.serialize()?)?;

Ok(())
}

0 comments on commit a8083c9

Please sign in to comment.