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:
Will Murphy 2024-10-08 09:31:33 -04:00
parent ccbee94b87
commit 6a33b80048
4 changed files with 168 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/alpine" "github.com/anchore/syft/syft/pkg/cataloger/alpine"
"github.com/anchore/syft/syft/pkg/cataloger/arch" "github.com/anchore/syft/syft/pkg/cataloger/arch"
"github.com/anchore/syft/syft/pkg/cataloger/binary" "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/cpp"
"github.com/anchore/syft/syft/pkg/cataloger/dart" "github.com/anchore/syft/syft/pkg/cataloger/dart"
"github.com/anchore/syft/syft/pkg/cataloger/debian" "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", pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
), ),
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag),
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"), newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
} }
} }

View 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
}

View 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())
})
}
}

View File

@ -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": []
}