feat: add support for Bitnami cataloguer (#3341)

* 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>

* wire up bitnami cataloger to run on images by default

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* feat: add support for Bitnami cataloguer

Signed-off-by: juan131 <jariza@vmware.com>

* feat: use a better SPDX sample for unit tests

Signed-off-by: juan131 <jariza@vmware.com>

* bugfix: only report bitnami pkgs

Signed-off-by: juan131 <jariza@vmware.com>

* feat: adapt JSON schema, spdxutil and packagemetadata

Signed-off-by: juan131 <jariza@vmware.com>

* bugfix: integration tests

Signed-off-by: juan131 <jariza@vmware.com>

* feat: implement FileOwner interface

Signed-off-by: juan131 <jariza@vmware.com>

* bugfix: update json schema

Signed-off-by: juan131 <jariza@vmware.com>

* [wip] add bitnami owned files and fix binary package ownership filtering

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* feat: obtain bitnami pkg files based on SPDX relationships tree

Signed-off-by: juan131 <jariza@vmware.com>

* preserve type switches

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename bitnami entry metadata type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* restrict find main pkg logic

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add missing graalvm source info

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bugfix: integration tests

Signed-off-by: juan131 <jariza@vmware.com>

* bugfix: mod tidy

Signed-off-by: juan131 <jariza@vmware.com>

---------

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
Signed-off-by: juan131 <jariza@vmware.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Will Murphy <willmurphyscode@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Juan Ariza Toledano 2025-02-18 15:07:47 +01:00 committed by GitHub
parent 869908ece1
commit bffe26bcc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 4674 additions and 60 deletions

View File

@ -9,3 +9,7 @@ A cataloger contributed by Oracle Corporation that extracts packages given withi
## Swift Package Manager ## Swift Package Manager
A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager. A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager.
## Bitnami Packages
A cataloger contributed by Bitnami developer that catalogs packages described in Bitnami SBOMs.

View File

@ -123,6 +123,7 @@ Note that flags using the @<version> can be used for earlier versions of each sp
### Supported Ecosystems ### Supported Ecosystems
- Alpine (apk) - Alpine (apk)
- Bitnami packages
- C (conan) - C (conan)
- C++ (conan) - C++ (conan)
- Dart (pubs) - Dart (pubs)

View File

@ -74,6 +74,8 @@ func TestPkgCoverageImage(t *testing.T) {
definedPkgs.Remove(string(pkg.ConanPkg)) definedPkgs.Remove(string(pkg.ConanPkg))
definedPkgs.Remove(string(pkg.HackagePkg)) definedPkgs.Remove(string(pkg.HackagePkg))
definedPkgs.Remove(string(pkg.BinaryPkg)) definedPkgs.Remove(string(pkg.BinaryPkg))
definedPkgs.Remove(string(pkg.BitnamiPkg))
definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
definedPkgs.Remove(string(pkg.HexPkg)) definedPkgs.Remove(string(pkg.HexPkg))
definedPkgs.Remove(string(pkg.LinuxKernelPkg)) definedPkgs.Remove(string(pkg.LinuxKernelPkg))
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
@ -219,6 +221,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
definedLanguages.Remove(pkg.R.String()) definedLanguages.Remove(pkg.R.String())
observedPkgs.Remove(string(pkg.UnknownPkg)) observedPkgs.Remove(string(pkg.UnknownPkg))
definedPkgs.Remove(string(pkg.BinaryPkg)) definedPkgs.Remove(string(pkg.BinaryPkg))
definedPkgs.Remove(string(pkg.BitnamiPkg))
definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
definedPkgs.Remove(string(pkg.LinuxKernelPkg)) definedPkgs.Remove(string(pkg.LinuxKernelPkg))
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg)) definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
definedPkgs.Remove(string(pkg.Rpkg)) definedPkgs.Remove(string(pkg.Rpkg))

1
go.mod
View File

@ -90,6 +90,7 @@ require (
github.com/OneOfOne/xxhash v1.2.8 github.com/OneOfOne/xxhash v1.2.8
github.com/adrg/xdg v0.5.3 github.com/adrg/xdg v0.5.3
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
github.com/hashicorp/hcl/v2 v2.23.0 github.com/hashicorp/hcl/v2 v2.23.0
github.com/magiconair/properties v1.8.9 github.com/magiconair/properties v1.8.9
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56

2
go.sum
View File

@ -149,6 +149,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww=
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=

View File

@ -3,5 +3,5 @@ package internal
const ( const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.21" JSONSchemaVersion = "16.0.22"
) )

View File

