From 41fa7b27b55faa6d30ebf98973509836829b4b1c Mon Sep 17 00:00:00 2001 From: Zhijie Yang Date: Tue, 5 May 2026 17:24:11 +0200 Subject: [PATCH 1/2] feat: more conformity with SPDX-2.3 spec --- go.mod | 1 + go.sum | 2 ++ internal/builder/builder.go | 45 ++++++++++++++++++++++++------------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 239a2c2..f894ff5 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/canonical/chisel v1.1.1-0.20250127163729-ad87b0bb6f96 // indirect github.com/go-ini/ini v1.67.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/kr/pretty v0.2.1 // indirect diff --git a/go.sum b/go.sum index f905195..780f439 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 086c17d..6d1e178 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -1,13 +1,18 @@ package builder import ( + "crypto/md5" "fmt" + "regexp" "strings" + "time" + "github.com/google/uuid" "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/common" ) +const EmptySHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709" const EmptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" const DocumentName = "Chiselled Ubuntu Rootfs" @@ -41,6 +46,14 @@ var ChiselSbomDocCreator = []common.Creator{ } func BuildSPDXDocument(distro string, sliceInfos *[]SliceInfo, packageInfos *[]PackageInfo, pathInfos *[]PathInfo) (*spdx.Document, error) { + normalizedName := regexp.MustCompile(`(\s|#)+`).ReplaceAllString(strings.TrimSpace(DocumentName), "-") + randomUuid, err := uuid.NewUUID() + if err != nil { + return nil, err + } + + docNameSpace := "https://sbom.canonical.com/spdxdocs/" + normalizedName + "-" + randomUuid.String() + doc := &spdx.Document{ SPDXVersion: spdx.Version, DataLicense: spdx.DataLicense, @@ -48,7 +61,9 @@ func BuildSPDXDocument(distro string, sliceInfos *[]SliceInfo, packageInfos *[]P DocumentName: DocumentName, CreationInfo: &spdx.CreationInfo{ Creators: ChiselSbomDocCreator, + Created: time.Now().UTC().Format(time.RFC3339), }, + DocumentNamespace: docNameSpace, } if distro != "" { @@ -102,20 +117,26 @@ func BuildSPDXDocument(distro string, sliceInfos *[]SliceInfo, packageInfos *[]P return doc, nil } +func getMD5Hash(s string) string { + h := md5.New() + h.Write([]byte(s)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + func OSId(distro string) string { - return fmt.Sprintf("OperatingSystem-ubuntu-%s", distro) + return fmt.Sprintf("OperatingSystem-ubuntu-%s", getMD5Hash(distro)) } func (p *PackageInfo) SPDXId() string { - return fmt.Sprintf("Package-%s", p.Name) + return fmt.Sprintf("Package-%s", getMD5Hash(p.Name)) } func (s *SliceInfo) SPDXId() string { - return fmt.Sprintf("Slice-%s", s.Name) + return fmt.Sprintf("Slice-%s", getMD5Hash(s.Name)) } func (p *PathInfo) SPDXId() string { - return fmt.Sprintf("File-%s", p.Path) + return fmt.Sprintf("File-%s", getMD5Hash(p.Path)) } var UbuntuPackageSupplier = common.Supplier{ @@ -123,10 +144,6 @@ var UbuntuPackageSupplier = common.Supplier{ Supplier: "Ubuntu Developers ", } -func (p *PackageInfo) CPE23Locator() string { - return fmt.Sprintf("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*", p.Name, p.Name, p.Version) -} - func (p *PackageInfo) PurlLocator() string { locator := fmt.Sprintf("pkg:deb/ubuntu/%s@%s?arch=%s", p.Name, p.Version, p.Arch) if p.Distro != "" { @@ -146,11 +163,6 @@ func (p *PackageInfo) buildPackageSection() (*spdx.Package, *spdx.Relationship, PackageComment: "This package includes one or more slice(s); see Relationship information.", PackageSupplier: &UbuntuPackageSupplier, PackageExternalReferences: []*spdx.PackageExternalReference{ - { - Category: "SECURITY", - RefType: "cpe23Type", - Locator: p.CPE23Locator(), - }, { Category: "PACKAGE_MANAGER", RefType: "purl", @@ -175,7 +187,7 @@ func (s *SliceInfo) buildSliceSection() (*spdx.Package, *spdx.Relationship, erro PackageName: s.Name, PackageSPDXIdentifier: common.ElementID(s.SPDXId()), PackageDownloadLocation: "NOASSERTION", - FilesAnalyzed: false, + FilesAnalyzed: true, PackageComment: fmt.Sprintf("This slice is a sub-package of the package %s; see Relationship information.", packageName), } @@ -211,9 +223,12 @@ func (f *PathInfo) buildPathSection() (*spdx.File, []*spdx.Relationship, error) if f.FinalSHA256 != "" { sha256 = f.FinalSHA256 } + if sha256 == "" { + sha256 = EmptySHA256 + } var fileType int file := &spdx.File{ - FileName: f.Path, + FileName: strings.TrimLeft(f.Path, "/"), FileSPDXIdentifier: common.ElementID(f.SPDXId()), Checksums: []common.Checksum{{Algorithm: common.SHA256, Value: sha256}}, FileCopyrightText: "NOASSERTION", From f0c8bae092a9af92b0304821eb36cd2de6e734eb Mon Sep 17 00:00:00 2001 From: Zhijie Yang Date: Tue, 5 May 2026 17:51:38 +0200 Subject: [PATCH 2/2] feat: read doc name from os-release --- cmd/ssbom/main.go | 15 ++++++++++----- internal/builder/builder.go | 7 +++---- internal/converter/converter.go | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cmd/ssbom/main.go b/cmd/ssbom/main.go index d78dd0a..84234d7 100644 --- a/cmd/ssbom/main.go +++ b/cmd/ssbom/main.go @@ -57,7 +57,7 @@ func run() error { } defer zstdReader.Close() - osRelease, err := ReadOSRelease(filepath.Join(root, osReleasePath)) + osId, versionId, err := ReadOSRelease(filepath.Join(root, osReleasePath)) if err != nil { if os.IsNotExist(err) { fmt.Println("Warning: OS release file not found in the chiselled rootfs.") @@ -67,7 +67,7 @@ func run() error { } } - doc, err := converter.Convert(zstdReader, osRelease) + doc, err := converter.Convert(zstdReader, osId, versionId) if err != nil { return err } @@ -83,13 +83,18 @@ func run() error { return nil } -func ReadOSRelease(configfile string) (string, error) { +func ReadOSRelease(configfile string) (string, string, error) { cfg, err := ini.Load(configfile) if err != nil { - return "", err + return "", "", err } versionId := cfg.Section("").Key("VERSION_ID").String() + osId := cfg.Section("").Key("ID").String() - return versionId, nil + if versionId == "" || osId == "" { + return "", "", fmt.Errorf("invalid os-release file: missing ID or VERSION_ID") + } + + return osId, versionId, nil } diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 6d1e178..196137b 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -14,7 +14,6 @@ import ( const EmptySHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709" const EmptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" -const DocumentName = "Chiselled Ubuntu Rootfs" type PackageInfo struct { Name string @@ -45,8 +44,8 @@ var ChiselSbomDocCreator = []common.Creator{ }, } -func BuildSPDXDocument(distro string, sliceInfos *[]SliceInfo, packageInfos *[]PackageInfo, pathInfos *[]PathInfo) (*spdx.Document, error) { - normalizedName := regexp.MustCompile(`(\s|#)+`).ReplaceAllString(strings.TrimSpace(DocumentName), "-") +func BuildSPDXDocument(name, distro string, sliceInfos *[]SliceInfo, packageInfos *[]PackageInfo, pathInfos *[]PathInfo) (*spdx.Document, error) { + normalizedName := regexp.MustCompile(`(\s|#)+`).ReplaceAllString(strings.TrimSpace(name), "-") randomUuid, err := uuid.NewUUID() if err != nil { return nil, err @@ -58,7 +57,7 @@ func BuildSPDXDocument(distro string, sliceInfos *[]SliceInfo, packageInfos *[]P SPDXVersion: spdx.Version, DataLicense: spdx.DataLicense, SPDXIdentifier: spdx.ElementID("DOCUMENT"), - DocumentName: DocumentName, + DocumentName: name, CreationInfo: &spdx.CreationInfo{ Creators: ChiselSbomDocCreator, Created: time.Now().UTC().Format(time.RFC3339), diff --git a/internal/converter/converter.go b/internal/converter/converter.go index 6d56658..354ad8d 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -12,7 +12,7 @@ import ( ) // Convert converts a JSONWall to an SPDX document. -func Convert(reader io.Reader, distro string) (*spdx.Document, error) { +func Convert(reader io.Reader, name string, distro string) (*spdx.Document, error) { db, err := jsonwall.ReadDB(reader) if err != nil { return nil, fmt.Errorf("cannot read manifest: %s", err) @@ -27,7 +27,7 @@ func Convert(reader io.Reader, distro string) (*spdx.Document, error) { packageInfos := manifestData.ProcessPackages() pathInfos := manifestData.ProcessPaths() - doc, err := builder.BuildSPDXDocument(manifestData.Distro, &sliceInfos, &packageInfos, &pathInfos) + doc, err := builder.BuildSPDXDocument(name, distro, &sliceInfos, &packageInfos, &pathInfos) if err != nil { return nil, err }