mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 23:46:35 +01:00
fix: support CycloneDX 1.7 (#4967)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": "urn:uuid:5208fea9-73dd-4624-b596-69fddccdb9e7",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2023-09-29T12:02:02-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "a0ff99a6af10f11f",
|
||||
"type": "file",
|
||||
"name": "go.mod",
|
||||
"version": "sha256:sha256:dc333f342905248a52e424d8dfd061251d01867d01a4f9d7397144a775ff9ebd"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651?package-id=2ff71a67fb024c86",
|
||||
"type": "library",
|
||||
"name": "github.com/wagoodman/go-partybus",
|
||||
"version": "v0.0.0-20230516145632-8ccac152c651",
|
||||
"cpe": "cpe:2.3:a:wagoodman:go-partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*",
|
||||
"purl": "pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "go-module-file-cataloger"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:language",
|
||||
"value": "go"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "GolangModMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "go-module"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:wagoodman:go_partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/go.mod"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"specVersion": "1.7",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.7" serialNumber="urn:uuid:098e8516-ecd5-4130-9d5f-c32ba1ddb0dd" version="1">
|
||||
<metadata>
|
||||
<timestamp>2023-09-29T11:48:10-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
<name>syft</name>
|
||||
<version>[not provided]</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="a0ff99a6af10f11f" type="file">
|
||||
<name>go.mod</name>
|
||||
<version>sha256:sha256:dc333f342905248a52e424d8dfd061251d01867d01a4f9d7397144a775ff9ebd</version>
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651?package-id=2ff71a67fb024c86" type="library">
|
||||
<name>github.com/wagoodman/go-partybus</name>
|
||||
<version>v0.0.0-20230516145632-8ccac152c651</version>
|
||||
<cpe>cpe:2.3:a:wagoodman:go-partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*</cpe>
|
||||
<purl>pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651</purl>
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">go-module-file-cataloger</property>
|
||||
<property name="syft:package:language">go</property>
|
||||
<property name="syft:package:metadataType">GolangModMetadata</property>
|
||||
<property name="syft:package:type">go-module</property>
|
||||
<property name="syft:cpe23">cpe:2.3:a:wagoodman:go_partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*</property>
|
||||
<property name="syft:location:0:path">/go.mod</property>
|
||||
</properties>
|
||||
</component>
|
||||
</components>
|
||||
</bom>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="redacted" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.7" serialNumber="redacted" version="1">
|
||||
<metadata>
|
||||
<timestamp>redacted</timestamp>
|
||||
<tools>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="redacted" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.7" serialNumber="redacted" version="1">
|
||||
<metadata>
|
||||
<timestamp>redacted</timestamp>
|
||||
<tools>
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const DefaultVersion = "1.6"
|
||||
|
||||
type Encoder struct {
|
||||
version cyclonedx.SpecVersion
|
||||
format cyclonedx.BOMFileFormat
|
||||
|
||||
@@ -2,6 +2,10 @@ package cyclonedxutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
@@ -13,59 +17,81 @@ const (
|
||||
JSONFormatID sbom.FormatID = "cyclonedx-json"
|
||||
)
|
||||
|
||||
const DefaultVersion = "1.7"
|
||||
|
||||
var commonVersions = map[string]cyclonedx.SpecVersion{
|
||||
"1.2": cyclonedx.SpecVersion1_2,
|
||||
"1.3": cyclonedx.SpecVersion1_3,
|
||||
"1.4": cyclonedx.SpecVersion1_4,
|
||||
"1.5": cyclonedx.SpecVersion1_5,
|
||||
"1.6": cyclonedx.SpecVersion1_6,
|
||||
DefaultVersion: cyclonedx.SpecVersion1_7,
|
||||
}
|
||||
|
||||
var allVersions = func() map[string]cyclonedx.SpecVersion {
|
||||
out := map[string]cyclonedx.SpecVersion{
|
||||
"1.0": cyclonedx.SpecVersion1_0,
|
||||
"1.1": cyclonedx.SpecVersion1_1,
|
||||
}
|
||||
maps.Copy(out, commonVersions)
|
||||
return out
|
||||
}()
|
||||
|
||||
func SupportedVersions(id sbom.FormatID) []string {
|
||||
versions := []string{
|
||||
"1.2",
|
||||
"1.3",
|
||||
"1.4",
|
||||
"1.5",
|
||||
"1.6",
|
||||
versionSet := commonVersions
|
||||
if id == XMLFormatID {
|
||||
versionSet = allVersions
|
||||
}
|
||||
|
||||
if id != JSONFormatID {
|
||||
// JSON format not supported for version < 1.2
|
||||
versions = append([]string{"1.0", "1.1"}, versions...)
|
||||
versions := make([]string, 0, len(versionSet))
|
||||
for _, v := range versionSet {
|
||||
versions = append(versions, v.String())
|
||||
}
|
||||
|
||||
slices.SortFunc(versions, versionSort)
|
||||
return versions
|
||||
}
|
||||
|
||||
func SpecVersionFromString(v string) (cyclonedx.SpecVersion, error) {
|
||||
switch v {
|
||||
case "1.0":
|
||||
return cyclonedx.SpecVersion1_0, nil
|
||||
case "1.1":
|
||||
return cyclonedx.SpecVersion1_1, nil
|
||||
case "1.2":
|
||||
return cyclonedx.SpecVersion1_2, nil
|
||||
case "1.3":
|
||||
return cyclonedx.SpecVersion1_3, nil
|
||||
case "1.4":
|
||||
return cyclonedx.SpecVersion1_4, nil
|
||||
case "1.5":
|
||||
return cyclonedx.SpecVersion1_5, nil
|
||||
case "1.6":
|
||||
return cyclonedx.SpecVersion1_6, nil
|
||||
if specVersion, ok := allVersions[v]; ok {
|
||||
return specVersion, nil
|
||||
}
|
||||
return -1, fmt.Errorf("unsupported CycloneDX version %q", v)
|
||||
}
|
||||
|
||||
func VersionFromSpecVersion(spec cyclonedx.SpecVersion) string {
|
||||
switch spec {
|
||||
case cyclonedx.SpecVersion1_0:
|
||||
return "1.0"
|
||||
case cyclonedx.SpecVersion1_1:
|
||||
return "1.1"
|
||||
case cyclonedx.SpecVersion1_2:
|
||||
return "1.2"
|
||||
case cyclonedx.SpecVersion1_3:
|
||||
return "1.3"
|
||||
case cyclonedx.SpecVersion1_4:
|
||||
return "1.4"
|
||||
case cyclonedx.SpecVersion1_5:
|
||||
return "1.5"
|
||||
case cyclonedx.SpecVersion1_6:
|
||||
return "1.6"
|
||||
for version, specVersion := range allVersions {
|
||||
if specVersion == spec {
|
||||
return version
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func versionSort(a string, b string) int {
|
||||
partsA := strings.Split(a, ".")
|
||||
partsB := strings.Split(b, ".")
|
||||
lenA := len(partsA)
|
||||
lenB := len(partsB)
|
||||
for i := range max(lenA, lenB) {
|
||||
if i >= lenA {
|
||||
return -1 // 1 < 1.x
|
||||
}
|
||||
if i >= lenB {
|
||||
return 1 // 1.x > 1
|
||||
}
|
||||
partA, errA := strconv.ParseInt(partsA[i], 10, 64)
|
||||
partB, errB := strconv.ParseInt(partsB[i], 10, 64)
|
||||
if errA != nil || errB != nil {
|
||||
// string compare if we can't parse one of the sides
|
||||
strcmp := strings.Compare(partsA[i], partsB[i])
|
||||
if strcmp == 0 {
|
||||
continue
|
||||
}
|
||||
return strcmp // not equal
|
||||
}
|
||||
if partA == partB {
|
||||
continue
|
||||
}
|
||||
return int(partA - partB)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package cyclonedxutil
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Test_allSpecVersionsMapped loads the cyclonedx-go package's type information and enumerates every
|
||||
// SpecVersion* constant it declares, asserting each one is present in our version maps. Upgrading
|
||||
// cyclonedx-go to a release that adds a new spec version will fail this test until versions.go is updated.
|
||||
func Test_allSpecVersionsMapped(t *testing.T) {
|
||||
const pkgPath = "github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName | packages.NeedTypes}, pkgPath)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pkgs, 1)
|
||||
|
||||
pkg := pkgs[0]
|
||||
require.Empty(t, pkg.Errors, "errors loading %s", pkgPath)
|
||||
require.NotNil(t, pkg.Types, "no type info loaded for %s", pkgPath)
|
||||
|
||||
// allVersions is the superset (json/common versions merged in), so it covers every spec version we map.
|
||||
mapped := make(map[cyclonedx.SpecVersion]struct{})
|
||||
for syftVersion, sv := range allVersions {
|
||||
// make sure the key matches the version it's mapped to
|
||||
require.Contains(t, sv.String(), syftVersion, "version %q is not correctly mapped to cyclonedx SpecVersion %q", syftVersion, sv.String())
|
||||
mapped[sv] = struct{}{}
|
||||
}
|
||||
|
||||
specVersionConst := regexp.MustCompile(`^SpecVersion\d`)
|
||||
scope := pkg.Types.Scope()
|
||||
found := 0
|
||||
for _, name := range scope.Names() {
|
||||
if !specVersionConst.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
c, ok := scope.Lookup(name).(*types.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
found++
|
||||
val, ok := constant.Int64Val(c.Val())
|
||||
require.Truef(t, ok, "could not read int value of cyclonedx.%s", name)
|
||||
_, ok = mapped[cyclonedx.SpecVersion(val)]
|
||||
|
||||
assert.Truef(t, ok, "cyclonedx.%s is not mapped in versions.go; add it when upgrading cyclonedx-go", name)
|
||||
}
|
||||
require.NotZero(t, found, "no SpecVersion* constants found in %s", pkgPath)
|
||||
}
|
||||
|
||||
func Test_versionSort(t *testing.T) {
|
||||
// versionSort returns <0 when a<b, 0 when a==b, >0 when a>b; assert on sign rather than exact value.
|
||||
sign := func(n int) int {
|
||||
switch {
|
||||
case n < 0:
|
||||
return -1
|
||||
case n > 0:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a string
|
||||
b string
|
||||
want int
|
||||
}{
|
||||
{name: "equal single part", a: "1", b: "1", want: 0},
|
||||
{name: "equal two parts", a: "1.4", b: "1.4", want: 0},
|
||||
{name: "minor less than", a: "1.3", b: "1.4", want: -1},
|
||||
{name: "minor greater than", a: "1.6", b: "1.5", want: 1},
|
||||
{name: "major less than", a: "1.7", b: "2.0", want: -1},
|
||||
{name: "major greater than", a: "2.0", b: "1.7", want: 1},
|
||||
{name: "fewer parts sorts first", a: "1", b: "1.0", want: -1},
|
||||
{name: "more parts sorts last", a: "1.0", b: "1", want: 1},
|
||||
{name: "numeric not lexical (10 > 9)", a: "1.10", b: "1.9", want: 1},
|
||||
{name: "multi-digit major", a: "10.0", b: "9.0", want: 1},
|
||||
{name: "three parts equal", a: "1.4.0", b: "1.4.0", want: 0},
|
||||
{name: "three parts patch differs", a: "1.4.1", b: "1.4.0", want: 1},
|
||||
{name: "non-numeric equal falls through", a: "1.x", b: "1.x", want: 0},
|
||||
{name: "non-numeric string compare", a: "1.a", b: "1.b", want: -1},
|
||||
{name: "non-numeric side string compare", a: "1.2", b: "1.x", want: -1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, sign(versionSort(tt.a, tt.b)))
|
||||
// comparator must be antisymmetric: swapping operands negates the sign.
|
||||
assert.Equal(t, -tt.want, sign(versionSort(tt.b, tt.a)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_versionSort_sortsSlice(t *testing.T) {
|
||||
versions := []string{"1.10", "1.2", "1", "2.0", "1.9", "1.0"}
|
||||
slices.SortFunc(versions, versionSort)
|
||||
assert.Equal(t, []string{"1", "1.0", "1.2", "1.9", "1.10", "2.0"}, versions)
|
||||
}
|
||||
Reference in New Issue
Block a user