Skip to content

Commit

Permalink
build: add CycloneDX SBOM JSON support
Browse files Browse the repository at this point in the history
CycloneDX is an open source standard developed by the OWASP foundation.
It supports a wide range of development ecosystems, a comprehensive set
of use cases, and focuses on automation, ease of adoption, and
progressive enhancement of SBOMs (Software Bill Of Materials) throughout
build pipelines.

So lets add support for CycloneDX SBOM for packages and images
manifests.

Signed-off-by: Petr Štetiar <ynezz@true.cz>
(cherry picked from commit d604a07)
  • Loading branch information
ynezz committed Nov 2, 2023
1 parent 4ef8899 commit 21e5db9
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 8 deletions.
8 changes: 8 additions & 0 deletions config/Config-build.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ menu "Global build settings"
directory containing machine readable list of built profiles
and resulting images.

config JSON_CYCLONEDX_SBOM
bool "Create CycloneDX SBOM JSON"
default BUILDBOT
help
Create a JSON files *.bom.cdx.json in the build
directory containing Software Bill Of Materials in CycloneDX
format.

config ALL_NONSHARED
bool "Select all target specific packages by default"
select ALL_KMODS
Expand Down
5 changes: 5 additions & 0 deletions include/image.mk
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ endef
define Image/Manifest
$(call opkg,$(TARGET_DIR_ORIG)) list-installed > \
$(BIN_DIR)/$(IMG_PREFIX)$(if $(PROFILE_SANITIZED),-$(PROFILE_SANITIZED)).manifest
$(if $(CONFIG_JSON_CYCLONEDX_SBOM), \
$(SCRIPT_DIR)/package-metadata.pl imgcyclonedxsbom \
$(TMP_DIR)/.packageinfo \
$(BIN_DIR)/$(IMG_PREFIX)$(if $(PROFILE_SANITIZED),-$(PROFILE_SANITIZED)).manifest > \
$(BIN_DIR)/$(IMG_PREFIX)$(if $(PROFILE_SANITIZED),-$(PROFILE_SANITIZED)).bom.cdx.json)
endef

define Image/gzip-ext4-padded-squashfs
Expand Down
8 changes: 8 additions & 0 deletions package/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ ifdef CONFIG_SIGNED_PACKAGES
$(STAGING_DIR_HOST)/bin/usign -S -m Packages -s $(BUILD_KEY); \
); done
endif
ifdef CONFIG_JSON_CYCLONEDX_SBOM
@echo Creating CycloneDX package SBOMs...
@for d in $(PACKAGE_SUBDIRS); do ( \
[ -d $$d ] && \
cd $$d || continue; \
$(SCRIPT_DIR)/package-metadata.pl pkgcyclonedxsbom Packages.manifest > Packages.bom.cdx.json || true; \
); done
endif

$(curdir)/flags-install:= -j1

Expand Down
40 changes: 39 additions & 1 deletion scripts/metadata.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package metadata;
use base 'Exporter';
use strict;
use warnings;
our @EXPORT = qw(%package %vpackage %srcpackage %category %overrides clear_packages parse_package_metadata parse_target_metadata get_multiline @ignore %usernames %groupnames);
our @EXPORT = qw(%package %vpackage %srcpackage %category %overrides clear_packages parse_package_metadata parse_package_manifest_metadata parse_target_metadata get_multiline @ignore %usernames %groupnames);

our %package;
our %vpackage;
Expand Down Expand Up @@ -317,4 +317,42 @@ sub parse_package_metadata($) {
return 1;
}