@ -58,11 +58,10 @@ func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, i
// PackagesToRemove returns a list of binary packages (resolved by the ELF cataloger) that should be removed from the SBOM // PackagesToRemove returns a list of binary packages (resolved by the ELF cataloger) that should be removed from the SBOM
// These packages are removed because they are already represented by a higher order packages in the SBOM. // These packages are removed because they are already represented by a higher order packages in the SBOM.
func PackagesToRemove(resolver file.Resolver, accessor sbomsync.Accessor) []artifact.ID { func PackagesToRemove(accessor sbomsync.Accessor) []artifact.ID {
pkgsToDelete := make([]artifact.ID, 0) pkgsToDelete := make([]artifact.ID, 0)
accessor.ReadFromSBOM(func(s *sbom.SBOM) { accessor.ReadFromSBOM(func(s *sbom.SBOM) {
// OTHER package type > ELF package type > Binary package type // ELF package type > Binary package type
pkgsToDelete = append(pkgsToDelete, getBinaryPackagesToDelete(resolver, s)...)
pkgsToDelete = append(pkgsToDelete, compareElfBinaryPackages(s)...) pkgsToDelete = append(pkgsToDelete, compareElfBinaryPackages(s)...)
}) })
return pkgsToDelete return pkgsToDelete
@ -114,33 +113,6 @@ func isElfPackage(p pkg.Package) bool {
return ok return ok
} }
func getBinaryPackagesToDelete(resolver file.Resolver, s *sbom.SBOM) []artifact.ID {
pkgsToDelete := make([]artifact.ID, 0)
for p := range s.Artifacts.Packages.Enumerate() {
if p.Type == pkg.BinaryPkg {
continue
}
fileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
continue
}
ownedFiles := fileOwner.OwnedFiles()
locations, err := resolver.FilesByPath(ownedFiles...)
if err != nil {
log.WithFields("error", err).Trace("unable to find path for owned file")
continue
}
for _, loc := range locations {
for _, pathPkg := range s.Artifacts.Packages.PackagesByPath(loc.RealPath) {
if pathPkg.Type == pkg.BinaryPkg {
pkgsToDelete = append(pkgsToDelete, pathPkg.ID())
}
}
}
}
return pkgsToDelete
}
func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, addRelationship func(artifact.Relationship), index *sharedLibraryIndex) { func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, addRelationship func(artifact.Relationship), index *sharedLibraryIndex) {
for _, libReference := range exec.ImportedLibraries { for _, libReference := range exec.ImportedLibraries {
// for each library reference, check s.Artifacts.Packages.Sorted(pkg.BinaryPkg) for a binary package that represents that library // for each library reference, check s.Artifacts.Packages.Sorted(pkg.BinaryPkg) for a binary package that represents that library

View File

@ -21,7 +21,7 @@ func TestPackagesToRemove(t *testing.T) {
Name: "glibc", Name: "glibc",
Version: "2.28-236.el8_9.12", Version: "2.28-236.el8_9.12",
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewLocation(glibcCoordinate.RealPath), file.NewLocation("path/to/rpmdb"),
), ),
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
Metadata: pkg.RpmDBEntry{ Metadata: pkg.RpmDBEntry{
@ -88,50 +88,45 @@ func TestPackagesToRemove(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
resolver file.Resolver
accessor sbomsync.Accessor accessor sbomsync.Accessor
want []artifact.ID want []artifact.ID
}{ }{
{ {
name: "remove packages that are overlapping rpm --> binary", name: "remove packages that are overlapping rpm --> binary",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackage}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackage}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{glibCBinaryELFPackage.ID()}, // this is surprising, right? the calling function reasons about if any generic binary package (regardless of it being an ELF package or not)
// should be deleted or kept based on the user configuration to do so.
want: []artifact.ID{},
}, },
{ {
name: "keep packages that are overlapping rpm --> binary when the binary self identifies as an RPM", name: "keep packages that are overlapping rpm --> binary when the binary self identifies as an RPM",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{}, want: []artifact.ID{},
}, },
{ {
name: "remove no packages when there is a single binary package (or self identifying RPM)", name: "remove no packages when there is a single binary package (or self identifying RPM)",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{}, want: []artifact.ID{},
}, },
{ {
name: "remove packages when there is a single binary package and a classifier package", name: "remove packages when there is a single binary package and a classifier package",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{glibCBinaryClassifierPackage.ID()}, want: []artifact.ID{glibCBinaryClassifierPackage.ID()},
}, },
{ {
name: "ensure we're considering ELF packages, not just binary packages (supporting evidence)", name: "ensure we're considering ELF packages, not just binary packages (supporting evidence)",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{}, want: []artifact.ID{},
}, },
{ {
name: "ensure we're considering ELF packages, not just binary packages (primary evidence)", name: "ensure we're considering ELF packages, not just binary packages (primary evidence)",
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
accessor: newAccessor([]pkg.Package{libCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil), accessor: newAccessor([]pkg.Package{libCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
want: []artifact.ID{}, want: []artifact.ID{},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
pkgsToDelete := PackagesToRemove(tt.resolver, tt.accessor) pkgsToDelete := PackagesToRemove(tt.accessor)
if diff := cmp.Diff(tt.want, pkgsToDelete); diff != "" { if diff := cmp.Diff(tt.want, pkgsToDelete); diff != "" {
t.Errorf("unexpected packages to delete (-want, +got): %s", diff) t.Errorf("unexpected packages to delete (-want, +got): %s", diff)
} }

View File

@ -22,6 +22,9 @@ var (
binaryCatalogerTypes = []pkg.Type{ binaryCatalogerTypes = []pkg.Type{
pkg.BinaryPkg, pkg.BinaryPkg,
} }
bitnamiCatalogerTypes = []pkg.Type{
pkg.BitnamiPkg,
}
binaryMetadataTypes = []string{ binaryMetadataTypes = []string{
reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(), reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(),
reflect.TypeOf(pkg.BinarySignature{}).Name(), reflect.TypeOf(pkg.BinarySignature{}).Name(),
@ -65,6 +68,10 @@ func excludeByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collection) a
return idToRemove return idToRemove
} }
if idToRemove := identifyOverlappingBitnamiRelationship(parent, child); idToRemove != "" {
return idToRemove
}
return "" return ""
} }
@ -124,3 +131,24 @@ func identifyOverlappingOSRelationship(parent *pkg.Package, child *pkg.Package)
return child.ID() return child.ID()
} }
// identifyOverlappingBitnamiRelationship indicates the package ID to remove if this is a Bitnami pkg -> bin pkg relationship.
func identifyOverlappingBitnamiRelationship(parent *pkg.Package, child *pkg.Package) artifact.ID {
if !slices.Contains(bitnamiCatalogerTypes, parent.Type) {
return ""
}
if slices.Contains(binaryCatalogerTypes, child.Type) {
return child.ID()
}
if child.Metadata == nil {
return ""
}
if !slices.Contains(binaryMetadataTypes, reflect.TypeOf(child.Metadata).Name()) {
return ""
}
return child.ID()
}

View File

@ -13,7 +13,8 @@ func TestExcludeByFileOwnershipOverlap(t *testing.T) {
packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg} packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg}
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg, Metadata: pkg.JavaVMInstallation{}} packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg, Metadata: pkg.JavaVMInstallation{}}
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}} packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}}
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC} { packageD := pkg.Package{Name: "package-d", Type: pkg.BitnamiPkg}
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} {
p := p p := p
p.SetID() p.SetID()
} }
@ -46,6 +47,17 @@ func TestExcludeByFileOwnershipOverlap(t *testing.T) {
packages: pkg.NewCollection(packageC, packageB), packages: pkg.NewCollection(packageC, packageB),
shouldExclude: true, shouldExclude: true,
}, },
{
// prove that Bitnami -> bin exclusions are wired
name: "exclusions from bitnami -> binary",
relationship: artifact.Relationship{
Type: artifact.OwnershipByFileOverlapRelationship,
From: packageD, // Bitnami
To: packageC, // ELF binary
},
packages: pkg.NewCollection(packageD, packageC),
shouldExclude: true,
},
} }
for _, test := range tests { for _, test := range tests {
@ -117,6 +129,63 @@ func TestIdentifyOverlappingOSRelationship(t *testing.T) {
} }
} }
func TestIdentifyOverlappingBitnamiRelationship(t *testing.T) {
packageA := pkg.Package{Name: "package-a", Type: pkg.BitnamiPkg} // Bitnami package
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg}
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.BinarySignature{}}
packageD := pkg.Package{Name: "package-d", Type: pkg.PythonPkg} // Language package
packageE := pkg.Package{Name: "package-e", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{}}
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE} {
p.SetID()
}
tests := []struct {
name string
parent *pkg.Package
child *pkg.Package
expectedID artifact.ID
}{
{
name: "Bitnami -> binary without metadata",
parent: &packageA,
child: &packageB,
expectedID: packageB.ID(), // Bitnami package to binary package, should return child ID
},
{
name: "Bitnami -> binary with binary metadata",
parent: &packageA,
child: &packageC,
expectedID: packageC.ID(), // Bitnami package to binary package with binary metadata, should return child ID
},
{
name: "Bitnami -> non-binary package",
parent: &packageA,
child: &packageD,
expectedID: "", // Bitnami package to non-binary package, no exclusion
},
{
name: "Bitnami -> binary with ELF metadata",
parent: &packageA,
child: &packageE,
expectedID: packageE.ID(), // Bitnami package to binary package with ELF metadata, should return child ID
},
{
name: "non-Bitnami parent",
parent: &packageD, // non-Bitnami package
child: &packageC,
expectedID: "", // non-Bitnami parent, no exclusion
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resultID := identifyOverlappingBitnamiRelationship(tt.parent, tt.child)
assert.Equal(t, tt.expectedID, resultID)
})
}
}
func TestIdentifyOverlappingJVMRelationship(t *testing.T) { func TestIdentifyOverlappingJVMRelationship(t *testing.T) {
packageA := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg} packageA := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg}

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"
@ -152,6 +153,7 @@ func DefaultPackageTaskFactories() Factories {
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, pkgcataloging.ImageTag),
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"), newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"), newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"),
} }

