diff --git a/go.mod b/go.mod index b73651343..954abf294 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/quay/zlog v1.1.7 github.com/remind101/migrate v0.0.0-20170729031349-52c1edff7319 github.com/rs/zerolog v1.30.0 + github.com/spdx/tools-golang v0.5.3 github.com/ulikunitz/xz v0.5.11 go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 @@ -35,6 +36,7 @@ require ( ) require ( + github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 30a35cb03..1c5d89f60 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -176,6 +178,9 @@ github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXY github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= +github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -191,6 +196,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -318,6 +324,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -346,3 +353,4 @@ modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/sbom/spdx/spdx.go b/pkg/sbom/spdx/spdx.go new file mode 100644 index 000000000..955131696 --- /dev/null +++ b/pkg/sbom/spdx/spdx.go @@ -0,0 +1,74 @@ +package spdx + +import ( + "fmt" + "runtime/debug" + "time" + + "github.com/spdx/tools-golang/spdx/v2/common" + spdxtools "github.com/spdx/tools-golang/spdx/v2/v2_3" + + "github.com/quay/claircore" +) + +func ParseIndexReport(vr *claircore.IndexReport) (*spdxtools.Document, error) { + // Initial metadata + out := &spdxtools.Document{ + SPDXVersion: spdxtools.Version, + DataLicense: spdxtools.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "SPDX-claircore-" + vr.Hash.String(), + // This would be nice to have but don't know how we'd get context w/o + // having to accept it as an argument. + // DocumentNamespace: "https://clairproject.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301", + CreationInfo: &spdxtools.CreationInfo{ + Creators: []common.Creator{ + {CreatorType: "Tool", Creator: "Claircore"}, + {CreatorType: "Organization", Creator: "Clair"}, + }, + Created: time.Now().Format("2006-01-02T15:04:05Z"), + }, + DocumentComment: fmt.Sprintf("This document was created using claircore (%s).", getVersion()), + } + + for _, p := range vr.Packages { + pkgDB := "" + for _, e := range vr.Environments[p.ID] { + if e.PackageDB != "" { + pkgDB = e.PackageDB + } + } + pkg := &spdxtools.Package{ + PackageName: p.Name, + PackageSPDXIdentifier: common.ElementID("SPDXRef-" + p.ID), + PackageVersion: p.Version, + PackageFileName: pkgDB, + PackageDownloadLocation: "NOASSERTION", + FilesAnalyzed: true, + } + out.Packages = append(out.Packages, pkg) + } + return out, nil +} + +// GetVersion is copied from Clair and can hopefully give some +// context as to which revision of claircore was used. +func getVersion() string { + info, infoOK := debug.ReadBuildInfo() + var core string + if infoOK { + for _, m := range info.Deps { + if m.Path != "github.com/quay/claircore" { + continue + } + core = m.Version + if m.Replace != nil && m.Replace.Version != m.Version { + core = m.Replace.Version + } + } + } + if core == "" { + core = "unknown revision" + } + return core +} diff --git a/pkg/sbom/spdx/spdx_test.go b/pkg/sbom/spdx/spdx_test.go new file mode 100644 index 000000000..6a5c8dc85 --- /dev/null +++ b/pkg/sbom/spdx/spdx_test.go @@ -0,0 +1,70 @@ +package spdx + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/quay/claircore" + + "github.com/spdx/tools-golang/tagvalue" +) + +func TestParseIndexReport(t *testing.T) { + //ctx := context.Background() + for _, ir := range testIndexReports { + t.Run("TODO", func(t *testing.T) { + + s, err := ParseIndexReport(ir) + if err != nil { + t.Fatal(err) + } + if len(s.Packages) != 2 { + t.Error("expecting 2 packages") + } + w := &bytes.Buffer{} + err = tagvalue.Write(s, w) + if err != nil { + t.Fatal(err) + } + fmt.Println(string(w.Bytes())) + + }) + } + t.Error() +} + +var testIndexReports = []*claircore.IndexReport{ + { + Hash: claircore.MustParseDigest(`sha256:` + strings.Repeat(`a`, 64)), + Packages: map[string]*claircore.Package{ + "123": { + ID: "123", + Name: "package A", + Version: "v1.0.0", + }, + "456": { + ID: "456", + Name: "package B", + Version: "v2.0.0", + }, + }, + Environments: map[string][]*claircore.Environment{ + "123": { + { + PackageDB: "var/lib/dpkg/status", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`b`, 64)), + RepositoryIDs: []string{"11"}, + }, + }, + "456": { + { + PackageDB: "maven:opt/couchbase/lib/cbas/repo/eventstream-1.0.1.jar", + IntroducedIn: claircore.MustParseDigest(`sha256:` + strings.Repeat(`c`, 64)), + RepositoryIDs: []string{"12"}, + }, + }, + }, + }, +}