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

Allow running sbomnix without realising out paths #86

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 2 additions & 6 deletions nixgraph/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

import argparse
import pathlib
import sys
from nixgraph.graph import NixDependencies
from sbomnix.utils import (
LOG,
set_log_verbosity,
get_py_pkg_version,
check_positive,
Expand Down Expand Up @@ -83,11 +81,9 @@ def main():
"""main entry point"""
args = getargs()
set_log_verbosity(args.verbose)
if not args.NIX_PATH.exists():
LOG.fatal("Invalid path: '%s'", args.NIX_PATH)
sys.exit(1)
target_path = args.NIX_PATH.resolve().as_posix()
exit_unless_nix_artifact(target_path)
runtime = args.buildtime is False
exit_unless_nix_artifact(target_path, force_realise=runtime)
deps = NixDependencies(target_path, args.buildtime)
deps.graph(args)

Expand Down
11 changes: 3 additions & 8 deletions sbomnix/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import argparse
import pathlib
import sys
from sbomnix.sbomdb import SbomDb
from sbomnix.utils import (
LOG,
Expand Down Expand Up @@ -81,19 +80,15 @@ def main():
"""main entry point"""
args = getargs()
set_log_verbosity(args.verbose)
if not args.NIX_PATH.exists():
LOG.fatal("Invalid path: '%s'", args.NIX_PATH)
sys.exit(1)
target_path = args.NIX_PATH.resolve().as_posix()
exit_unless_nix_artifact(target_path)
runtime = args.type in ("runtime", "both")
buildtime = args.type in ("buildtime", "both")
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)"
)
runtime = args.type in ("runtime", "both")
buildtime = args.type in ("buildtime", "both")

sbomdb = SbomDb(target_path, runtime, buildtime, args.meta, args.depth)
if args.cdx:
sbomdb.to_cdx(args.cdx)
Expand Down
10 changes: 5 additions & 5 deletions sbomnix/nix.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,6 @@ def find_deriver(path):
LOG.debug(path)
if path.endswith(".drv"):
return path
# Deriver from QueryPathInfo
qpi_deriver = exec_cmd(["nix-store", "-qd", path]).strip()
LOG.debug("qpi_deriver: %s", qpi_deriver)
if qpi_deriver and qpi_deriver != "unknown-deriver" and os.path.exists(qpi_deriver):
return qpi_deriver
# Deriver from QueryValidDerivers
ret = exec_cmd(["nix", "show-derivation", path], raise_on_error=False)
if not ret:
Expand All @@ -115,6 +110,11 @@ def find_deriver(path):
LOG.debug("qvd_deriver: %s", qvd_deriver)
if qvd_deriver and os.path.exists(qvd_deriver):
return qvd_deriver
# Deriver from QueryPathInfo
qpi_deriver = exec_cmd(["nix-store", "-qd", path]).strip()
LOG.debug("qpi_deriver: %s", qpi_deriver)
if qpi_deriver and qpi_deriver != "unknown-deriver" and os.path.exists(qpi_deriver):
return qpi_deriver

error = ""
if qpi_deriver and qpi_deriver != "unknown-deriver":
Expand Down
14 changes: 11 additions & 3 deletions sbomnix/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,17 @@ def exec_cmd(cmd, raise_on_error=True, return_error=False):
return None


def exit_unless_nix_artifact(path):
"""Exit with error if `path` is not a nix artifact"""
cmd = ["nix-store", "-q", path]
def exit_unless_nix_artifact(path, force_realise=False):
"""
Exit with error if `path` is not a nix artifact. If `force_realize` is True,
run the nix-store-query command with `--force-realize` realising the `path`
argument before running query.
"""
LOG.debug("force_realize: %s", force_realise)
if force_realise:
cmd = ["nix-store", "-qf", path]
else:
cmd = ["nix-store", "-q", path]
try:
exec_cmd(cmd)
return
Expand Down
5 changes: 4 additions & 1 deletion scripts/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ else
fi
# Remove duplicates from the PYTHONPATH, preserving order
PYTHONPATH=$(remove_dups "$PYTHONPATH")
echo "export PYTHONPATH=$PYTHONPATH"
export PYTHONPATH="$PYTHONPATH"
# Add all subdirs of REPOROOTDIR/scripts/ to PATH
for d in "$REPOROOTDIR/scripts/"*; do if [ -d "$d" ]; then PATH="$d:$PATH"; fi; done
PATH=$(remove_dups "$PATH")
export PATH="$PATH"

################################################################################
15 changes: 7 additions & 8 deletions scripts/nixupdate/nix_outdated.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ def getargs():
"that would have an update in the package's upstream repository."
)
parser.add_argument("--local", help=helps, action="store_true")
helps = (
"Include target's buildtime dependencies to the scan. "
"By default, only runtime dependencies are considered."
)
helps = "Scan target buildtime instead of runtime dependencies."
parser.add_argument("--buildtime", help=helps, action="store_true")
helps = "Path to output file (default: ./nix_outdated.csv)"
parser.add_argument("--out", nargs="?", help=helps, default="nix_outdated.csv")
Expand All @@ -77,9 +74,8 @@ def getargs():
################################################################################