sub parse_package_manifest_metadata($) {
my $file = shift;
my $pkg;
my %pkgs;

open FILE, "<$file" or do {
warn "Cannot open '$file': $!\n";
return undef;
};

while (<FILE>) {
chomp;
/^Package:\s*(.+?)\s*$/ and do {
$pkg = {};
$pkg->{name} = $1;
$pkg->{depends} = [];
$pkgs{$1} = $pkg;
};
/^Version:\s*(.+)\s*$/ and $pkg->{version} = $1;
/^Depends:\s*(.+)\s*$/ and $pkg->{depends} = [ split /\s+/, $1 ];
/^Source:\s*(.+)\s*$/ and $pkg->{source} = $1;
/^SourceName:\s*(.+)\s*$/ and $pkg->{sourcename} = $1;
/^License:\s*(.+)\s*$/ and $pkg->{license} = $1;
/^LicenseFiles:\s*(.+)\s*$/ and $pkg->{licensefiles} = $1;
/^Section:\s*(.+)\s*$/ and $pkg->{section} = $1;
/^SourceDateEpoch: \s*(.+)\s*$/ and $pkg->{sourcedateepoch} = $1;
/^CPE-ID:\s*(.+)\s*$/ and $pkg->{cpe_id} = $1;
/^Architecture:\s*(.+)\s*$/ and $pkg->{architecture} = $1;
/^Installed-Size:\s*(.+)\s*$/ and $pkg->{installedsize} = $1;
/^Filename:\s*(.+)\s*$/ and $pkg->{filename} = $1;
/^Size:\s*(\d+)\s*$/ and $pkg->{size} = $1;
/^SHA256sum:\s*(.*)\s*$/ and $pkg->{sha256sum} = $1;
}

close FILE;
return %pkgs;
}

1;
187 changes: 180 additions & 7 deletions scripts/package-metadata.pl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
use strict;
use metadata;
use Getopt::Long;
use Time::Piece;
use JSON::PP;

my %board;

Expand Down Expand Up @@ -620,6 +622,173 @@ ()
print "[$json]";
}

sub image_manifest_packages($)
{
my %packages;
my $imgmanifest = shift;

open FILE, "<$imgmanifest" or return;
while (<FILE>) {
/^(.+?) - (.+)$/ and $packages{$1} = $2;
}
close FILE;

return %packages;
}

sub dump_cyclonedxsbom_json {
my (@components) = @_;

my $uuid = sprintf(
"%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
rand(0xffff), rand(0xffff), rand(0xffff),
rand(0x0fff) | 0x4000,
rand(0x3fff) | 0x8000,
rand(0xffff), rand(0xffff), rand(0xffff)
);

my $cyclonedx = {
bomFormat => "CycloneDX",
specVersion => "1.4",
serialNumber => "urn:uuid:$uuid",
version => 1,
metadata => {
timestamp => gmtime->datetime,
},
"components" => [@components],
};

return encode_json($cyclonedx);
}

sub gen_image_cyclonedxsbom() {
my $pkginfo = shift @ARGV;
my $imgmanifest = shift @ARGV;
my @components;
my %image_packages;

%image_packages = image_manifest_packages($imgmanifest);
%image_packages or exit 1;
parse_package_metadata($pkginfo) or exit 1;

$package{"kernel"} = {
license => "GPL-2.0",
cpe_id => "cpe:/o:linux:linux_kernel",
name => "kernel",
};

my %abimap;
my @abipkgs = grep { defined $package{$_}->{abi_version} } keys %package;
foreach my $name (@abipkgs) {
my $pkg = $package{$name};
my $abipkg = $name . $pkg->{abi_version};
$abimap{$abipkg} = $name;
}

foreach my $name (sort {uc($a) cmp uc($b)} keys %image_packages) {
my $pkg = $package{$name};
if (!$pkg) {
$pkg = $package{$abimap{$name}};
next if !$pkg;
}

my @licenses;
my @license = split(/\s+/, $pkg->{license});
foreach my $lic (@license) {
push @licenses, (
{ "license" => { "name" => $lic } }
);
}
my $type;
if ($pkg->{category}) {
my $category = $pkg->{category};
my %cat_type = (
"Firmware" => "firmware",
"Libraries" => "library"
);

if ($cat_type{$category}) {
$type = $cat_type{$category};
} else {
$type = "application";
}
}

my $version = $pkg->{version};
if ($image_packages{$name}) {
$version = $image_packages{$name};
}
$version =~ s/-\d+$// if $version;
if ($name =~ /^(kernel|kmod-)/ and $version =~ /^(\d+\.\d+\.\d+)/) {
$version = $1;
}

push @components, {
name => $pkg->{name},
version => $version,
@licenses > 0 ? (licenses => [ @licenses ]) : (),
$pkg->{cpe_id} ? (cpe => $pkg->{cpe_id}.":".$version) : (),
$type ? (type => $type) : (),
$version ? (version => $version) : (),
};
}

print dump_cyclonedxsbom_json(@components);
}

