diff --git a/Dockerfile b/Dockerfile index 811699e..1554b1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,11 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins RUN git clone https://github.com/gardenlinux/resizefat32 RUN make -C resizefat32 install +FROM debian:testing AS syft +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates wget jq +RUN wget --quiet https://github.com/anchore/syft/releases/download/v1.44.0/syft_1.44.0_linux_$(dpkg --print-architecture).deb +RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends ./syft_1.44.0_linux_$(dpkg --print-architecture).deb + FROM debian:testing LABEL org.opencontainers.image.source="https://github.com/gardenlinux/builder" @@ -24,6 +29,7 @@ COPY --from=mv_data /usr/bin/mv_data /usr/bin/mv_data COPY --from=datefudge /usr/lib/datefudge/datefudge.so /usr/lib/datefudge/datefudge.so COPY --from=datefudge /usr/bin/datefudge /usr/bin/datefudge COPY --from=resizefat32 /usr/bin/resizefat32 /usr/bin/resizefat32 +COPY --from=syft /usr/bin/syft /usr/bin/syft RUN curl "https://github.com/gardenlinux/aws-kms-pkcs11/releases/download/latest/aws_kms_pkcs11-$(dpkg --print-architecture).so" -sLo "/usr/lib/$(uname -m)-linux-gnu/pkcs11/aws_kms_pkcs11.so" COPY builder /builder RUN mkdir /builder/cert diff --git a/README.md b/README.md index 14c5525..5e20e20 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,13 @@ ln -f -s ../builder/build build Now you can make your modifications inside the builder directory and running `./build ${target}` inside the gardenlinux repo will use the local builder, rebuilding the build container if necessary. + +If you want to use a modified builder docker image, you can edit your changes into the `Dockerfile` and run the image build with +``` +cd gardenlinux +./build --container-image localhost/builder aws-gardener_prod +``` + ## Licensing Copyright 2025 SAP SE or an SAP affiliate company and GardenLinux contributors. Please see our [LICENSE](LICENSE) for diff --git a/builder/dpkg_to_cyclonedx b/builder/dpkg_to_cyclonedx deleted file mode 100755 index b0b5aed..0000000 --- a/builder/dpkg_to_cyclonedx +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate a CycloneDX 1.5 SBOM (JSON) from installed dpkg packages. -Usage: - dpkg_to_cyclonedx [--input file.csv] [--output sbom.json] -""" - -import subprocess -import argparse -import uuid -import sys -import platform -import json - -from typing import Any - -from datetime import datetime, timezone -from pathlib import Path -from dataclasses import dataclass - -VERSION="1.0.0" - -DPKG_FIELDS = "\t".join([ - "${Package}", - "${Version}", - "${Architecture}", - "${Homepage}", - "${Maintainer}", - "${source:Package}", - "${source:Version}", -]) - -@dataclass -class Package: - """fields see https://man7.org/linux/man-pages/man1/dpkg-query.1.html""" - package: str - version: str - arch: str - homepage: str - maintainer: str - srcPackage: str - srcVersion: str - -@dataclass -class BuilderMetadata: - cname: str - version: str - arch: str - features: str - timestamp: int - -def read_packages(input_file: Path|None) -> list[Package]: - """Execute dpkg-query to evaluate packages""" - output: str = "" - - if input_file: - with open(input_file, 'r') as f: - output = f.read() - else: - result = subprocess.run(["dpkg-query", "--show", f"--show-fields='{DPKG_FIELDS}\n'"], capture_output=True, text=True, check=True) - - if result.returncode != 0: - raise ValueError("dpkg-query failed with rc=%d", [result.returncode]) - - output = result.stdout - - return parse_dpkg(output) - -def parse_dpkg(output: str) -> list[Package]: - """Parse output to list of packages""" - packages: list[Package] = [] - - for line in output.splitlines(): - parts = line.split("\t") # default delimiter with dpkg-query - if len(parts) != 7: - continue - - package = Package(package=parts[0], version=parts[1], arch=parts[2], homepage=parts[3], maintainer=parts[4], srcPackage=parts[5], srcVersion=parts[6]) - packages.append(package) - - return packages - -def pkg_to_component(pkg: Package) -> dict[str, Any]: - purl = f"pkg:deb/gardenlinux/{pkg.srcPackage}@{pkg.srcVersion}?arch={pkg.arch}" - - # TODO: add swid - component = { - "type": "library", - "bom-ref": f"{pkg.package}@{pkg.version}", - "name": pkg.srcPackage, - "version": pkg.srcVersion, - "description": "", - "licenses": [], - "purl": purl, - "hashes": [], - "cpe": "", - "externalReferences": [] - } - - if pkg.homepage: - homepageRef = { - "type": "website", - "url": pkg.homepage - } - component["externalReferences"].append(homepageRef) - - return component - -def build_sbom(packages: list[Package], builderMetadata: BuilderMetadata) -> dict[str, Any]: - """Assemble the full CycloneDX 1.5 BOM document. See https://github.com/CycloneDX/specification/blob/master/schema/bom-1.7.schema.json""" - now = datetime.now(timezone.utc).isoformat() - serial = f"urn:uuid:{uuid.uuid4()}" - - components = [pkg_to_component(p) for p in packages] - - # TODO: add swid - bom = { - "bomFormat": "CycloneDX", - "specVersion": "1.7", - "serialNumber": serial, - "version": 1, - "metadata": { - "timestamp": now, - "tools": [ - { - "vendor": "GardenLinux", - "name": sys.argv[0], - "version": VERSION - } - ], - "component": { - "type": "operating-system", - "name": "GardenLinux", - "version": builderMetadata.version, - "description": builderMetadata.cname, - "cpe": "", - "properties": [ - { - "name": "cname", - "value": builderMetadata.cname - }, - { - "name": "arch", - "value": builderMetadata.arch - }, - { - "name": "features", - "value": builderMetadata.features - }, - { - "name": "build timestamp", - "value": datetime.fromtimestamp(builderMetadata.timestamp).strftime('%Y-%m-%dT%H:%M:%SZ') - } - ] - }, - "lifecycles": [ - { - "phase": "post-build" - } - ] - }, - "components": components - } - - return bom - -def dpkg_to_cyclonedx(input_file: Path|None, builderMetadata: BuilderMetadata) -> dict[str, Any]: - """read packages and convert output to cyclonedx""" - packages = read_packages(input_file) - return build_sbom(packages=packages, builderMetadata=builderMetadata) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate CycloneDX SBOM from dpkg") - parser.add_argument("--input", default=None, help="Use an csv file as input instead of dpkg-query output") - parser.add_argument("--output", default="sbom.json", help="Output file (default: sbom.json)") - parser.add_argument("--builder_features", default="", help="GardenLinux image features present in the image at hand") - parser.add_argument("--builder_cname", default="", help="Cname") - parser.add_argument("--builder_arch", default="", help="Architecture") - parser.add_argument("--builder_version", default="", help="Version") - parser.add_argument("--builder_unixtimestamp", type=int, default="0", help="Unix-Timestamp of build time") - args = parser.parse_args() - - builderMetadata = BuilderMetadata(cname=args.builder_cname, version=args.builder_version, arch=args.builder_arch, features=args.builder_features, timestamp=args.builder_unixtimestamp) - - sbom = dpkg_to_cyclonedx(input_file=Path(args.input), builderMetadata=builderMetadata) - with open(Path(args.output), "w", encoding="utf-8") as f: - json.dump(sbom, f, indent=2, ensure_ascii=False) - - print(f"created sbom with {len(sbom["components"])} packages") diff --git a/builder/image.sbom b/builder/image.sbom index c558850..e30a44e 100755 --- a/builder/image.sbom +++ b/builder/image.sbom @@ -9,10 +9,26 @@ tar --extract --xattrs --xattrs-include '*' --directory "$chroot_dir" < "$1" mount --rbind --make-rslave /proc "$chroot_dir/proc" # build cyclonedx sbom -tmpfile="$(mktemp --suffix '.dpkg.csv')" -#shellcheck disable=SC2016 -chroot "$chroot_dir" dpkg-query --show --showformat='${binary:Package}\t${Version}\t${Architecture}\t${Homepage}\t${Maintainer}\t${source:Package}\t${source:Version}\n' > "$tmpfile" -./dpkg_to_cyclonedx --input "$tmpfile" --output "$2" --builder_features "$BUILDER_FEATURES" --builder_cname "$BUILDER_CNAME" --builder_arch "$BUILDER_ARCH" --builder_version "$BUILDER_VERSION" --builder_unixtimestamp "$BUILDER_TIMESTAMP" +SYFT_CACHE_DIR="$chroot_dir"/syft-cache syft --quiet "$1" --config syft.yaml --output cyclonedx-json | jq \ + --arg cname "$BUILDER_CNAME" \ + --arg arch "$BUILDER_ARCH" \ + --arg version "$BUILDER_VERSION" \ + --arg features "$BUILDER_FEATURES" \ + --arg builder_timestamp "$BUILDER_TIMESTAMP" \ + --arg cpe "" \ + '.metadata.lifecycles = [{"phase": "post-build"}] | + .metadata.component = { + "type": "operating-system", + "name": "GardenLinux", + "version": $version, + "cpe": $cpe, + "properties": [ + {"name": "cname", "value": $cname}, + {"name": "arch", "value": $arch}, + {"name": "features", "value": $features}, + {"name": "build timestamp", "value": $builder_timestamp} + ] + }' > "$2" umount -l "$chroot_dir/proc" diff --git a/builder/syft.yaml b/builder/syft.yaml new file mode 100644 index 0000000..64c77d4 --- /dev/null +++ b/builder/syft.yaml @@ -0,0 +1,10 @@ +catalogers: + - dpkg-db-cataloger + +format: + cyclonedx-json: + pretty: true + +package: + search-indexed-archives: true + search-unindexed-archives: true diff --git a/pkg.list b/pkg.list index 288c8a7..098d523 100644 --- a/pkg.list +++ b/pkg.list @@ -16,6 +16,7 @@ gnupg2 libcurl4 libengine-pkcs11-openssl libjson-c5 +jq make mmdebstrap mtools