def _generate_sbom(target_path, buildtime=False):
def _generate_sbom(target_path, runtime=True, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
runtime = True
sbomdb = SbomDb(target_path, runtime, buildtime, meta_path=None)
prefix = "nixdeps_"
suffix = ".cdx.json"
Expand Down Expand Up @@ -254,9 +250,12 @@ def main():
args = getargs()
set_log_verbosity(args.verbose)
target_path_abs = args.NIXPATH.resolve().as_posix()
exit_unless_nix_artifact(target_path_abs)
runtime = args.buildtime is False
dtype = "runtime" if runtime else "buildtime"
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, args.buildtime)
sbom_path = _generate_sbom(target_path_abs, runtime, args.buildtime)
LOG.info("Using SBOM '%s'", sbom_path)

repology_out_path = _run_repology_cli(sbom_path)
Expand Down
11 changes: 6 additions & 5 deletions scripts/nixupdate/nix_secupdates.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,7 @@ def getargs():
"not enabled by default."
)
parser.add_argument("--pr", help=helps, action="store_true")
helps = (
"Include target's buildtime dependencies to the scan. "
"By default, only runtime dependencies are considered."
)
helps = "Scan target buildtime instead of runtime dependencies."
parser.add_argument("--buildtime", help=helps, action="store_true")
helps = "Path to output file (default: ./nix_secupdates.csv)"
parser.add_argument("--out", nargs="?", help=helps, default="nix_secupdates.csv")
Expand Down Expand Up @@ -400,7 +397,11 @@ def main():
"""main entry point"""
args = getargs()
set_log_verbosity(args.verbose)
exit_unless_nix_artifact(args.NIXPATH.resolve().as_posix())
runtime = args.buildtime is False
nix_path = args.NIXPATH.resolve().as_posix()
dtype = "runtime" if runtime else "buildtime"
LOG.info("Checking %s dependencies referenced by '%s'", dtype, nix_path)
exit_unless_nix_artifact(nix_path, runtime)
df = _find_secupdates(args)
_report(df)
df_to_csv_file(df, args.out)
Expand Down
35 changes: 20 additions & 15 deletions scripts/vulnxscan/vulnxscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def getargs():
helps = "Path to output file (default: ./vulns.csv)"
parser.add_argument("--out", nargs="?", help=helps, default="vulns.csv")
helps = (
"Include target's buildtime dependencies to the scan. "
"By default, only runtime dependencies are scanned."
"Scan target buildtime instead of runtime dependencies. This option "
"has no impact if the scan target is SBOM (ref: --sbom)."
)
parser.add_argument("--buildtime", help=helps, action="store_true")
helps = (
Expand Down Expand Up @@ -109,12 +109,16 @@ def scan_vulnix(self, target_path, buildtime=False):
if buildtime:
extra_opts = "--json"
cmd = ["vulnix", target_path] + extra_opts.split()
# vulnix exit status is non-zero if it found vulnerabilities,
# therefore, we need to set the raise_on_error=False and
# return_error=True to be able to read the command's stdout on
# failure
# vulnix exit status is non-zero if it found vulnerabilities.
# Therefore, we need to set the raise_on_error=False and
# return_error=True to be able to read the vulnerabilities
# from vulnix stdout even if the exit status indicates failure.
ret = exec_cmd(cmd, raise_on_error=False, return_error=True)
if ret and hasattr(ret, "stdout") and ret.stdout:
if ret and hasattr(ret, "stderr") and ret.stderr:
LOG.warning(ret)
LOG.warning(ret.stderr)
self.df_vulnix = None
elif ret and hasattr(ret, "stdout") and ret.stdout:
self._parse_vulnix(ret.stdout)

def _parse_grype(self, json_str):
Expand Down Expand Up @@ -156,7 +160,9 @@ def _parse_osv(self, df_osv):
self.df_osv["scanner"] = "osv"
self.df_osv.replace(np.nan, "", regex=True, inplace=True)
self.df_osv.drop_duplicates(keep="first", inplace=True)
self.df_osv["modified"] = pd.to_datetime(self.df_osv["modified"])
self.df_osv["modified"] = pd.to_datetime(
self.df_osv["modified"], format="%Y-%m-%d", exact=False
)
LOG.log(LOG_SPAM, "osv data:\n%s", self.df_osv.to_markdown())
LOG.debug("OSV scan found vulnerabilities")
if LOG.level <= logging.DEBUG:
Expand Down Expand Up @@ -306,9 +312,8 @@ def _is_patched(row):
return False


def _generate_sbom(target_path, buildtime=False):
def _generate_sbom(target_path, runtime=True, buildtime=False):
LOG.info("Generating SBOM for target '%s'", target_path)
runtime = True
sbomdb = SbomDb(target_path, runtime, buildtime, meta_path=None)
prefix = "vulnxscan_"
cdx_suffix = ".json"
Expand Down Expand Up @@ -349,9 +354,6 @@ def main():
"""main entry point"""
args = getargs()
set_log_verbosity(args.verbose)
if not args.TARGET.exists():
LOG.fatal("Invalid path: '%s'", args.TARGET)
sys.exit(1)

# Fail early if following commands are not in path
exit_unless_command_exists("grype")
Expand All @@ -367,8 +369,11 @@ def main():
sbom_cdx_path = target_path_abs
sbom_csv_path = None
else:
exit_unless_nix_artifact(target_path_abs)
sbom_cdx_path, sbom_csv_path = _generate_sbom(target_path_abs, args.buildtime)
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
)
LOG.info("Using cdx SBOM '%s'", sbom_cdx_path)
LOG.info("Using csv SBOM '%s'", sbom_csv_path)
scanner.scan_vulnix(target_path_abs, args.buildtime)
Expand Down