Skip to content

Commit 6e9c2c3

Browse files
authored
fix(spdx): add PkgDownloadLocation field (#3879)
1 parent 18eeea2 commit 6e9c2c3

File tree

3 files changed

+101
-58
lines changed

3 files changed

+101
-58
lines changed

integration/testdata/conda-spdx.json.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
"packages": [
2727
{
2828
"SPDXID": "SPDXRef-Application-ee5ef1aa4ac89125",
29+
"downloadLocation": "NONE",
2930
"filesAnalyzed": false,
3031
"name": "conda-pkg",
3132
"sourceInfo": "Conda"
3233
},
3334
{
3435
"SPDXID": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
36+
"downloadLocation": "NONE",
3537
"attributionTexts": [
3638
"SchemaVersion: 2"
3739
],
@@ -40,6 +42,7 @@
4042
},
4143
{
4244
"SPDXID": "SPDXRef-Package-2984084f02572600",
45+
"downloadLocation": "NONE",
4346
"externalRefs": [
4447
{
4548
"referenceCategory": "PACKAGE-MANAGER",
@@ -58,6 +61,7 @@
5861
},
5962
{
6063
"SPDXID": "SPDXRef-Package-ac33eb699b3aa81d",
64+
"downloadLocation": "NONE",
6165
"externalRefs": [
6266
{
6367
"referenceCategory": "PACKAGE-MANAGER",

pkg/sbom/spdx/marshal.go

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
DocumentNamespace = "http://aquasecurity.github.io/trivy"
3030
CreatorOrganization = "aquasecurity"
3131
CreatorTool = "trivy"
32+
noneField = "NONE"
3233
)
3334

3435
const (
@@ -114,9 +115,10 @@ func NewMarshaler(version string, opts ...marshalOption) *Marshaler {
114115
func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
115116
var relationShips []*spdx.Relationship2_2
116117
packages := make(map[spdx.ElementID]*spdx.Package2_2)
118+
pkgDownloadLocation := getPackageDownloadLocation(r.ArtifactType, r.ArtifactName)
117119

118120
// Root package contains OS, OS packages, language-specific packages and so on.
119-
rootPkg, err := m.rootPackage(r)
121+
rootPkg, err := m.rootPackage(r, pkgDownloadLocation)
120122
if err != nil {
121123
return nil, xerrors.Errorf("failed to generate a root package: %w", err)
122124
}
@@ -126,7 +128,7 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
126128
)
127129

128130
for _, result := range r.Results {
129-
parentPackage, err := m.resultToSpdxPackage(result, r.Metadata.OS)
131+
parentPackage, err := m.resultToSpdxPackage(result, r.Metadata.OS, pkgDownloadLocation)
130132
if err != nil {
131133
return nil, xerrors.Errorf("failed to parse result: %w", err)
132134
}
@@ -136,7 +138,7 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
136138
)
137139

138140
for _, pkg := range result.Packages {
139-
spdxPackage, err := m.pkgToSpdxPackage(result.Type, result.Class, r.Metadata, pkg)
141+
spdxPackage, err := m.pkgToSpdxPackage(result.Type, pkgDownloadLocation, result.Class, r.Metadata, pkg)
140142
if err != nil {
141143
return nil, xerrors.Errorf("failed to parse package: %w", err)
142144
}
@@ -163,16 +165,16 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
163165
}, nil
164166
}
165167

166-
func (m *Marshaler) resultToSpdxPackage(result types.Result, os *ftypes.OS) (spdx.Package2_2, error) {
168+
func (m *Marshaler) resultToSpdxPackage(result types.Result, os *ftypes.OS, pkgDownloadLocation string) (spdx.Package2_2, error) {
167169
switch result.Class {
168170
case types.ClassOSPkg:
169-
osPkg, err := m.osPackage(os)
171+
osPkg, err := m.osPackage(os, pkgDownloadLocation)
170172
if err != nil {
171173
return spdx.Package2_2{}, xerrors.Errorf("failed to parse operating system package: %w", err)
172174
}
173175
return osPkg, nil
174176
case types.ClassLangPkg:
175-
langPkg, err := m.langPackage(result.Target, result.Type)
177+
langPkg, err := m.langPackage(result.Target, result.Type, pkgDownloadLocation)
176178
if err != nil {
177179
return spdx.Package2_2{}, xerrors.Errorf("failed to parse application package: %w", err)
178180
}
@@ -195,7 +197,7 @@ func (m *Marshaler) parseFile(filePath string) (spdx.File2_2, error) {
195197
return file, nil
196198
}
197199

198-
func (m *Marshaler) rootPackage(r types.Report) (*spdx.Package2_2, error) {
200+
func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*spdx.Package2_2, error) {
199201
var externalReferences []*spdx.PackageExternalReference2_2
200202
attributionTexts := []string{attributionText(PropertySchemaVersion, strconv.Itoa(r.SchemaVersion))}
201203

@@ -231,12 +233,13 @@ func (m *Marshaler) rootPackage(r types.Report) (*spdx.Package2_2, error) {
231233
return &spdx.Package2_2{
232234
PackageName: r.ArtifactName,
233235
PackageSPDXIdentifier: elementID(camelCase(string(r.ArtifactType)), pkgID),
236+
PackageDownloadLocation: pkgDownloadLocation,
234237
PackageAttributionTexts: attributionTexts,
235238
PackageExternalReferences: externalReferences,
236239
}, nil
237240
}
238241

239-
func (m *Marshaler) osPackage(osFound *ftypes.OS) (spdx.Package2_2, error) {
242+
func (m *Marshaler) osPackage(osFound *ftypes.OS, pkgDownloadLocation string) (spdx.Package2_2, error) {
240243
if osFound == nil {
241244
return spdx.Package2_2{}, nil
242245
}
@@ -247,26 +250,28 @@ func (m *Marshaler) osPackage(osFound *ftypes.OS) (spdx.Package2_2, error) {
247250
}
248251

249252
return spdx.Package2_2{
250-
PackageName: osFound.Family,
251-
PackageVersion: osFound.Name,
252-
PackageSPDXIdentifier: elementID(ElementOperatingSystem, pkgID),
253+
PackageName: osFound.Family,
254+
PackageVersion: osFound.Name,
255+
PackageSPDXIdentifier: elementID(ElementOperatingSystem, pkgID),
256+
PackageDownloadLocation: pkgDownloadLocation,
253257
}, nil
254258
}
255259

256-
func (m *Marshaler) langPackage(target, appType string) (spdx.Package2_2, error) {
260+
func (m *Marshaler) langPackage(target, appType, pkgDownloadLocation string) (spdx.Package2_2, error) {
257261
pkgID, err := calcPkgID(m.hasher, fmt.Sprintf("%s-%s", target, appType))
258262
if err != nil {
259263
return spdx.Package2_2{}, xerrors.Errorf("failed to get %s package ID: %w", target, err)
260264
}
261265

262266
return spdx.Package2_2{
263-
PackageName: appType,
264-
PackageSourceInfo: target, // TODO: Files seems better
265-
PackageSPDXIdentifier: elementID(ElementApplication, pkgID),
267+
PackageName: appType,
268+
PackageSourceInfo: target, // TODO: Files seems better
269+
PackageSPDXIdentifier: elementID(ElementApplication, pkgID),
270+
PackageDownloadLocation: pkgDownloadLocation,
266271
}, nil
267272
}
268273

269-
func (m *Marshaler) pkgToSpdxPackage(t string, class types.ResultClass, metadata types.Metadata, pkg ftypes.Package) (spdx.Package2_2, error) {
274+
func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.ResultClass, metadata types.Metadata, pkg ftypes.Package) (spdx.Package2_2, error) {
270275
license := GetLicense(pkg)
271276

272277
pkgID, err := calcPkgID(m.hasher, pkg)
@@ -296,10 +301,11 @@ func (m *Marshaler) pkgToSpdxPackage(t string, class types.ResultClass, metadata
296301
}
297302

298303
return spdx.Package2_2{
299-
PackageName: pkg.Name,
300-
PackageVersion: pkg.Version,
301-
PackageSPDXIdentifier: elementID(ElementPackage, pkgID),
302-
PackageSourceInfo: pkgSrcInfo,
304+
PackageName: pkg.Name,
305+
PackageVersion: pkg.Version,
306+
PackageSPDXIdentifier: elementID(ElementPackage, pkgID),
307+
PackageDownloadLocation: pkgDownloadLocation,
308+
PackageSourceInfo: pkgSrcInfo,
303309

304310
// The Declared License is what the authors of a project believe govern the package
305311
PackageLicenseConcluded: license,
@@ -361,7 +367,7 @@ func purlExternalReference(packageURL string) *spdx.PackageExternalReference2_2
361367

362368
func GetLicense(p ftypes.Package) string {
363369
if len(p.Licenses) == 0 {
364-
return "NONE"
370+
return noneField
365371
}
366372

367373
license := strings.Join(lo.Map(p.Licenses, func(license string, index int) string {
@@ -384,7 +390,7 @@ func getDocumentNamespace(r types.Report, m *Marshaler) string {
384390
return fmt.Sprintf("%s/%s/%s-%s",
385391
DocumentNamespace,
386392
string(r.ArtifactType),
387-
r.ArtifactName,
393+
strings.ReplaceAll(strings.ReplaceAll(r.ArtifactName, "https://", ""), "http://", ""), // remove http(s):// prefix when scanning repos
388394
m.newUUID().String(),
389395
)
390396
}
@@ -421,3 +427,16 @@ func camelCase(inputUnderScoreStr string) (camelCase string) {
421427
}
422428
return
423429
}
430+
431+
func getPackageDownloadLocation(t ftypes.ArtifactType, artifactName string) string {
432+
location := noneField
433+
// this field is used for git/mercurial/subversion/bazaar:
434+
// https://spdx.github.io/spdx-spec/v2.2.2/package-information/#77-package-download-location-field
435+
if t == ftypes.ArtifactRemoteRepository {
436+
// Trivy currently only supports git repositories. Format examples:
437+
// git+https://git.myproject.org/MyProject.git
438+
// git+http://git.myproject.org/MyProject
439+
location = fmt.Sprintf("git+%s", artifactName)
440+
}
441+
return location
442+
}

0 commit comments

Comments
 (0)