Skip to content

Commit

Permalink
sbomnix: Remove command-line argument: --type
Browse files Browse the repository at this point in the history
- Remove `--type` command-line argument from sbomnix. Instead, change
  sbomnix so that it support `--buildtime` argument to align the
  command-line usage with other tools.
- Update relevant documentation and tests

Signed-off-by: Henri Rosten <[email protected]>
  • Loading branch information
henrirosten committed Dec 11, 2023
1 parent e9e0889 commit d451ca2
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 104 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ release-asset: clean ## Build release asset
nix-shell -p nix-info --run "nix-info -m"
nix-env -qa --meta --json -f $(shell nix-shell -p nix-info --run "nix-info -m" | grep "nixpkgs: " | cut -d'`' -f2) '.*' >meta.json
mkdir -p build/
nix run .#sbomnix -- result --type=runtime \
nix run .#sbomnix -- result \
--meta=./meta.json \
--cdx=./build/sbom.runtime.cdx.json \
--spdx=./build/sbom.runtime.spdx.json \
--csv=./build/sbom.runtime.csv
nix run .#sbomnix -- result --type=buildtime \
nix run .#sbomnix -- result --buildtime \
--meta=./meta.json \
--cdx=./build/sbom.buildtime.cdx.json \
--spdx=./build/sbom.buildtime.spdx.json \
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ For reference, following is a link to graph from an example hello-world C progra
<img src="doc/img/c_hello_world_runtime.svg" width="700">


By default, all the tools in this repository assume runtime dependencies. This means, for instance, that unless specified otherwise, `sbomnix` will output an SBOM including the target runtime dependencies, `nixgraph` outputs runtime dependency graph, and `vulnxscan` and `nix_outdated` scan runtime dependencies. Since Nix needs to build the target output to determine the runtime dependencies, all the tools in this repository will also build (force-realise) the target output as part of each tool's invocation when determining the runtime dependencies. All the mentioned tools in this repository also support working with buildtime dependencies instead of runtime dependencies with the help of `--buildtime` command line argument. As mentioned earlier, generating buildtime dependencies in Nix does not require building the target. Similarly, when `--buildtime` is specified, the tools in this repository do not need to be build the given target.


## Usage Examples
The usage examples work for both the built package, as well as inside the devshell.

Expand All @@ -126,7 +129,7 @@ $ nix eval -f '<nixpkgs>' 'wget.outPath'

#### Generate SBOM Based on Derivation File or Out-path
By default `sbomnix` scans the given target and generates an SBOM including the runtime dependencies.
Keep in mind that determining the target runtime dependencies requires building the target.
Notice: determining the target runtime dependencies in Nix requires building the target.
```bash
$ sbomnix /nix/store/8nbv1drmvh588pwiwsxa47iprzlgwx6j-wget-1.21.3
...
Expand All @@ -147,11 +150,11 @@ $ sbomnix /nix/store/8nbv1drmvh588pwiwsxa47iprzlgwx6j-wget-1.21.3 --meta meta.js
```

#### Generate SBOM Including Buildtime Dependencies
By default `sbomnix` scans the given target for runtime dependencies. You can tell sbomnix to determine the buildtime dependencies using the `--type` argument.
Acceptable values for `--type` are `runtime, buildtime, both`. Below example generates SBOM including buildtime dependencies.
By default `sbomnix` scans the given target for runtime dependencies. You can tell sbomnix to determine the buildtime dependencies using the `--buildtime` argument.
Below example generates SBOM including buildtime dependencies.
Notice: as opposed to runtime dependencies, determining the buildtime dependencies does not require building the target.
```bash
$ sbomnix /nix/store/8nbv1drmvh588pwiwsxa47iprzlgwx6j-wget-1.21.3 --meta meta.json --type=buildtime
$ sbomnix /nix/store/8nbv1drmvh588pwiwsxa47iprzlgwx6j-wget-1.21.3 --meta meta.json --buildtime
```
#### Generate SBOM Based on Result Symlink
`sbomnix` can be used with output paths too (e.g. anything which produces a result symlink):
Expand Down
10 changes: 6 additions & 4 deletions src/nixupdate/nix_outdated.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ def getargs():
################################################################################


