mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
feat: support dependencies and purl for Native Image SBOMs (#3399)
Signed-off-by: Joel Rudsberg <joel.rudsberg@oracle.com>
This commit is contained in:
parent
9302e20d62
commit
fcf1350a0e
@ -8,7 +8,6 @@ import (
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -18,34 +17,14 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type nativeImageCycloneDX struct {
|
||||
BomFormat string `json:"bomFormat"`
|
||||
SpecVersion string `json:"specVersion"`
|
||||
Version int `json:"version"`
|
||||
Components []nativeImageComponent `json:"components"`
|
||||
}
|
||||
|
||||
type nativeImageComponent struct {
|
||||
Type string `json:"type"`
|
||||
Group string `json:"group"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Properties []nativeImageCPE `json:"properties"`
|
||||
}
|
||||
|
||||
type nativeImageCPE struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type nativeImage interface {
|
||||
fetchPkgs() ([]pkg.Package, error)
|
||||
fetchPkgs() ([]pkg.Package, []artifact.Relationship, error)
|
||||
}
|
||||
|
||||
type nativeImageElf struct {
|
||||
@ -113,40 +92,12 @@ func (c *nativeImageCataloger) Name() string {
|
||||
return nativeImageCatalogerName
|
||||
}
|
||||
|
||||
// getPackage returns the package given within a NativeImageComponent.
|
||||
func getPackage(component nativeImageComponent) pkg.Package {
|
||||
var cpes []cpe.CPE
|
||||
for _, property := range component.Properties {
|
||||
c, err := cpe.New(property.Value, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.Debugf("unable to parse Attributes: %v", err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
return pkg.Package{
|
||||
Name: component.Name,
|
||||
Version: component.Version,
|
||||
Language: pkg.Java,
|
||||
Type: pkg.GraalVMNativeImagePkg,
|
||||
FoundBy: nativeImageCatalogerName,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: component.Group,
|
||||
},
|
||||
},
|
||||
CPEs: cpes,
|
||||
}
|
||||
}
|
||||
|
||||
// decompressSbom returns the packages given within a native image executable's SBOM.
|
||||
func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg.Package, error) {
|
||||
var pkgs []pkg.Package
|
||||
|
||||
func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
lengthEnd := lengthStart + 8
|
||||
bufLen := len(dataBuf)
|
||||
if lengthEnd > uint64(bufLen) {
|
||||
return nil, errors.New("the 'sbom_length' symbol overflows the binary")
|
||||
return nil, nil, errors.New("the 'sbom_length' symbol overflows the binary")
|
||||
}
|
||||
|
||||
length := dataBuf[lengthStart:lengthEnd]
|
||||
@ -154,39 +105,31 @@ func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg
|
||||
var storedLength uint64
|
||||
err := binary.Read(p, binary.LittleEndian, &storedLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read from binary file: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not read from binary file: %w", err)
|
||||
}
|
||||
|
||||
log.WithFields("len", storedLength).Trace("found java native-image SBOM")
|
||||
sbomEnd := sbomStart + storedLength
|
||||
if sbomEnd > uint64(bufLen) {
|
||||
return nil, errors.New("the sbom symbol overflows the binary")
|
||||
return nil, nil, errors.New("the sbom symbol overflows the binary")
|
||||
}
|
||||
|
||||
sbomCompressed := dataBuf[sbomStart:sbomEnd]
|
||||
p = bytes.NewBuffer(sbomCompressed)
|
||||
gzreader, err := gzip.NewReader(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decompress the java native-image SBOM: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not decompress the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
output, err := io.ReadAll(gzreader)
|
||||
sbom, _, _, err := cyclonedxjson.NewFormatDecoder().Decode(gzreader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read the java native-image SBOM: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not unmarshal the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
var sbomContent nativeImageCycloneDX
|
||||
err = json.Unmarshal(output, &sbomContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
for _, component := range sbomContent.Components {
|
||||
p := getPackage(component)
|
||||
var pkgs []pkg.Package
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, sbom.Relationships, nil
|
||||
}
|
||||
|
||||
// fileError logs an error message when an executable cannot be read.
|
||||
@ -294,7 +237,7 @@ func newPE(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages given in the binary.
|
||||
func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@ -310,10 +253,10 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
|
||||
si, err := bi.Symbols()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no symbols found in binary: %w", err)
|
||||
return nil, nil, fmt.Errorf("no symbols found in binary: %w", err)
|
||||
}
|
||||
if si == nil {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
for _, s := range si {
|
||||
switch s.Name {
|
||||
@ -326,16 +269,16 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
}
|
||||
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
dataSection := bi.Section(".data")
|
||||
if dataSection == nil {
|
||||
return nil, fmt.Errorf("no .data section found in binary: %w", err)
|
||||
return nil, nil, fmt.Errorf("no .data section found in binary: %w", err)
|
||||
}
|
||||
dataSectionBase := dataSection.SectionHeader.Addr
|
||||
data, err := dataSection.Data()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read the .data section: %w", err)
|
||||
return nil, nil, fmt.Errorf("cannot read the .data section: %w", err)
|
||||
}
|
||||
sbomLocation := sbom.Value - dataSectionBase
|
||||
lengthLocation := sbomLength.Value - dataSectionBase
|
||||
@ -344,7 +287,7 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages from a Native Image given as a Mach O file.
|
||||
func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@ -359,7 +302,7 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
|
||||
bi := ni.file
|
||||
if bi.Symtab == nil {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
for _, s := range bi.Symtab.Syms {
|
||||
switch s.Name {
|
||||
@ -372,17 +315,17 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
}
|
||||
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
|
||||
dataSegment := bi.Segment("__DATA")
|
||||
if dataSegment == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
dataBuf, err := dataSegment.Data()
|
||||
if err != nil {
|
||||
log.Tracef("cannot obtain buffer from data segment")
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
sbomLocation := sbom.Value - dataSegment.Addr
|
||||
lengthLocation := sbomLength.Value - dataSegment.Addr
|
||||
@ -494,7 +437,7 @@ func (ni nativeImagePE) fetchSbomSymbols(content *exportContentPE) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages from a Native Image given as a PE file.
|
||||
func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@ -506,32 +449,32 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
content, err := ni.fetchExportContent()
|
||||
if err != nil {
|
||||
log.Debugf("could not fetch the content of the export directory entry: %v", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
ni.fetchSbomSymbols(content)
|
||||
if content.addressOfSbom == uint32(0) || content.addressOfSbomLength == uint32(0) || content.addressOfSvmVersion == uint32(0) {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
functionsBase := content.addressOfFunctions - ni.exportSymbols.VirtualAddress
|
||||
sbomOffset := content.addressOfSbom
|
||||
sbomAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomOffset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch SBOM pointer from exported functions: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not fetch SBOM pointer from exported functions: %w", err)
|
||||
}
|
||||
sbomLengthOffset := content.addressOfSbomLength
|
||||
sbomLengthAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomLengthOffset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch SBOM length pointer from exported functions: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not fetch SBOM length pointer from exported functions: %w", err)
|
||||
}
|
||||
bi := ni.file
|
||||
dataSection := bi.Section(".data")
|
||||
if dataSection == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
dataBuf, err := dataSection.Data()
|
||||
if err != nil {
|
||||
log.Tracef("cannot obtain buffer from the java native-image .data section")
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
sbomLocation := sbomAddress - dataSection.VirtualAddress
|
||||
lengthLocation := sbomLengthAddress - dataSection.VirtualAddress
|
||||
@ -540,8 +483,9 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
|
||||
// fetchPkgs provides the packages available in a UnionReader.
|
||||
func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package, []artifact.Relationship) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
imageFormats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
|
||||
|
||||
// NOTE: multiple readers are returned to cover universal binaries, which are files
|
||||
@ -549,7 +493,7 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
readers, err := unionreader.GetReaders(reader)
|
||||
if err != nil {
|
||||
log.Debugf("failed to open the java native-image binary: %v", err)
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
for _, r := range readers {
|
||||
for _, makeNativeImage := range imageFormats {
|
||||
@ -560,47 +504,51 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
if ni == nil {
|
||||
continue
|
||||
}
|
||||
newPkgs, err := ni.fetchPkgs()
|
||||
newPkgs, newRelationships, err := ni.fetchPkgs()
|
||||
if err != nil {
|
||||
log.Tracef("unable to extract SBOM from possible java native-image %s: %v", filename, err)
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
relationships = append(relationships, newRelationships...)
|
||||
}
|
||||
}
|
||||
return pkgs
|
||||
return pkgs, relationships
|
||||
}
|
||||
|
||||
// Catalog attempts to find any native image executables reachable from a resolver.
|
||||
func (c *nativeImageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
fileMatches, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
||||
if err != nil {
|
||||
return pkgs, nil, fmt.Errorf("failed to find binaries by mime types: %w", err)
|
||||
}
|
||||
|
||||
for _, location := range fileMatches {
|
||||
newPkgs, err := processLocation(location, resolver)
|
||||
newPkgs, newRelationships, err := processLocation(location, resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
relationships = append(relationships, newRelationships...)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Package, error) {
|
||||
func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
readerCloser, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
log.Debugf("error opening file: %v", err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
defer internal.CloseAndLogError(readerCloser, location.RealPath)
|
||||
|
||||
reader, err := unionreader.GetUnionReader(readerCloser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return fetchPkgs(reader, location.RealPath), nil
|
||||
pkgs, relationships := fetchPkgs(reader, location.RealPath)
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@ -40,10 +42,11 @@ func TestParseNativeImage(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
parsed := false
|
||||
readers, err := unionreader.GetReaders(reader)
|
||||
assert.NoError(t, err)
|
||||
for _, r := range readers {
|
||||
ni, err := test.newFn(test.fixture, r)
|
||||
assert.NoError(t, err)
|
||||
_, err = ni.fetchPkgs()
|
||||
_, _, err = ni.fetchPkgs()
|
||||
if err == nil {
|
||||
t.Fatalf("should have failed to extract SBOM.")
|
||||
}
|
||||
@ -60,25 +63,25 @@ func TestParseNativeImage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseNativeImageSbom(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
}{
|
||||
const (
|
||||
nettyPurl = "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final"
|
||||
micronautPurl = "pkg:maven/io.micronaut/core@4.2.3"
|
||||
mainAppPurl = "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT"
|
||||
)
|
||||
|
||||
mainAppPkg := makePackage("main-test-app", "1.0-SNAPSHOT", mainAppPurl, []cpe.CPE{
|
||||
{
|
||||
fixture: "test-fixtures/graalvm-sbom/micronaut.json",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "netty-codec-http2",
|
||||
Version: "4.1.73.Final",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.GraalVMNativeImagePkg,
|
||||
FoundBy: nativeImageCatalogerName,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.netty",
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "app",
|
||||
Product: "main-test-app",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
CPEs: []cpe.CPE{
|
||||
})
|
||||
|
||||
nettyPkg := makePackage("netty-codec-http2", "4.1.73.Final", nettyPurl, []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
@ -106,31 +109,159 @@ func TestParseNativeImageSbom(t *testing.T) {
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
})
|
||||
|
||||
micronautPkg := makePackage("core", "4.2.3", micronautPurl, []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "core",
|
||||
Product: "core",
|
||||
Version: "4.2.3",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "micronaut",
|
||||
Product: "core",
|
||||
Version: "4.2.3",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
})
|
||||
|
||||
basicPkg := makePackage("basic-lib", "1.0", "", nil)
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expectedPackages []pkg.Package
|
||||
expectedRelations []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/graalvm-sbom/micronaut.json",
|
||||
expectedPackages: []pkg.Package{nettyPkg, micronautPkg, basicPkg, mainAppPkg},
|
||||
expectedRelations: []artifact.Relationship{
|
||||
{
|
||||
From: nettyPkg,
|
||||
To: micronautPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(path.Base(test.fixture), func(t *testing.T) {
|
||||
// Create a buffer to resemble a compressed SBOM in a native image.
|
||||
sbom, err := os.ReadFile(test.fixture)
|
||||
assert.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
writebytes := bufio.NewWriter(&b)
|
||||
z := gzip.NewWriter(writebytes)
|
||||
_, err = z.Write(sbom)
|
||||
assert.NoError(t, err)
|
||||
_ = z.Close()
|
||||
_ = writebytes.Flush()
|
||||
compressedsbom := b.Bytes()
|
||||
sbomlength := uint64(len(compressedsbom))
|
||||
_ = binary.Write(writebytes, binary.LittleEndian, sbomlength)
|
||||
_ = writebytes.Flush()
|
||||
compressedsbom = b.Bytes()
|
||||
actual, err := decompressSbom(compressedsbom, 0, sbomlength)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
compressed, length := createCompressedSbom(t, test.fixture)
|
||||
actualPkgs, actualRels, err := decompressSbom(compressed, 0, length)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
verifyPackages(t, test.expectedPackages, actualPkgs)
|
||||
verifyRelationships(t, test.expectedRelations, actualRels)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// makePackage makes a package using the data that we know must be in the parsed package
|
||||
func makePackage(name, version, purl string, cpes []cpe.CPE) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
CPEs: cpes,
|
||||
PURL: purl,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// createCompressedSbom creates a compressed a buffer to resemble a compressed SBOM in a native image.
|
||||
func createCompressedSbom(t *testing.T, filename string) ([]byte, uint64) {
|
||||
sbom, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
w := bufio.NewWriter(&b)
|
||||
z := gzip.NewWriter(w)
|
||||
|
||||
if _, err := z.Write(sbom); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z.Close()
|
||||
w.Flush()
|
||||
|
||||
compressedSbom := b.Bytes()
|
||||
sbomLength := uint64(len(compressedSbom))
|
||||
|
||||
if err := binary.Write(w, binary.LittleEndian, sbomLength); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return b.Bytes(), sbomLength
|
||||
}
|
||||
|
||||
func verifyPackages(t *testing.T, expected, actual []pkg.Package) {
|
||||
expectedPkgMap := buildPackageMap(expected)
|
||||
actualPkgMap := buildPackageMap(actual)
|
||||
|
||||
for name, expectedPkg := range expectedPkgMap {
|
||||
actualPkg, exists := actualPkgMap[name]
|
||||
if !exists {
|
||||
t.Errorf("Expected package %s not found in actual packages", name)
|
||||
continue
|
||||
}
|
||||
verifyPackageFields(t, expectedPkg, actualPkg)
|
||||
}
|
||||
}
|
||||
|
||||
func buildPackageMap(packages []pkg.Package) map[string]pkg.Package {
|
||||
pkgMap := make(map[string]pkg.Package)
|
||||
for _, p := range packages {
|
||||
pkgMap[p.Name] = p
|
||||
}
|
||||
return pkgMap
|
||||
}
|
||||
|
||||
func verifyPackageFields(t *testing.T, expected, actual pkg.Package) {
|
||||
assert.Equal(t, expected.Name, actual.Name)
|
||||
assert.Equal(t, expected.Version, actual.Version)
|
||||
assert.Equal(t, expected.FoundBy, actual.FoundBy)
|
||||
assert.Equal(t, expected.PURL, actual.PURL)
|
||||
assert.ElementsMatch(t, expected.CPEs, actual.CPEs)
|
||||
}
|
||||
|
||||
func verifyRelationships(t *testing.T, expected, actual []artifact.Relationship) {
|
||||
expectedRelMap := buildRelationshipMap(expected)
|
||||
actualRelMap := buildRelationshipMap(actual)
|
||||
|
||||
for key, expectedRels := range expectedRelMap {
|
||||
actualRels, exists := actualRelMap[key]
|
||||
if !exists {
|
||||
t.Errorf("Expected relationship %s not found in actual relationships", key)
|
||||
continue
|
||||
}
|
||||
verifyRelationshipFields(t, expectedRels, actualRels)
|
||||
}
|
||||
}
|
||||
|
||||
func buildRelationshipMap(relationships []artifact.Relationship) map[string][]artifact.Relationship {
|
||||
relMap := make(map[string][]artifact.Relationship)
|
||||
for _, rel := range relationships {
|
||||
// we cannot control the id, so use the names instead
|
||||
key := fmt.Sprintf("%s->%s", rel.From.(pkg.Package).Name, rel.To.(pkg.Package).Name)
|
||||
relMap[key] = append(relMap[key], rel)
|
||||
}
|
||||
return relMap
|
||||
}
|
||||
|
||||
func verifyRelationshipFields(t *testing.T, expected, actual []artifact.Relationship) {
|
||||
assert.Equal(t, len(expected), len(actual))
|
||||
for i := range expected {
|
||||
assert.Equal(t, expected[i].Type, actual[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,32 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad",
|
||||
"metadata": {
|
||||
"component": {
|
||||
"type": "library",
|
||||
"group": "com.test",
|
||||
"name": "main-test-app",
|
||||
"version": "1.0-SNAPSHOT",
|
||||
"purl": "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT",
|
||||
"bom-ref": "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:app:main-test-app:1.0-SNAPSHOT:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.netty",
|
||||
"name": "netty-codec-http2",
|
||||
"version": "4.1.73.Final",
|
||||
"purl": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
|
||||
"bom-ref": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
@ -22,7 +41,38 @@
|
||||
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.micronaut",
|
||||
"name": "core",
|
||||
"version": "4.2.3",
|
||||
"purl": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"bom-ref": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:core:core:4.2.3:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:micronaut:core:4.2.3:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"group": "org.example",
|
||||
"name": "basic-lib",
|
||||
"version": "1.0"
|
||||
}
|
||||
],
|
||||
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad"
|
||||
"dependencies": [
|
||||
{
|
||||
"ref": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"dependsOn": [
|
||||
"pkg:maven/io.netty/netty-codec-http2@4.1.104.Final"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user