Skip to content

Commit

Permalink
Merge pull request #97 from nikstur/vendored-dependencies
Browse files Browse the repository at this point in the history
Read vendored dependencies from passthru
  • Loading branch information
nikstur authored May 13, 2024
2 parents 722ac5a + 04dab93 commit 45fa2aa
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 16 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ in
bombon.buildBom pkgs.hello { }
```

## Vendored Dependencies

Some language ecosystems in Nixpkgs (most notably Rust and Go) vendor
dependencies. This means that not every dependency is its own derivation and
thus bombon cannot record their information as it does with "normal" Nix
dependencies. However, bombon can automatically read SBOMs generated by other
tools (like `cargo-cyclonedx`) for the vendored dependencies from a passthru
derivation called `bombonVendoredSbom`.

You can use the `passthruVendoredSbom.rust` function to add the
`bombonVendoredSbom` passthru derivation to a Rust package:

```nix
myPackageWithSbom = pkgs.callPackage (bombon.passthruVendoredSbom.rust myPackage) { };
```

An SBOM built from this new derivation will now include the vendored dependencies.

## Options

`buildBom` accepts options as an attribute set. All attributes are optional:
Expand Down Expand Up @@ -97,7 +115,7 @@ are interested in.
:b lib.x86_64-linux.buildBom python3 { }
```

Remember to re load the bombon flake every time you made changes to any of the
Remember to re-load the bombon flake every time you made changes to any of the
source code.

## Acknowledgements
Expand Down
22 changes: 18 additions & 4 deletions default.nix
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
{ pkgs }: rec {
{ pkgs }:

transformer = pkgs.callPackage ./nix/packages/transformer.nix { };
let
passthruVendoredSbom = import ./nix/passthru-vendored.nix;
transformerWithoutSbom = pkgs.callPackage ./nix/packages/transformer.nix { };
in
rec {

buildBom = pkgs.callPackage ./nix/build-bom.nix {
inherit transformer;
# It's useful to have these exposed for debugging. However, they are not a
# public interface.
__internal = {
buildtimeDependencies = pkgs.callPackage ./nix/buildtime-dependencies.nix { };
runtimeDependencies = pkgs.callPackage ./nix/runtime-dependencies.nix { };
};

transformer = pkgs.callPackage (passthruVendoredSbom.rust transformerWithoutSbom) { };

buildBom = pkgs.callPackage ./nix/build-bom.nix {
inherit transformer;
inherit (__internal) buildtimeDependencies runtimeDependencies;
};

inherit passthruVendoredSbom;

}
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
# This is mostly here for development
inherit transformer;
default = transformer;
sbom = buildBom transformer { };
};

checks = {
Expand Down Expand Up @@ -108,6 +109,7 @@
pkgs.cargo-edit
pkgs.cargo-bloat
pkgs.cargo-deny
pkgs.cargo-cyclonedx
];

inputsFrom = [ transformer ];
Expand Down
28 changes: 19 additions & 9 deletions nix/buildtime-dependencies.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@

let

# Find the outputs of a derivation.
#
# Returns a list of all derivations that correspond to an output of the input
# derivation.
drvOutputs = drv:
if builtins.hasAttr "outputs" drv
then map (output: drv.${output}) drv.outputs
else [ drv ];

# Recurse into the derivation attributes to find new derivations
drvDeps = lib.mapAttrsToList
# Find the dependencies of a derivation via it's `drvAttrs`.
#
# Returns a list of all dependencies.
drvDeps = drv: lib.mapAttrsToList
(k: v:
if lib.isDerivation v then (drvOutputs v)
else if lib.isList v
then lib.concatMap drvOutputs (lib.filter lib.isDerivation v)
if lib.isDerivation v then
(drvOutputs v)
else if lib.isList v then
lib.concatMap drvOutputs (lib.filter lib.isDerivation v)
else [ ]
);
)
drv.drvAttrs;

wrap = drv: { key = drv.outPath; inherit drv; };

Expand All @@ -34,10 +42,10 @@ let
# All outputs are included because they have different outPaths
buildtimeDerivations = drv0: builtins.genericClosure {
startSet = map wrap (drvOutputs drv0);
operator = obj: map wrap (lib.concatLists (drvDeps obj.drv.drvAttrs));
operator = item: map wrap (lib.concatLists (drvDeps item.drv));
};

# Works like lib.getAttrs but omits attrs that do not exist
# Like lib.getAttrs but omit attrs that do not exist.
optionalGetAttrs = names: attrs:
lib.genAttrs (builtins.filter (x: lib.hasAttr x attrs) names) (name: attrs.${name});

Expand All @@ -54,6 +62,8 @@ let
} // lib.optionalAttrs (drv.src ? outputHash) {
hash = drv.src.outputHash;
};
} // lib.optionalAttrs (drv ? bombonVendoredSbom) {
vendored_sbom = drv.bombonVendoredSbom.outPath;
};