def _generate_sbom(target_path, runtime=True, buildtime=False):
def _generate_sbom(target_path, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
sbomdb = SbomDb(target_path, runtime, buildtime, meta_path=None)
sbomdb = SbomDb(target_path, buildtime, meta_path=None)
prefix = "nixdeps_"
suffix = ".cdx.json"
with NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) as f:
Expand Down Expand Up @@ -121,7 +121,7 @@ def _nix_visualize_csv_to_df(csvpath):
# Followed by the version string
r"(\d[-_.0-9pf]*g?b?(?:pre[0-9])*(?:\+git[0-9]*)?)"
# Optionally followed by any of the following strings
r"(?:-lib|-bin|-env|-man|-su|-dev|-doc|-info|-nc|-host|-p[0-9]+|)"
r"(?:-lib|-bin|-env|-man|-su|-dev|-doc|-info|-nc|-host|-p[0-9]+|\.drv|)"
# Followed by the end of line
r"$"
)
Expand Down Expand Up @@ -256,7 +256,7 @@ def main():
LOG.info("Checking %s dependencies referenced by '%s'", dtype, target_path_abs)
exit_unless_nix_artifact(target_path_abs, force_realise=runtime)

sbom_path = _generate_sbom(target_path_abs, runtime, args.buildtime)
sbom_path = _generate_sbom(target_path_abs, args.buildtime)
LOG.debug("Using SBOM '%s'", sbom_path)

df_repology = _run_repology_cli(sbom_path)
Expand All @@ -276,6 +276,8 @@ def main():
LOG.info("Not running nix-visualize due to '--buildtime' argument")
df_nix_visualize = None

df_log(df_repology, logging.DEBUG)
df_log(df_nix_visualize, logging.DEBUG)
df_report = _generate_report_df(df_nix_visualize, df_repology)
_report(df_report, args)