sub gen_package_cyclonedxsbom() {
my $pkgmanifest = shift @ARGV;
my @components;
my %mpkgs;

%mpkgs = parse_package_manifest_metadata($pkgmanifest);
%mpkgs or exit 1;

foreach my $name (sort {uc($a) cmp uc($b)} keys %mpkgs) {
my $pkg = $mpkgs{$name};

my @licenses;
my @license = split(/\s+/, $pkg->{license});
foreach my $lic (@license) {
push @licenses, (
{ "license" => { "name" => $lic } }
);
}

my $type;
if ($pkg->{section}) {
my $section = $pkg->{section};
my %section_type = (
"firmware" => "firmware",
"libs" => "library"
);

if ($section_type{$section}) {
$type = $section_type{$section};
} else {
$type = "application";
}
}

my $version = $pkg->{version};
$version =~ s/-\d+$// if $version;
if ($name =~ /^(kernel|kmod-)/ and $version =~ /^(\d+\.\d+\.\d+)/) {
$version = $1;
}

push @components, {
name => $name,
version => $version,
@licenses > 0 ? (licenses => [ @licenses ]) : (),
$pkg->{cpe_id} ? (cpe => $pkg->{cpe_id}.":".$version) : (),
$type ? (type => $type) : (),
$version ? (version => $version) : (),
};
}

print dump_cyclonedxsbom_json(@components);
}

sub parse_command() {
GetOptions("ignore=s", \@ignore);
my $cmd = shift @ARGV;
Expand All @@ -630,22 +799,26 @@ ()
/^source$/ and return gen_package_source();
/^pkgaux$/ and return gen_package_auxiliary();
/^pkgmanifestjson$/ and return gen_package_manifest_json();
/^imgcyclonedxsbom$/ and return gen_image_cyclonedxsbom();
/^pkgcyclonedxsbom$/ and return gen_package_cyclonedxsbom();
/^license$/ and return gen_package_license(0);
/^licensefull$/ and return gen_package_license(1);
/^usergroup$/ and return gen_usergroup_list();
/^version_filter$/ and return gen_version_filtered_list();
}
die <<EOF
Available Commands:
$0 mk [file] Package metadata in makefile format
$0 config [file] Package metadata in Kconfig format
$0 mk [file] Package metadata in makefile format
$0 config [file] Package metadata in Kconfig format
$0 kconfig [file] [config] [patchver] Kernel config overrides
$0 source [file] Package source file information
$0 pkgaux [file] Package auxiliary variables in makefile format
$0 pkgmanifestjson [file] Package manifests in JSON format
$0 license [file] Package license information
$0 source [file] Package source file information
$0 pkgaux [file] Package auxiliary variables in makefile format
$0 pkgmanifestjson [file] Package manifests in JSON format
$0 imgcyclonedxsbom <file> [manifest] Image package manifest in CycloneDX SBOM JSON format
$0 pkgcyclonedxsbom <file> Package manifest in CycloneDX SBOM JSON format
$0 license [file] Package license information
$0 licensefull [file] Package license information (full list)
$0 usergroup [file] Package usergroup allocation list
$0 usergroup [file] Package usergroup allocation list
$0 version_filter [patchver] [list...] Filter list of version tagged strings
Options:
Expand Down

0 comments on commit 21e5db9

Please sign in to comment.