in
Expand All @@ -69,7 +79,7 @@ let
unformattedJson = writeText
"${drv.name}-unformatted-buildtime-dependencies.json"
(builtins.toJSON
(map (obj: (fields obj.drv)) allBuildtimeDerivations)
(map (item: (fields item.drv)) allBuildtimeDerivations)
);

in
Expand Down
26 changes: 26 additions & 0 deletions nix/passthru-vendored.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{

# Add a passthru derivation to a Rust derivation `package` that generates a
# CycloneDX SBOM.
#
# This could be done much more elegantly if `buildRustPackage` supported
# finalAttrs. When https://github.com/NixOS/nixpkgs/pull/194475 lands, we can
# most likely get rid of this.
rust = package: { cargo-cyclonedx }: package.overrideAttrs
(previousAttrs: {
passthru = (previousAttrs.passthru or { }) // {
bombonVendoredSbom = package.overrideAttrs (previousAttrs: {
nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [ cargo-cyclonedx ];
phases = [ "unpackPhase" "patchPhase" "buildPhase" "installPhase" ];
buildPhase = ''
cargo cyclonedx --spec-version 1.4 --format json
'';
installPhase = ''
mkdir -p $out
find . -name "*.cdx.json" -exec install {} $out/{} \;
'';
});
};
});

}
29 changes: 28 additions & 1 deletion rust/transformer/src/cyclonedx.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::convert::Into;
use std::fs;
use std::path::Path;
use std::str::FromStr;

use anyhow::Result;
use anyhow::{Context, Result};
use cyclonedx_bom::external_models::normalized_string::NormalizedString;
use cyclonedx_bom::external_models::uri::Purl;
use cyclonedx_bom::models::bom::{Bom, UrnUuid};
Expand All @@ -23,12 +25,19 @@ const VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct CycloneDXBom(Bom);

impl CycloneDXBom {
/// Serialize to JSON as bytes.
pub fn serialize(self) -> Result<Vec<u8>> {
let mut output = Vec::<u8>::new();
self.0.output_as_json_v1_4(&mut output)?;
Ok(output)
}

/// Read a `CycloneDXBom` from a path.
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let file = fs::File::open(path)?;
Ok(Self(Bom::parse_from_json(file)?))
}

pub fn build(target: Derivation, components: CycloneDXComponents, output: &Path) -> Self {
Self(Bom {
components: Some(components.into()),
Expand All @@ -39,6 +48,10 @@ impl CycloneDXBom {
..Bom::default()
})
}

fn components(self) -> Option<Components> {
self.0.components
}
}

/// Derive a serial number from some arbitrary data.
Expand Down Expand Up @@ -68,6 +81,20 @@ impl CycloneDXComponents {
.collect(),
))
}

/// Extend the `Components` with components read from multiple BOMs inside a directory.
pub fn extend_from_directory(&mut self, path: impl AsRef<Path>) -> Result<()> {
for entry in fs::read_dir(&path)
.with_context(|| format!("Failed to read {:?}", path.as_ref()))?
.flatten()
{
let bom = CycloneDXBom::from_file(entry.path())?;
if let Some(component) = bom.components() {
self.0 .0.extend(component.0);
}
}
Ok(())
}
}

impl From<CycloneDXComponents> for Components {
Expand Down
1 change: 1 addition & 0 deletions rust/transformer/src/derivation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct Derivation {
pub version: Option<String>,
pub meta: Option<Meta>,
pub src: Option<Src>,
pub vendored_sbom: Option<String>,
}

impl Derivation {
Expand Down
17 changes: 16 additions & 1 deletion rust/transformer/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,28 @@ pub fn transform(
.filter(|derivation| !runtime_input.0.contains(&derivation.path))
.unique_by(|d| d.name.clone().unwrap_or(d.path.clone()));

let components = if include_buildtime_dependencies {
let mut components = if include_buildtime_dependencies {
let all_derivations = runtime_derivations.chain(buildtime_derivations);
CycloneDXComponents::from_derivations(all_derivations)
} else {
CycloneDXComponents::from_derivations(runtime_derivations)
};

// Augment the components with those retrieved from the `sbom` passthru attribute of the
// derivations.
//
// We have to explicitly handle the target derivation since we removed it from the components
// previously.
if let Some(sbom_path) = &target_derivation.vendored_sbom {
components.extend_from_directory(sbom_path)?;
}

for derivation in buildtime_input.0.values() {
if let Some(sbom_path) = &derivation.vendored_sbom {
components.extend_from_directory(sbom_path)?;
}
}

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

0 comments on commit 45fa2aa

Please sign in to comment.