Expand Down
16 changes: 4 additions & 12 deletions src/sbomnix/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,8 @@ def getargs():
"to the output of this script (default: None)"
)
parser.add_argument("--meta", nargs="?", help=helps, default=None)
helps = (
"Set the type of dependencies included to the SBOM (default: runtime). "
"Note: generating 'runtime' SBOM requires realising (building) the "
"output paths of the target derivation. When 'runtime' SBOM is "
"requested, sbomnix will realise the target derivation unless its already "
"realised. See `nix-store --realise --help` for more info."
)
types = ["runtime", "buildtime", "both"]
parser.add_argument("--type", choices=types, help=helps, default="runtime")
helps = "Scan buildtime dependencies instead of runtime dependencies"
parser.add_argument("--buildtime", help=helps, action="store_true")
helps = (
"Set the depth of the included dependencies. As an example, --depth=1 "
"indicates the SBOM should include only the NIX_PATH direct dependencies. "
Expand Down Expand Up @@ -81,15 +74,14 @@ def main():
args = getargs()
set_log_verbosity(args.verbose)
target_path = args.NIX_PATH.resolve().as_posix()
runtime = args.type in ("runtime", "both")
buildtime = args.type in ("buildtime", "both")
runtime = args.buildtime is False
exit_unless_nix_artifact(target_path, force_realise=runtime)
if not args.meta:
LOG.warning(
"Command line argument '--meta' missing: SBOM will not include "
"license information (see '--help' for more details)"
)
sbomdb = SbomDb(target_path, runtime, buildtime, args.meta, args.depth)
sbomdb = SbomDb(target_path, args.buildtime, args.meta, args.depth)
if args.cdx:
sbomdb.to_cdx(args.cdx)
if args.spdx:
Expand Down
42 changes: 16 additions & 26 deletions src/sbomnix/sbomdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# SPDX-License-Identifier: Apache-2.0

# pylint: disable=invalid-name, too-many-instance-attributes, too-many-arguments
# pylint: disable=invalid-name, too-many-instance-attributes

""" Module for generating SBOMs in various formats """

Expand All @@ -27,39 +27,33 @@
class SbomDb:
"""Generates SBOMs in various formats"""

def __init__(
self, nix_path, runtime=True, buildtime=False, meta_path=None, depth=None
):
def __init__(self, nix_path, buildtime=False, meta_path=None, depth=None):
# self.uid specifies the attribute that SbomDb uses as unique
# identifier for the sbom components. See the column names in
# self.df_sbomdb (sbom.csv) for a list of all components' attributes.
self.uid = "store_path"
self.runtime = runtime
self.buildtime = buildtime
self.meta_path = meta_path
self.target_deriver = find_deriver(nix_path)
self.df_rdeps = None
self.df_bdeps = None
self.df_deps = None
self.depth = depth
self._init_dependencies(nix_path)
self.df_sbomdb = None
self.df_sbomdb_outputs_exploded = None
self._init_sbomdb()
self.uuid = uuid.uuid4()
self.sbom_type = "runtime_and_buildtime"
if self.runtime and not self.buildtime:
if not self.buildtime:
self.sbom_type = "runtime_only"
elif not self.runtime and self.buildtime:
self.sbom_type = "buildtime_only"

def _init_dependencies(self, nix_path):
"""Initialize runtime and buildtime dependencies (df_rdeps, df_bdeps)"""
if self.runtime:
runtime_dependencies = NixDependencies(nix_path, buildtime=False)
self.df_rdeps = self._get_dependencies_df(runtime_dependencies)
"""Initialize dependencies (df_deps)"""
if self.buildtime:
buildtime_dependencies = NixDependencies(nix_path, buildtime=True)
self.df_bdeps = self._get_dependencies_df(buildtime_dependencies)
self.df_deps = self._get_dependencies_df(buildtime_dependencies)
else:
runtime_dependencies = NixDependencies(nix_path, buildtime=False)
self.df_deps = self._get_dependencies_df(runtime_dependencies)

def _get_dependencies_df(self, nix_dependencies):
if self.depth:
Expand All @@ -75,18 +69,14 @@ def _get_dependencies_df(self, nix_dependencies):

def _init_sbomdb(self):
"""Initialize self.df_sbomdb"""
if (self.df_bdeps is None or self.df_bdeps.empty) and (
self.df_rdeps is None or self.df_rdeps.empty
):
if self.df_deps is None or self.df_deps.empty:
# No dependencies, so the only component in the sbom
# will be the target itself
paths = set([self.target_deriver])
else:
# Concat buildtime and runtime dependencies dropping duplicates
df_paths = pd.concat([self.df_rdeps, self.df_bdeps], ignore_index=True)
# Get unique src_paths and target_paths
src_paths = df_paths["src_path"].unique().tolist()
target_paths = df_paths["target_path"].unique().tolist()
src_paths = self.df_deps["src_path"].unique().tolist()
target_paths = self.df_deps["target_path"].unique().tolist()
paths = set(src_paths + target_paths)
# Populate store based on the dependencies
store = Store(self.buildtime)
Expand Down Expand Up @@ -128,17 +118,17 @@ def _lookup_dependencies(self, drv, uid="store_path"):
# Find runtime dependencies
# Runtime dependencies: drv.outputs matches with target_path
dfr = None
if self.df_rdeps is not None and not self.df_rdeps.empty:
df = self.df_rdeps[self.df_rdeps["target_path"].isin(drv.outputs)]
if self.df_deps is not None and not self.df_deps.empty:
df = self.df_deps[self.df_deps["target_path"].isin(drv.outputs)]
# Find the requested 'uid' values for the dependencies (df.src_path)
dfr = self.df_sbomdb_outputs_exploded.merge(
df, how="inner", left_on=["outputs"], right_on=["src_path"]
).loc[:, [uid]]
# Find buildtime dependencies
dfb = None
if self.df_bdeps is not None and not self.df_bdeps.empty:
if self.df_deps is not None and not self.df_deps.empty:
# Buildtime dependencies: drv.store_path matches with target_path
df = self.df_bdeps[self.df_bdeps["target_path"] == drv.store_path]
df = self.df_deps[self.df_deps["target_path"] == drv.store_path]
# Find the requested 'uid' values for the dependencies (df.src_path)
dfb = self.df_sbomdb.merge(
df, how="inner", left_on=["store_path"], right_on=["src_path"]
Expand Down
8 changes: 3 additions & 5 deletions src/vulnxscan/vulnxscan_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,9 +734,9 @@ def _is_patched(row):
return False


def _generate_sbom(target_path, runtime=True, buildtime=False):
def _generate_sbom(target_path, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
sbomdb = SbomDb(target_path, runtime, buildtime, meta_path=None)
sbomdb = SbomDb(target_path, buildtime, meta_path=None)
prefix = "vulnxscan_"
cdx_suffix = ".json"
csv_suffix = ".csv"
Expand Down Expand Up @@ -884,9 +884,7 @@ def main():
else:
runtime = args.buildtime is False
exit_unless_nix_artifact(target_path_abs, force_realise=runtime)
sbom_cdx_path, sbom_csv_path = _generate_sbom(
target_path_abs, runtime, args.buildtime
)
sbom_cdx_path, sbom_csv_path = _generate_sbom(target_path_abs, args.buildtime)
LOG.debug("Using cdx SBOM '%s'", sbom_cdx_path)
LOG.debug("Using csv SBOM '%s'", sbom_csv_path)
scanner.scan_vulnix(target_path_abs, args.buildtime)
Expand Down
2 changes: 1 addition & 1 deletion tests/compare_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def compare_dependencies(df_sbom, df_graph, sbom_type, graph_type):
df_sbom = df_sbom.astype(str)

if (graph_type == "runtime" and sbom_type != "runtime_only") or (
graph_type == "buildtime" and sbom_type != "buildtime_only"
graph_type == "buildtime" and sbom_type == "runtime_only"
):
LOG.fatal("Unable to compare: graph='%s' vs sbom='%s'", graph_type, sbom_type)
return False
Expand Down
Loading

0 comments on commit d451ca2

Please sign in to comment.