View File

@ -42,7 +42,7 @@ func finalizeRelationships(resolver file.Resolver, builder sbomsync.Builder, cfg
// remove ELF packages and Binary packages that are already // remove ELF packages and Binary packages that are already
// represented by a source package (e.g. a package that is evident by some package manager) // represented by a source package (e.g. a package that is evident by some package manager)
builder.DeletePackages(binary.PackagesToRemove(resolver, accessor)...) builder.DeletePackages(binary.PackagesToRemove(accessor)...)
// add relationships showing packages that are evident by a file which is owned by another package (package-to-package) // add relationships showing packages that are evident by a file which is owned by another package (package-to-package)
if cfg.PackageFileOwnershipOverlap { if cfg.PackageFileOwnershipOverlap {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.21/document", "$id": "anchore.io/schema/syft/json/16.0.22/document",
"$ref": "#/$defs/Document", "$ref": "#/$defs/Document",
"$defs": { "$defs": {
"AlpmDbEntry": { "AlpmDbEntry": {
@ -218,6 +218,44 @@
"matches" "matches"
] ]
}, },
"BitnamiSbomEntry": {
"properties": {
"name": {
"type": "string"
},
"arch": {
"type": "string"
},
"distro": {
"type": "string"
},
"revision": {
"type": "string"
},
"version": {
"type": "string"
},
"path": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"name",
"arch",
"distro",
"revision",
"version",
"path",
"files"
]
},
"CConanFileEntry": { "CConanFileEntry": {
"properties": { "properties": {
"ref": { "ref": {
@ -1653,6 +1691,9 @@
{ {
"$ref": "#/$defs/BinarySignature" "$ref": "#/$defs/BinarySignature"
}, },
{
"$ref": "#/$defs/BitnamiSbomEntry"
},
{ {
"$ref": "#/$defs/CConanFileEntry" "$ref": "#/$defs/CConanFileEntry"
}, },

View File

@ -43,6 +43,10 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen
case pkg.ApkDBEntry: case pkg.ApkDBEntry:
author = metadata.Maintainer author = metadata.Maintainer
case pkg.BitnamiSBOMEntry:
typ = orgType
author = "Bitnami"
case pkg.DotnetPortableExecutableEntry: case pkg.DotnetPortableExecutableEntry:
typ = orgType typ = orgType
author = metadata.CompanyName author = metadata.CompanyName

View File

@ -12,6 +12,7 @@ import (
func Test_OriginatorSupplier(t *testing.T) { func Test_OriginatorSupplier(t *testing.T) {
completionTester := packagemetadata.NewCompletionTester(t, completionTester := packagemetadata.NewCompletionTester(t,
pkg.BinarySignature{}, pkg.BinarySignature{},
pkg.BitnamiSBOMEntry{},
pkg.CocoaPodfileLockEntry{}, pkg.CocoaPodfileLockEntry{},
pkg.ConanV1LockEntry{}, pkg.ConanV1LockEntry{},
pkg.ConanV2LockEntry{}, // the field Username might be the username of either the package originator or the supplier (unclear currently) pkg.ConanV2LockEntry{}, // the field Username might be the username of either the package originator or the supplier (unclear currently)
@ -89,6 +90,14 @@ func Test_OriginatorSupplier(t *testing.T) {
originator: "", originator: "",
supplier: "Person: someone", supplier: "Person: someone",
}, },
{
name: "from bitnami",
input: pkg.Package{
Metadata: pkg.BitnamiSBOMEntry{},
},
originator: "Organization: Bitnami",
supplier: "Organization: Bitnami",
},
{ {
name: "from dotnet -- PE binary", name: "from dotnet -- PE binary",
input: pkg.Package{ input: pkg.Package{

View File

@ -16,6 +16,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from RPM DB" answer = "acquired package info from RPM DB"
case pkg.ApkPkg: case pkg.ApkPkg:
answer = "acquired package info from APK DB" answer = "acquired package info from APK DB"
case pkg.BitnamiPkg:
answer = "acquired package info from a Bitnami SBOM"
case pkg.DartPubPkg: case pkg.DartPubPkg:
answer = "acquired package info from pubspec manifest" answer = "acquired package info from pubspec manifest"
case pkg.DebPkg: case pkg.DebPkg:
@ -32,6 +34,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from installed gem metadata file" answer = "acquired package info from installed gem metadata file"
case pkg.GoModulePkg: case pkg.GoModulePkg:
answer = "acquired package info from go module information" answer = "acquired package info from go module information"
case pkg.GraalVMNativeImagePkg:
answer = "acquired package info from GraalVM native image"
case pkg.RustPkg: case pkg.RustPkg:
answer = "acquired package info from rust cargo manifest" answer = "acquired package info from rust cargo manifest"
case pkg.PhpComposerPkg: case pkg.PhpComposerPkg:

View File

@ -111,6 +111,14 @@ func Test_SourceInfo(t *testing.T) {
"from go module information", "from go module information",
}, },
}, },
{
input: pkg.Package{
Type: pkg.GraalVMNativeImagePkg,
},
expected: []string{
"from GraalVM native image",
},
},
{ {
input: pkg.Package{ input: pkg.Package{
Type: pkg.RustPkg, Type: pkg.RustPkg,
@ -199,6 +207,14 @@ func Test_SourceInfo(t *testing.T) {
"acquired package info from the following paths", "acquired package info from the following paths",
}, },
}, },
{
input: pkg.Package{
Type: pkg.BitnamiPkg,
},
expected: []string{
"acquired package info from a Bitnami SBOM",
},
},
{ {
input: pkg.Package{ input: pkg.Package{
Type: pkg.HexPkg, Type: pkg.HexPkg,

View File

@ -10,6 +10,7 @@ func AllTypes() []any {
pkg.AlpmDBEntry{}, pkg.AlpmDBEntry{},
pkg.ApkDBEntry{}, pkg.ApkDBEntry{},
pkg.BinarySignature{}, pkg.BinarySignature{},
pkg.BitnamiSBOMEntry{},
pkg.CocoaPodfileLockEntry{}, pkg.CocoaPodfileLockEntry{},
pkg.ConanV1LockEntry{}, pkg.ConanV1LockEntry{},
pkg.ConanV2LockEntry{}, pkg.ConanV2LockEntry{},

View File

@ -65,6 +65,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.AlpmDBEntry{}, "alpm-db-entry", "AlpmMetadata"), jsonNames(pkg.AlpmDBEntry{}, "alpm-db-entry", "AlpmMetadata"),
jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"), jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"),
jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"), jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"),
jsonNames(pkg.BitnamiSBOMEntry{}, "bitnami-sbom-entry"),
jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"), jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"),
jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"), jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
jsonNames(pkg.ConanV2LockEntry{}, "c-conan-lock-v2-entry"), jsonNames(pkg.ConanV2LockEntry{}, "c-conan-lock-v2-entry"),

17
syft/pkg/bitnami.go Normal file
View File

@ -0,0 +1,17 @@
package pkg
// BitnamiSBOMEntry represents all captured data from Bitnami packages
// described in Bitnami' SPDX files.
type BitnamiSBOMEntry struct {
Name string `mapstructure:"name" json:"name"`
Architecture string `mapstructure:"arch" json:"arch"`
Distro string `mapstructure:"distro" json:"distro"`
Revision string `mapstructure:"revision" json:"revision"`
Version string `mapstructure:"version" json:"version"`
Path string `mapstructure:"path" json:"path"`
Files []string `mapstructure:"files" json:"files"`
}
func (b BitnamiSBOMEntry) OwnedFiles() (result []string) {
return b.Files
}

View File

@ -0,0 +1,173 @@
/*
Package bitnami provides a concrete Cataloger implementation for capturing packages embedded within Bitnami SBOM files.
*/
package bitnami
import (
"context"
"path/filepath"
"strings"
"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-*.spdx",
)
}
func parseSBOM(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
s, sFormat, _, err := format.Decode(reader)
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
}
// Bitnami exclusively uses SPDX JSON SBOMs
if sFormat != "spdx-json" {
log.WithFields("path", reader.Location.RealPath).Trace("file is not an SPDX JSON SBOM")
return nil, nil, nil
}
var pkgs []pkg.Package
var secondaryPkgsFiles []string
mainPkgID := findMainPkgID(s.Relationships)
for _, p := range s.Artifacts.Packages.Sorted() {
// We only want to report Bitnami packages
if !strings.HasPrefix(p.PURL, "pkg:bitnami") {
continue
}
p.FoundBy = catalogerName
p.Type = pkg.BitnamiPkg
// 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),
)
// Parse the Bitnami-specific metadata
metadata, err := parseBitnamiPURL(p.PURL)
if err != nil {
return nil, nil, err
}
// Bitnami packages reported in a SPDX file are shipped under the same directory
// as the SPDX file itself.
metadata.Path = filepath.Dir(reader.Location.RealPath)
if p.ID() != mainPkgID {
metadata.Files = packageFiles(s.Relationships, p, metadata.Path)
secondaryPkgsFiles = append(secondaryPkgsFiles, metadata.Files...)
}
p.Metadata = metadata
pkgs = append(pkgs, p)
}
// Resolve all files owned by the main package in the SBOM and update the metadata
if mainPkgFiles, err := mainPkgFiles(resolver, reader.Location.RealPath, secondaryPkgsFiles); err == nil {
for i, p := range pkgs {
if p.ID() == mainPkgID {
metadata, ok := p.Metadata.(*pkg.BitnamiSBOMEntry)
if !ok {
log.WithFields("spdx-filepath", reader.Location.RealPath).Trace("main package in SBOM does not have Bitnami metadata")
continue
}
metadata.Files = mainPkgFiles
pkgs[i].Metadata = metadata
}
}
} else {
log.WithFields("spdx-filepath", reader.Location.RealPath, "error", err).Trace("unable to resolve owned files for main package in SBOM")
}
return pkgs, filterRelationships(s.Relationships, pkgs), nil
}
// filterRelationships filters out relationships that are not related to Bitnami packages
// and replaces the package information with the one with completed info
func filterRelationships(relationships []artifact.Relationship, pkgs []pkg.Package) []artifact.Relationship {
var result []artifact.Relationship
for _, r := range relationships {
if value, ok := r.From.(pkg.Package); ok {
found := false
for _, p := range pkgs {
if value.PURL == p.PURL {
r.From = p
found = true
break
}
}
if !found {
continue
}
}
if value, ok := r.To.(pkg.Package); ok {
found := false
for _, p := range pkgs {
if value.PURL == p.PURL {
r.To = p
found = true
break
}
}
if !found {
continue
}
}
result = append(result, r)
}
return result
}
// findMainPkgID goes through the list of relationships and finds the main package ID
// which is the one that contains other packages but is not contained by any other package
func findMainPkgID(relationships []artifact.Relationship) artifact.ID {
containedByAnother := func(candidateID artifact.ID) bool {
for _, r := range relationships {
if r.Type != artifact.ContainsRelationship {
continue
}
if to, ok := r.To.(pkg.Package); ok {
if to.ID() == candidateID {
return true
}
}
}
return false
}
for _, r := range relationships {
if from, ok := r.From.(pkg.Package); ok {
if !strings.HasPrefix(from.PURL, "pkg:bitnami") {
continue
}
if !containedByAnother(from.ID()) {
return from.ID()
}
}
}
return ""
}

View File

@ -0,0 +1,470 @@
package bitnami
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func mustCPEs(s ...string) (c []cpe.CPE) {
for _, i := range s {
newCPE := cpe.Must(i, "")
newCPE.Source = cpe.DeclaredSource
c = append(c, newCPE)
}
return
}
func TestBitnamiCataloger(t *testing.T) {
postgresqlMainPkg := pkg.Package{
Name: "postgresql",
Version: "17.2.0-8",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("PostgreSQL", license.Concluded),
pkg.NewLicenseFromType("PostgreSQL", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/postgresql@17.2.0-8?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:postgresql:postgresql:17.2.0:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "postgresql",
Version: "17.2.0",
Revision: "8",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
Files: []string{
"opt/bitnami/postgresql/readme.txt",
},
},
}
postgresqlSecondaryPkgs := []pkg.Package{
{
Name: "geos",
Version: "3.13.0",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-2.1-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/geos@3.13.0?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:libgeos:geos:3.13.0:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "geos",
Version: "3.13.0",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "proj",
Version: "6.3.2",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/proj@6.3.2?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:proj:proj:6.3.2:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "proj",
Version: "6.3.2",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "gdal",
Version: "3.10.1",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/gdal@3.10.1?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:osgeo:gdal:3.10.1:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "gdal",
Version: "3.10.1",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "json-c",
Version: "0.16.20220414",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/json-c@0.16.20220414?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:json-c_project:json-c:0.16.20220414:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "json-c",
Version: "0.16.20220414",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "orafce",
Version: "4.14.1",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("0BSD", license.Concluded),
pkg.NewLicenseFromType("0BSD", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/orafce@4.14.1?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:orafce:orafce:4.14.1:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "orafce",
Version: "4.14.1",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "pljava",
Version: "1.6.8",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pljava@1.6.8?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:pl/java_project:pl/java:1.6.8:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "pljava",
Version: "1.6.8",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
Files: []string{
"opt/bitnami/postgresql/share/pljava/pljava-api-1.6.8.jar",
"opt/bitnami/postgresql/share/pljava/pljava-1.6.8.jar",
"opt/bitnami/postgresql/share/pljava/pljava-examples-1.6.8.jar",
},
},
},
{
Name: "unixodbc",
Version: "2.3.12",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-2.1-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/unixodbc@2.3.12?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:unixodbc:unixodbc:2.3.12:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "unixodbc",
Version: "2.3.12",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "psqlodbc",
Version: "16.0.0",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-3.0-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-3.0-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/psqlodbc@16.0.0?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:postgresql:psqlodbc:16.0.0:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "psqlodbc",
Version: "16.0.0",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "protobuf",
Version: "3.21.12",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/protobuf@3.21.12?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:golang:protobuf:3.21.12:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "protobuf",
Version: "3.21.12",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "protobuf-c",
Version: "1.5.1",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-2-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-2-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/protobuf-c@1.5.1?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:protobuf-c:protobuf-c:1.5.1:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "protobuf-c",
Version: "1.5.1",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "postgis",
Version: "3.4.4",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("GPL-2.0-or-later", license.Concluded),
pkg.NewLicenseFromType("GPL-2.0-or-later", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/postgis@3.4.4?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:postgis:postgis:3.4.4:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "postgis",
Version: "3.4.4",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "pgaudit",
Version: "17.0.0",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("PostgreSQL", license.Concluded),
pkg.NewLicenseFromType("PostgreSQL", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pgaudit@17.0.0?arch=arm64&distro=debian-12",
Metadata: &pkg.BitnamiSBOMEntry{
Name: "pgaudit",
Version: "17.0.0",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "pgbackrest",
Version: "2.54.2",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pgbackrest@2.54.2?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:pgbackrest:pgbackrest:2.54.2:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "pgbackrest",
Version: "2.54.2",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "wal2json",
Version: "2.6.0",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/wal2json@2.6.0?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:wal2json:wal2json:2.6.0:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "wal2json",
Version: "2.6.0",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
{
Name: "nss-wrapper",
Version: "1.1.16",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/nss_wrapper@1.1.16?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:nss_wrapper:nss_wrapper:1.1.16:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "nss_wrapper",
Version: "1.1.16",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/postgresql",
},
},
}
postgresqlExpectedPkgs := []pkg.Package{postgresqlMainPkg}
postgresqlExpectedPkgs = append(postgresqlExpectedPkgs, postgresqlSecondaryPkgs...)
pkg.Sort(postgresqlExpectedPkgs)
var postgresqlExpectedRelationships []artifact.Relationship
for _, p := range postgresqlSecondaryPkgs {
postgresqlExpectedRelationships = append(postgresqlExpectedRelationships, artifact.Relationship{
From: postgresqlMainPkg,
To: p,
Type: artifact.ContainsRelationship,
})
}
renderTemplateMainPkg := pkg.Package{
Name: "render-template",
Version: "1.0.7-4",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/render-template/.spdx-render-template.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("Apache-2.0", license.Concluded),
pkg.NewLicenseFromType("Apache-2.0", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/render-template@1.0.7-4?arch=arm64&distro=debian-12",
CPEs: mustCPEs(
"cpe:2.3:*:render-template:render-template:1.0.7:*:*:*:*:*:*:*",
),
Metadata: &pkg.BitnamiSBOMEntry{
Name: "render-template",
Version: "1.0.7",
Revision: "4",
Architecture: "arm64",
Distro: "debian-12",
Path: "opt/bitnami/render-template",
Files: []string{},
},
}
tests := []struct {
name string
fixture string
wantPkgs []pkg.Package
wantRelationships []artifact.Relationship
wantErr require.ErrorAssertionFunc
}{
{
name: "parse valid PostgreSQL SBOM",
fixture: "test-fixtures/json",
wantPkgs: postgresqlExpectedPkgs,
wantRelationships: postgresqlExpectedRelationships,
wantErr: require.NoError,
},
{
name: "parse valid SBOM that includes both Bitnami and non-Bitnami packages",
fixture: "test-fixtures/mix",
wantPkgs: []pkg.Package{renderTemplateMainPkg},
wantRelationships: nil,
wantErr: require.NoError,
},
{
name: "Redis SBOM with not allowed tag-value format",
fixture: "test-fixtures/tag-value",
wantPkgs: nil,
wantRelationships: nil,
wantErr: require.NoError,
},
{
name: "Invalid SBOM",
fixture: "test-fixtures/invalid",
wantPkgs: nil,
wantRelationships: nil,
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, tt.fixture).
Expects(tt.wantPkgs, tt.wantRelationships).
WithErrorAssertion(tt.wantErr).
TestCataloger(t, NewCataloger())
})
}
}

View File

@ -0,0 +1,92 @@
package bitnami
import (
"fmt"
"path"
"path/filepath"
"slices"
"strings"
version "github.com/bitnami/go-version/pkg/version"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func parseBitnamiPURL(p string) (*pkg.BitnamiSBOMEntry, error) {
purl, err := packageurl.FromString(p)
if err != nil {
return nil, err
}
v, err := version.Parse(purl.Version)
if err != nil {
return nil, err
}
entry := pkg.BitnamiSBOMEntry{
Name: purl.Name,
Version: strings.TrimSuffix(v.String(), fmt.Sprintf("-%s", v.Revision().String())),
Revision: v.Revision().String(),
}
for _, q := range purl.Qualifiers {
switch q.Key {
case "arch":
entry.Architecture = q.Value
case "distro":
entry.Distro = q.Value
}
}
return &entry, nil
}
// packageFiles goes through the list of relationships and finds the files that
// are owned by the given package
func packageFiles(relationships []artifact.Relationship, p pkg.Package, baseDirectory string) []string {
var result []string
for _, r := range relationships {
if r.Type != artifact.ContainsRelationship {
continue
}
if from, ok := r.From.(pkg.Package); ok {
if from.PURL == p.PURL {
if to, ok := r.To.(pkg.Package); ok {
result = append(result, packageFiles(relationships, to, baseDirectory)...)
}
if value, ok := r.To.(file.Location); ok {
// note: the file.Location is from the SBOM, and all files within the Bitnami SBOM by convention
// are relative to the /opt/bitnami/PRODUCT directory, so we need to prepend the base directory
// so that it's relative to the path found within the container image.
result = append(result, path.Join(baseDirectory, value.RealPath))
}
}
}
}
return result
}
// mainPkgFiles returns the files owned by the main package in the SPDX file.
func mainPkgFiles(resolver file.Resolver, spdxFilePath string, secondaryPkgsFiles []string) ([]string, error) {
ownedPathGlob := fmt.Sprintf("%s/**", filepath.Dir(spdxFilePath))
ownedLocations, err := resolver.FilesByGlob(ownedPathGlob)
if err != nil {
return nil, err
}
ownedLocationSet := file.NewLocationSet(ownedLocations...)
ownedFiles := ownedLocationSet.CoordinateSet().Paths()
// Remove the SPDX file and the files already assigned to other packages
// from the list of owned files
files := slices.DeleteFunc(ownedFiles, func(f string) bool {
return f == spdxFilePath || slices.Contains(secondaryPkgsFiles, f)
})
return files, nil
}

View File

@ -0,0 +1,57 @@
package bitnami
import (
"reflect"
"testing"
"github.com/anchore/syft/syft/pkg"
)
func Test_parseBitnamiPURL(t *testing.T) {
tests := []struct {
name string
purl string
want *pkg.BitnamiSBOMEntry
wantErr bool
}{
{
name: "Valid Bitnami pURL",
purl: "pkg:bitnami/redis@7.4.1-0?arch=arm64&distro=debian-12",
want: &pkg.BitnamiSBOMEntry{
Name: "redis",
Version: "7.4.1",
Revision: "0",
Architecture: "arm64",
Distro: "debian-12",
},
wantErr: false,
},
{
name: "Invalid pURL",
purl: "this/is/not/a/purl",
want: nil,
wantErr: true,
},
{
name: "Invalid version",
purl: "pkg:bitnami/redis@7.4.1.0?arch=arm64&distro=debian-12",
want: nil,
wantErr: true,
},
}
t.Parallel()
for _, testToRun := range tests {
test := testToRun
t.Run(test.name, func(tt *testing.T) {
tt.Parallel()
got, err := parseBitnamiPURL(test.purl)
if (err != nil) != test.wantErr {
tt.Errorf("parseBitnamiPURL() error = %v, wantErr %v", err, test.wantErr)
return
}
if !reflect.DeepEqual(got, test.want) {
tt.Errorf("parseBitnamiPURL() = %v, want %v", got, test.want)
}
})
}
}

View File

@ -0,0 +1,5 @@
{
"id": "invalid-id",
"name": "invalid-name",
"description: "This is not a SPDX file"
}

View File

@ -0,0 +1,573 @@
{
"SPDXID": "SPDXRef-postgresql",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2025-02-02T05:18:41.934Z",
"creators": [
"Organization: Broadcom Inc. and/or its subsidiaries."
]
},
"name": "SPDX document for PostgreSQL 17.2.0",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-postgresql"
],
"documentNamespace": "postgresql-17.2.0",
"packages": [
{
"SPDXID": "SPDXRef-postgresql",
"name": "postgresql",
"versionInfo": "17.2.0-8",
"downloadLocation": "https://ftp.postgresql.org/pub/source/v17.2/postgresql-17.2.tar.gz",
"licenseConcluded": "PostgreSQL",
"licenseDeclared": "PostgreSQL",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:postgresql:postgresql:17.2.0:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/postgresql@17.2.0-8?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-geos",
"name": "geos",
"versionInfo": "3.13.0",
"downloadLocation": "https://github.com/libgeos/geos/archive/3.13.0.tar.gz",
"licenseConcluded": "LGPL-2.1-only",
"licenseDeclared": "LGPL-2.1-only",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:libgeos:geos:3.13.0:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/geos@3.13.0?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-proj",
"name": "proj",
"versionInfo": "6.3.2",
"downloadLocation": "https://github.com/OSGeo/PROJ/archive/6.3.2.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:proj:proj:6.3.2:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/proj@6.3.2?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-gdal",
"name": "gdal",
"versionInfo": "3.10.1",
"downloadLocation": "https://github.com/OSGeo/gdal/releases/download/v3.10.1/gdal-3.10.1.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:osgeo:gdal:3.10.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/gdal@3.10.1?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-json-c",
"name": "json-c",
"versionInfo": "0.16.20220414",
"downloadLocation": "https://github.com/json-c/json-c/archive/refs/tags/json-c-0.16-20220414.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:json-c_project:json-c:0.16.20220414:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/json-c@0.16.20220414?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-orafce",
"name": "orafce",
"versionInfo": "4.14.1",
"downloadLocation": "https://github.com/orafce/orafce/archive/refs/tags/VERSION_4_14_1.tar.gz",
"licenseConcluded": "0BSD",
"licenseDeclared": "0BSD",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:orafce:orafce:4.14.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/orafce@4.14.1?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-pljava",
"name": "pljava",
"versionInfo": "1.6.8",
"downloadLocation": "https://github.com/tada/pljava/archive/V1_6_8.tar.gz",
"licenseConcluded": "BSD-3-Clause",
"licenseDeclared": "BSD-3-Clause",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:pl/java_project:pl/java:1.6.8:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/pljava@1.6.8?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"name": "org.postgresql:pljava",
"SPDXID": "SPDXRef-Package-551f76fc50fc5735",
"versionInfo": "1.6.8",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": true,
"packageVerificationCode": {
"packageVerificationCodeValue": "8b35292054790088fc1f41fb696a993d584d17cd"
},
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/org.postgresql/pljava@1.6.8"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "org.postgresql:pljava-api",
"SPDXID": "SPDXRef-Package-2dde48fd49927020",
"versionInfo": "1.6.8",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": true,
"packageVerificationCode": {
"packageVerificationCodeValue": "3aaf93e288e125481a250d50ab629d890f72e7b8"
},
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/org.postgresql/pljava-api@1.6.8"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "org.postgresql:pljava-examples",
"SPDXID": "SPDXRef-Package-f13571c8a05828cc",
"versionInfo": "1.6.8",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": true,
"packageVerificationCode": {
"packageVerificationCodeValue": "6f126f1237ddd2be6196726130c014770864bcc9"
},
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/org.postgresql/pljava-examples@1.6.8"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-unixodbc",
"name": "unixodbc",
"versionInfo": "2.3.12",
"downloadLocation": "http://www.unixodbc.org/unixODBC-2.3.12.tar.gz",
"licenseConcluded": "LGPL-2.1-only",
"licenseDeclared": "LGPL-2.1-only",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:unixodbc:unixodbc:2.3.12:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/unixodbc@2.3.12?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-psqlodbc",
"name": "psqlodbc",
"versionInfo": "16.0.0",
"downloadLocation": "https://ftp.postgresql.org/pub/odbc/versions/src/psqlodbc-16.00.0000.tar.gz",
"licenseConcluded": "LGPL-3.0-only",
"licenseDeclared": "LGPL-3.0-only",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:postgresql:psqlodbc:16.0.0:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/psqlodbc@16.0.0?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-protobuf",
"name": "protobuf",
"versionInfo": "3.21.12",
"downloadLocation": "https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.21.12.tar.gz",
"licenseConcluded": "BSD-3-Clause",
"licenseDeclared": "BSD-3-Clause",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:golang:protobuf:3.21.12:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/protobuf@3.21.12?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-protobuf-c",
"name": "protobuf-c",
"versionInfo": "1.5.1",
"downloadLocation": "https://github.com/protobuf-c/protobuf-c/releases/download/v1.5.1/protobuf-c-1.5.1.tar.gz",
"licenseConcluded": "BSD-2-Clause",
"licenseDeclared": "BSD-2-Clause",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:protobuf-c:protobuf-c:1.5.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/protobuf-c@1.5.1?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-postgis",
"name": "postgis",
"versionInfo": "3.4.4",
"downloadLocation": "http://download.osgeo.org/postgis/source/postgis-3.4.4.tar.gz",
"licenseConcluded": "GPL-2.0-or-later",
"licenseDeclared": "GPL-2.0-or-later",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:postgis:postgis:3.4.4:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/postgis@3.4.4?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-pgaudit",
"name": "pgaudit",
"versionInfo": "17.0.0",
"downloadLocation": "https://github.com/pgaudit/pgaudit/archive/17.0.tar.gz",
"licenseConcluded": "PostgreSQL",
"licenseDeclared": "PostgreSQL",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/pgaudit@17.0.0?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-pgbackrest",
"name": "pgbackrest",
"versionInfo": "2.54.2",
"downloadLocation": "https://github.com/pgbackrest/pgbackrest/archive/release/2.54.2.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:pgbackrest:pgbackrest:2.54.2:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/pgbackrest@2.54.2?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-wal2json",
"name": "wal2json",
"versionInfo": "2.6.0",
"downloadLocation": "https://github.com/eulerto/wal2json/archive/refs/tags/wal2json_2_6.tar.gz",
"licenseConcluded": "BSD-3-Clause",
"licenseDeclared": "BSD-3-Clause",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:wal2json:wal2json:2.6.0:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/wal2json@2.6.0?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"SPDXID": "SPDXRef-nss-wrapper",
"name": "nss-wrapper",
"versionInfo": "1.1.16",
"downloadLocation": "https://ftp.samba.org/pub/cwrap/nss_wrapper-1.1.16.tar.gz",
"licenseConcluded": "BSD-3-Clause",
"licenseDeclared": "BSD-3-Clause",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:nss_wrapper:nss_wrapper:1.1.16:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/nss_wrapper@1.1.16?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
}
],
"files": [
{
"fileName": "share/pljava/pljava-1.6.8.jar",
"SPDXID": "SPDXRef-File-28d7473df5d8af04-pljava",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "b0b8f544ddefbc9ba47ed72ddce13627df919cc9"
}
],
"copyrightText": ""
},
{
"fileName": "share/pljava/pljava-api-1.6.8.jar",
"SPDXID": "SPDXRef-File-3531f2e94fdf52e3-pljava",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "a2013f3202afc8dc15a21ef7a796730df08d1a00"
}
],
"copyrightText": ""
},
{
"fileName": "share/pljava/pljava-examples-1.6.8.jar",
"SPDXID": "SPDXRef-File-8f3dee04908d7a5a-pljava",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "1c61bddc63ce4aaaa803d9c39f0740a15c19d300"
}
],
"copyrightText": ""
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-geos"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-proj"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-gdal"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-json-c"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-orafce"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-pljava"
},
{
"spdxElementId": "SPDXRef-pljava",
"relatedSpdxElement": "SPDXRef-Package-2dde48fd49927020",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-pljava",
"relatedSpdxElement": "SPDXRef-Package-551f76fc50fc5735",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-pljava",
"relatedSpdxElement": "SPDXRef-Package-f13571c8a05828cc",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-2dde48fd49927020",
"relatedSpdxElement": "SPDXRef-File-3531f2e94fdf52e3-pljava",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-551f76fc50fc5735",
"relatedSpdxElement": "SPDXRef-File-28d7473df5d8af04-pljava",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-f13571c8a05828cc",
"relatedSpdxElement": "SPDXRef-File-8f3dee04908d7a5a-pljava",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-unixodbc"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-psqlodbc"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-protobuf"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-protobuf-c"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-postgis"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-pgaudit"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-pgbackrest"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-wal2json"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-nss-wrapper"
}
]
}

View File

@ -0,0 +1 @@
This file should be reported as part of the Bitnami PostgreSQL package.

View File

@ -0,0 +1,212 @@
{
"SPDXID": "SPDXRef-render-template",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2024-09-05T18:51:42.225Z",
"creators": [
"Organization: VMware, Inc."
]
},
"name": "SPDX document for render-template 1.0.7",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-render-template"
],
"documentNamespace": "render-template-1.0.7",
"packages": [
{
"SPDXID": "SPDXRef-render-template",
"name": "render-template",
"versionInfo": "1.0.7-4",
"downloadLocation": "https://github.com/bitnami/render-template/archive/refs/tags/v1.0.7.tar.gz",
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:render-template:render-template:1.0.7:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/render-template@1.0.7-4?arch=arm64&distro=debian-12"
}
],
"copyrightText": "NOASSERTION"
},
{
"name": "opt/bitnami/common/bin/render-template",
"SPDXID": "SPDXRef-Application-4b412cf3f25d2574-render-template",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"primaryPackagePurpose": "APPLICATION",
"copyrightText": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION"
},
{
"name": "github.com/aymerick/raymond",
"SPDXID": "SPDXRef-Package-c77f44f540ae92a0",
"versionInfo": "v2.0.2+incompatible",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/github.com/aymerick/raymond@v2.0.2%2Bincompatible"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "github.com/bitnami/render-template",
"SPDXID": "SPDXRef-Package-8213648cad51225d",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/github.com/bitnami/render-template"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "github.com/jessevdk/go-flags",
"SPDXID": "SPDXRef-Package-be6fde8a3edd7caf",
"versionInfo": "v1.6.1",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/github.com/jessevdk/go-flags@v1.6.1"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "github.com/mmikulicic/multierror",
"SPDXID": "SPDXRef-Package-b08d834237b92fed",
"versionInfo": "v0.0.0-20170428094957-c1ad6b5ecd26",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/github.com/mmikulicic/multierror@v0.0.0-20170428094957-c1ad6b5ecd26"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "golang.org/x/sys",
"SPDXID": "SPDXRef-Package-644a66965f04af7f",
"versionInfo": "v0.21.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/golang.org/x/sys@v0.21.0"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
},
{
"name": "stdlib",
"SPDXID": "SPDXRef-Package-7d9b78ebb84f1578",
"versionInfo": "1.22.7",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"sourceInfo": "opt/bitnami/common/package found in: opt/bitnami/common/bin/render-template",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:golang/stdlib@1.22.7"
}
],
"primaryPackagePurpose": "LIBRARY",
"copyrightText": "NOASSERTION"
}
],
"files": [],
"relationships": [
{
"spdxElementId": "SPDXRef-render-template",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Application-4b412cf3f25d2574-render-template"
},
{
"spdxElementId": "SPDXRef-Application-4b412cf3f25d2574-render-template",
"relatedSpdxElement": "SPDXRef-Package-8213648cad51225d",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-render-template",
"relatedSpdxElement": "SPDXRef-Application-4b412cf3f25d2574-render-template",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-8213648cad51225d",
"relatedSpdxElement": "SPDXRef-Package-644a66965f04af7f",
"relationshipType": "DEPENDS_ON"
},
{
"spdxElementId": "SPDXRef-Package-8213648cad51225d",
"relatedSpdxElement": "SPDXRef-Package-7d9b78ebb84f1578",
"relationshipType": "DEPENDS_ON"
},
{
"spdxElementId": "SPDXRef-Package-8213648cad51225d",
"relatedSpdxElement": "SPDXRef-Package-b08d834237b92fed",
"relationshipType": "DEPENDS_ON"
},
{
"spdxElementId": "SPDXRef-Package-8213648cad51225d",
"relatedSpdxElement": "SPDXRef-Package-be6fde8a3edd7caf",
"relationshipType": "DEPENDS_ON"
},
{
"spdxElementId": "SPDXRef-Package-8213648cad51225d",
"relatedSpdxElement": "SPDXRef-Package-c77f44f540ae92a0",
"relationshipType": "DEPENDS_ON"
}
]
}

View File

@ -0,0 +1,23 @@
SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
DocumentNamespace: redis-7.4.1
DocumentName: SPDX document for Redis(R) 7.4.1
SPDXID: SPDXRef-DOCUMENT
## Creation Information
Creator: Organization: VMware, Inc.
Created: 2024-10-02T20:17:11.692Z
## Relationships
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-redis
## Package Information
PackageName: redis
SPDXID: SPDXRef-redis
PackageVersion: 7.4.1-0
PackageDownloadLocation: http://download.redis.io/releases/redis-7.4.1.tar.gz
PackageLicenseConcluded: RSALv2
PackageLicenseDeclared: RSALv2
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:*:redis:redis:7.4.1:*:*:*:*:*:*:*
ExternalRef: PACKAGE-MANAGER purl pkg:bitnami/redis@7.4.1-0?arch=arm64&distro=debian-12
FilesAnalyzed: false

View File

@ -13,6 +13,7 @@ const (
AlpmPkg Type = "alpm" AlpmPkg Type = "alpm"
ApkPkg Type = "apk" ApkPkg Type = "apk"
BinaryPkg Type = "binary" BinaryPkg Type = "binary"
BitnamiPkg Type = "bitnami"
CocoapodsPkg Type = "pod" CocoapodsPkg Type = "pod"
ConanPkg Type = "conan" ConanPkg Type = "conan"
DartPubPkg Type = "dart-pub" DartPubPkg Type = "dart-pub"
@ -53,6 +54,7 @@ var AllPkgs = []Type{
AlpmPkg, AlpmPkg,
ApkPkg, ApkPkg,
BinaryPkg, BinaryPkg,
BitnamiPkg,
CocoapodsPkg, CocoapodsPkg,
ConanPkg, ConanPkg,
DartPubPkg, DartPubPkg,
@ -62,6 +64,7 @@ var AllPkgs = []Type{
GemPkg, GemPkg,
GithubActionPkg, GithubActionPkg,
GithubActionWorkflowPkg, GithubActionWorkflowPkg,
GraalVMNativeImagePkg,
GoModulePkg, GoModulePkg,
HackagePkg, HackagePkg,
HexPkg, HexPkg,
@ -96,6 +99,8 @@ func (t Type) PackageURLType() string {
return "alpm" return "alpm"
case ApkPkg: case ApkPkg:
return packageurl.TypeAlpine return packageurl.TypeAlpine
case BitnamiPkg:
return packageurl.TypeBitnami
case CocoapodsPkg: case CocoapodsPkg:
return packageurl.TypeCocoapods return packageurl.TypeCocoapods
case ConanPkg: case ConanPkg:
@ -177,28 +182,20 @@ func TypeFromPURL(p string) Type {
//nolint:funlen,gocyclo //nolint:funlen,gocyclo
func TypeByName(name string) Type { func TypeByName(name string) Type {
switch name { switch name {
case packageurl.TypeDebian:
return DebPkg
case packageurl.TypeRPM:
return RpmPkg
case packageurl.TypeLuaRocks:
return LuaRocksPkg
case "alpm": case "alpm":
return AlpmPkg return AlpmPkg
case packageurl.TypeAlpine, "alpine": case packageurl.TypeAlpine, "alpine":
return ApkPkg return ApkPkg
case packageurl.TypeMaven: case packageurl.TypeBitnami:
return JavaPkg return BitnamiPkg
case packageurl.TypeDebian:
return DebPkg
case packageurl.TypeComposer: case packageurl.TypeComposer:
return PhpComposerPkg return PhpComposerPkg
case "pecl": case "pecl":
return PhpPeclPkg return PhpPeclPkg
case packageurl.TypeGolang: case packageurl.TypeGolang:
return GoModulePkg return GoModulePkg
case packageurl.TypeNPM:
return NpmPkg
case packageurl.TypePyPi:
return PythonPkg
case packageurl.TypeGem: case packageurl.TypeGem:
return GemPkg return GemPkg
case "cargo", "crate": case "cargo", "crate":
@ -213,10 +210,18 @@ func TypeByName(name string) Type {
return ConanPkg return ConanPkg
case packageurl.TypeHackage: case packageurl.TypeHackage:
return HackagePkg return HackagePkg
case "portage":
return PortagePkg
case packageurl.TypeHex: case packageurl.TypeHex:
return HexPkg return HexPkg
case packageurl.TypeLuaRocks:
return LuaRocksPkg
case packageurl.TypeMaven:
return JavaPkg
case packageurl.TypeNPM:
return NpmPkg
case packageurl.TypePyPi:
return PythonPkg
case "portage":
return PortagePkg
case packageurl.TypeOTP: case packageurl.TypeOTP:
return ErlangOTPPkg return ErlangOTPPkg
case "linux-kernel": case "linux-kernel":
@ -229,6 +234,8 @@ func TypeByName(name string) Type {
return OpamPkg return OpamPkg
case packageurl.TypeCran: case packageurl.TypeCran:
return Rpkg return Rpkg
case packageurl.TypeRPM:
return RpmPkg
case packageurl.TypeSwift: case packageurl.TypeSwift:
return SwiftPkg return SwiftPkg
case "swiplpack": case "swiplpack":

View File

@ -22,6 +22,10 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:apk/alpine/util-linux@2.32.1", purl: "pkg:apk/alpine/util-linux@2.32.1",
expected: ApkPkg, expected: ApkPkg,
}, },
{
purl: "pkg:bitnami/apache@2.4.62-3?arch=arm64&distro=debian-12",
expected: BitnamiPkg,
},
{ {
purl: "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", purl: "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie",
expected: DebPkg, expected: DebPkg,
@ -50,7 +54,6 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:pub/util@1.2.34?hosted_url=pub.hosted.org", purl: "pkg:pub/util@1.2.34?hosted_url=pub.hosted.org",
expected: DartPubPkg, expected: DartPubPkg,
}, },
{ {
purl: "pkg:dotnet/Microsoft.CodeAnalysis.Razor@2.2.0", purl: "pkg:dotnet/Microsoft.CodeAnalysis.Razor@2.2.0",
expected: DotnetPkg, expected: DotnetPkg,
@ -137,6 +140,7 @@ func TestTypeFromPURL(t *testing.T) {
expectedTypes.Remove(string(GithubActionPkg), string(GithubActionWorkflowPkg)) expectedTypes.Remove(string(GithubActionPkg), string(GithubActionWorkflowPkg))
expectedTypes.Remove(string(WordpressPluginPkg)) expectedTypes.Remove(string(WordpressPluginPkg))
expectedTypes.Remove(string(TerraformPkg)) expectedTypes.Remove(string(TerraformPkg))
expectedTypes.Remove(string(GraalVMNativeImagePkg))
for _, test := range tests { for _, test := range tests {
t.Run(string(test.expected), func(t *testing.T) { t.Run(string(test.expected), func(t *testing.T) {