mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
prototype: start bitnami cataloger
Bitnami images have spdx SBOMs at predictable paths, and Syft could more accurately identify the software in these images by scanning those SBOMs. Start work on this by forking the sbom-cataloger as a new bitnami-cataloger. Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
This commit is contained in:
parent
ccbee94b87
commit
6a33b80048
@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/alpine"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/arch"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
bitnamiSbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/bitnami"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dart"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/debian"
|
||||
@ -150,6 +151,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
|
||||
),
|
||||
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
|
||||
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag),
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
}
|
||||
}
|
||||
|
||||
83
syft/pkg/cataloger/bitnami/cataloger.go
Normal file
83
syft/pkg/cataloger/bitnami/cataloger.go
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
Package bitnami provides a concrete Cataloger implementation for capturing packages embedded within Bitnami SBOM files.
|
||||
*/
|
||||
package bitnami
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
const catalogerName = "bitnami-cataloger"
|
||||
|
||||
// NewCataloger returns a new SBOM cataloger object loaded from saved SBOM JSON.
|
||||
func NewCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger(catalogerName).
|
||||
WithParserByGlobs(parseSBOM,
|
||||
"/opt/bitnami/**/*.spdx",
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: this is copied from the sbom-cataloger
|
||||
// it should probably be slimmed down so as not to duplicate
|
||||
// parts of the SBOM cataloger that it doesn't need.
|
||||
func parseSBOM(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
readSeeker, err := adaptToReadSeeker(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read SBOM file %q: %w", reader.Location.RealPath, err)
|
||||
}
|
||||
s, _, _, err := format.Decode(readSeeker)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
log.WithFields("path", reader.Location.RealPath).Trace("file is not an SBOM")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
relationships := s.Relationships
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
// replace all locations on the package with the location of the SBOM file.
|
||||
// Why not keep the original list of locations? Since the "locations" field is meant to capture
|
||||
// where there is evidence of this file, and the catalogers have not run against any file other than,
|
||||
// the SBOM, this is the only location that is relevant for this cataloger.
|
||||
p.Locations = file.NewLocationSet(
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
p.FoundBy = catalogerName
|
||||
|
||||
pkgs = append(pkgs, p)
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: p,
|
||||
To: reader.Location.Coordinates,
|
||||
Type: artifact.DescribedByRelationship,
|
||||
})
|
||||
}
|
||||
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
func adaptToReadSeeker(reader io.Reader) (io.ReadSeeker, error) {
|
||||
// with the stereoscope API and default file.Resolver implementation here in syft, odds are very high that
|
||||
// the underlying reader is already a ReadSeeker, so we can just return it as-is. We still want to
|
||||
if rs, ok := reader.(io.ReadSeeker); ok {
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
log.Debug("SBOM cataloger reader is not a ReadSeeker, reading entire SBOM into memory")
|
||||
|
||||
var buff bytes.Buffer
|
||||
_, err := io.Copy(&buff, reader)
|
||||
return bytes.NewReader(buff.Bytes()), err
|
||||
}
|
||||
41
syft/pkg/cataloger/bitnami/cataloger_test.go
Normal file
41
syft/pkg/cataloger/bitnami/cataloger_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package bitnami
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBitnamiCataloger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPkgs []pkg.Package
|
||||
wantError require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "simple-redis-sbom",
|
||||
fixture: "test-fixtures",
|
||||
// TODO: add package assertions
|
||||
},
|
||||
// TODO: add another test case or too, maybe an error case
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, tt.fixture).
|
||||
ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
for i, p := range pkgs {
|
||||
assert.Equal(t, p.Name, tt.wantPkgs[i].Name)
|
||||
assert.Equal(t, p.Version, tt.wantPkgs[i].Version)
|
||||
}
|
||||
}).
|
||||
TestCataloger(t, NewCataloger())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-redis",
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"creationInfo": {
|
||||
"created": "2024-08-08T11:12:35.680Z",
|
||||
"creators": [
|
||||
"Organization: VMware, Inc."
|
||||
]
|
||||
},
|
||||
"name": "SPDX document for Redis(R) 7.4.0",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-redis"
|
||||
],
|
||||
"documentNamespace": "redis-7.4.0",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-redis",
|
||||
"name": "redis",
|
||||
"versionInfo": "7.4.0-0",
|
||||
"downloadLocation": "http://download.redis.io/releases/redis-7.4.0.tar.gz",
|
||||
"licenseConcluded": "RSALv2",
|
||||
"licenseDeclared": "RSALv2",
|
||||
"filesAnalyzed": false,
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:redis:redis:7.4.0:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:bitnami/redis@7.4.0-0?arch=arm64&distro=debian-12"
|
||||
}
|
||||
],
|
||||
"copyrightText": "NOASSERTION"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"relationships": []
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user