mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 00:46:38 +01:00
The java-pom-cataloger was processing pom.xml files found inside
META-INF/maven/ directories, which are archive metadata embedded by the
Maven build process. These are verbatim copies of the original build POM
containing unresolved ${property} references and dependency versions
inherited from parent POMs that are unavailable inside the archive. This
caused phantom packages with UNKNOWN versions to appear when recursive
archive cataloging opened JARs and exposed their internal pom.xml files
to the pom cataloger.
The java-archive-cataloger already uses pom.properties (which always
contains resolved values) to identify these archives, so the pom
cataloger should skip META-INF/maven/ pom.xml files entirely.
Signed-off-by: Thomas Bechtold <thomasbechtold@jpberlin.de>
331 lines
11 KiB
Go
331 lines
11 KiB
Go
package java
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/anchore/syft/internal"
|
|
"github.com/anchore/syft/internal/log"
|
|
"github.com/anchore/syft/internal/unknown"
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
|
|
"github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven"
|
|
)
|
|
|
|
const (
|
|
pomXMLGlob = "**/*pom.xml"
|
|
pomCatalogerName = "java-pom-cataloger"
|
|
)
|
|
|
|
type pomXMLCataloger struct {
|
|
cfg ArchiveCatalogerConfig
|
|
}
|
|
|
|
func (p pomXMLCataloger) Name() string {
|
|
return pomCatalogerName
|
|
}
|
|
|
|
func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
|
locations, err := fileResolver.FilesByGlob("**/pom.xml")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
r := maven.NewResolver(fileResolver, p.cfg.mavenConfig())
|
|
|
|
var errs error
|
|
var poms []*maven.Project
|
|
pomLocations := map[*maven.Project]file.Location{}
|
|
for _, pomLocation := range locations {
|
|
if isArchiveMetaPom(pomLocation) {
|
|
continue
|
|
}
|
|
pom, err := readPomFromLocation(fileResolver, pomLocation)
|
|
if err != nil || pom == nil {
|
|
log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom")
|
|
errs = unknown.Appendf(errs, pomLocation, "error reading pom.xml: %w", err)
|
|
continue
|
|
}
|
|
|
|
poms = append(poms, pom)
|
|
pomLocations[pom] = pomLocation
|
|
r.AddPom(ctx, pom, pomLocation)
|
|
}
|
|
|
|
var pkgs []pkg.Package
|
|
var relationships []artifact.Relationship
|
|
resolved := map[maven.ID]*pkg.Package{}
|
|
|
|
// catalog all the main packages first so these can be referenced later when building the dependency graph
|
|
for _, pom := range poms {
|
|
location := pomLocations[pom] // should always exist
|
|
|
|
id := r.ResolveID(ctx, pom)
|
|
mainPkg := newPackageFromMavenPom(ctx, r, pom, location)
|
|
if mainPkg == nil {
|
|
continue
|
|
}
|
|
resolved[id] = mainPkg
|
|
pkgs = append(pkgs, licenses.RelativeToPackage(ctx, fileResolver, *mainPkg))
|
|
}
|
|
|
|
// catalog all dependencies
|
|
for _, pom := range poms {
|
|
location := pomLocations[pom] // should always exist
|
|
|
|
id := r.ResolveID(ctx, pom)
|
|
mainPkg := resolved[id]
|
|
|
|
newPkgs, newRelationships, newErrs := collectDependencies(ctx, r, resolved, mainPkg, pom, location, p.cfg.ResolveTransitiveDependencies)
|
|
pkgs = append(pkgs, newPkgs...)
|
|
relationships = append(relationships, newRelationships...)
|
|
errs = unknown.Join(errs, newErrs)
|
|
}
|
|
|
|
return pkgs, relationships, errs
|
|
}
|
|
|
|
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*maven.Project, error) {
|
|
contents, err := fileResolver.FileContentsByLocation(pomLocation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer internal.CloseAndLogError(contents, pomLocation.RealPath)
|
|
return maven.ParsePomXML(contents)
|
|
}
|
|
|
|
// newPackageFromMavenPom processes a single Maven POM for a given parent package, returning only the main package from the pom
|
|
func newPackageFromMavenPom(ctx context.Context, r *maven.Resolver, pom *maven.Project, location file.Location) *pkg.Package {
|
|
id := r.ResolveID(ctx, pom)
|
|
parent, err := r.ResolveParent(ctx, pom)
|
|
if err != nil {
|
|
// this is expected in many cases, there will be no network access and the maven resolver is unable to
|
|
// look up information, so we can continue with what little information we have
|
|
log.Tracef("unable to resolve parent due to: %v", err)
|
|
}
|
|
|
|
var javaPomParent *pkg.JavaPomParent
|
|
if parent != nil { // parent is returned in both cases: when it is resolved or synthesized from the pom.parent info
|
|
parentID := r.ResolveID(ctx, parent)
|
|
javaPomParent = &pkg.JavaPomParent{
|
|
GroupID: parentID.GroupID,
|
|
ArtifactID: parentID.ArtifactID,
|
|
Version: parentID.Version,
|
|
}
|
|
}
|
|
|
|
pomLicenses, err := r.ResolveLicenses(ctx, pom)
|
|
if err != nil {
|
|
log.Tracef("error resolving licenses: %v", err)
|
|
}
|
|
pkgLicenses := toPkgLicenses(ctx, &location, pomLicenses)
|
|
|
|
m := pkg.JavaArchive{
|
|
PomProject: &pkg.JavaPomProject{
|
|
Parent: javaPomParent,
|
|
GroupID: id.GroupID,
|
|
ArtifactID: id.ArtifactID,
|
|
Version: id.Version,
|
|
Name: r.ResolveProperty(ctx, pom, pom.Name),
|
|
Description: r.ResolveProperty(ctx, pom, pom.Description),
|
|
URL: r.ResolveProperty(ctx, pom, pom.URL),
|
|
},
|
|
}
|
|
|
|
p := &pkg.Package{
|
|
Name: id.ArtifactID,
|
|
Version: id.Version,
|
|
Locations: file.NewLocationSet(
|
|
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
|
),
|
|
Licenses: pkg.NewLicenseSet(pkgLicenses...),
|
|
Language: pkg.Java,
|
|
Type: pkg.JavaPkg,
|
|
FoundBy: pomCatalogerName,
|
|
PURL: packageURL(id.ArtifactID, id.Version, m),
|
|
Metadata: m,
|
|
}
|
|
|
|
finalizePackage(p)
|
|
|
|
return p
|
|
}
|
|
|
|
func collectDependencies(ctx context.Context, r *maven.Resolver, resolved map[maven.ID]*pkg.Package, parentPkg *pkg.Package, pom *maven.Project, loc file.Location, includeTransitiveDependencies bool) ([]pkg.Package, []artifact.Relationship, error) {
|
|
var errs error
|
|
var pkgs []pkg.Package
|
|
var relationships []artifact.Relationship
|
|
|
|
pomID := r.ResolveID(ctx, pom)
|
|
for _, dep := range maven.DirectPomDependencies(pom) {
|
|
depID := r.ResolveDependencyID(ctx, pom, dep)
|
|
log.WithFields("pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Trace("adding maven pom dependency")
|
|
|
|
// we may have a reference to a package pointing to an existing pom on the filesystem, but we don't want to duplicate these entries
|
|
depPkg := resolved[depID]
|
|
if depPkg == nil {
|
|
p, err := newPackageFromDependency(
|
|
ctx,
|
|
r,
|
|
pom,
|
|
dep,
|
|
loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
|
)
|
|
if err != nil {
|
|
log.WithFields("error", err, "pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Debugf("error adding dependency")
|
|
}
|
|
|
|
if p == nil {
|
|
// we don't have a valid package, just continue to the next dependency
|
|
continue
|
|
}
|
|
depPkg = p
|
|
resolved[depID] = depPkg
|
|
|
|
// only resolve transitive dependencies if we're not already looking these up for the specific package
|
|
if includeTransitiveDependencies && depID.Valid() {
|
|
depPom, err := r.FindPom(ctx, depID.GroupID, depID.ArtifactID, depID.Version)
|
|
if err != nil {
|
|
log.WithFields("mavenID", depID, "error", err).Debug("error finding pom")
|
|
}
|
|
if depPom != nil {
|
|
transitivePkgs, transitiveRelationships, transitiveErrs := collectDependencies(ctx, r, resolved, depPkg, depPom, loc, includeTransitiveDependencies)
|
|
pkgs = append(pkgs, transitivePkgs...)
|
|
relationships = append(relationships, transitiveRelationships...)
|
|
errs = unknown.Join(errs, transitiveErrs)
|
|
}
|
|
}
|
|
}
|
|
|
|
pkgs = append(pkgs, *depPkg)
|
|
if parentPkg != nil {
|
|
relationships = append(relationships, artifact.Relationship{
|
|
From: *depPkg,
|
|
To: *parentPkg,
|
|
Type: artifact.DependencyOfRelationship,
|
|
})
|
|
}
|
|
}
|
|
|
|
return pkgs, relationships, errs
|
|
}
|
|
|
|
func newPomProject(ctx context.Context, r *maven.Resolver, path string, pom *maven.Project) *pkg.JavaPomProject {
|
|
id := r.ResolveID(ctx, pom)
|
|
name := r.ResolveProperty(ctx, pom, pom.Name)
|
|
projectURL := r.ResolveProperty(ctx, pom, pom.URL)
|
|
|
|
log.WithFields("path", path, "artifactID", id.ArtifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
|
|
return &pkg.JavaPomProject{
|
|
Path: path,
|
|
Parent: pomParent(ctx, r, pom),
|
|
GroupID: id.GroupID,
|
|
ArtifactID: id.ArtifactID,
|
|
Version: id.Version,
|
|
Name: name,
|
|
Description: cleanDescription(r.ResolveProperty(ctx, pom, pom.Description)),
|
|
URL: projectURL,
|
|
}
|
|
}
|
|
|
|
func newPackageFromDependency(ctx context.Context, r *maven.Resolver, pom *maven.Project, dep maven.Dependency, locations ...file.Location) (*pkg.Package, error) {
|
|
id := r.ResolveDependencyID(ctx, pom, dep)
|
|
|
|
var err error
|
|
var pkgLicenses []pkg.License
|
|
dependencyPom, depErr := r.FindPom(ctx, id.GroupID, id.ArtifactID, id.Version)
|
|
if depErr != nil {
|
|
err = errors.Join(err, depErr)
|
|
}
|
|
|
|
var pomProject *pkg.JavaPomProject
|
|
if dependencyPom != nil {
|
|
depLicenses, _ := r.ResolveLicenses(ctx, dependencyPom)
|
|
pkgLicenses = append(pkgLicenses, toPkgLicenses(ctx, nil, depLicenses)...)
|
|
pomProject = &pkg.JavaPomProject{
|
|
Parent: pomParent(ctx, r, dependencyPom),
|
|
GroupID: id.GroupID,
|
|
ArtifactID: id.ArtifactID,
|
|
Version: id.Version,
|
|
Name: r.ResolveProperty(ctx, pom, pom.Name),
|
|
Description: r.ResolveProperty(ctx, pom, pom.Description),
|
|
URL: r.ResolveProperty(ctx, pom, pom.URL),
|
|
}
|
|
}
|
|
|
|
m := pkg.JavaArchive{
|
|
PomProperties: &pkg.JavaPomProperties{
|
|
GroupID: id.GroupID,
|
|
ArtifactID: id.ArtifactID,
|
|
Scope: r.ResolveProperty(ctx, pom, dep.Scope),
|
|
},
|
|
PomProject: pomProject,
|
|
}
|
|
|
|
p := &pkg.Package{
|
|
Name: id.ArtifactID,
|
|
Version: id.Version,
|
|
Locations: file.NewLocationSet(locations...),
|
|
Licenses: pkg.NewLicenseSet(pkgLicenses...),
|
|
PURL: packageURL(id.ArtifactID, id.Version, m),
|
|
Language: pkg.Java,
|
|
Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet?
|
|
FoundBy: pomCatalogerName,
|
|
Metadata: m,
|
|
}
|
|
|
|
finalizePackage(p)
|
|
|
|
return p, err
|
|
}
|
|
|
|
func pomParent(ctx context.Context, r *maven.Resolver, pom *maven.Project) *pkg.JavaPomParent {
|
|
if pom == nil || pom.Parent == nil {
|
|
return nil
|
|
}
|
|
|
|
groupID := r.ResolveProperty(ctx, pom, pom.Parent.GroupID)
|
|
artifactID := r.ResolveProperty(ctx, pom, pom.Parent.ArtifactID)
|
|
version := r.ResolveProperty(ctx, pom, pom.Parent.Version)
|
|
|
|
if groupID == "" && artifactID == "" && version == "" {
|
|
return nil
|
|
}
|
|
|
|
return &pkg.JavaPomParent{
|
|
GroupID: groupID,
|
|
ArtifactID: artifactID,
|
|
Version: version,
|
|
}
|
|
}
|
|
|
|
// isArchiveMetaPom returns true if the pom.xml location is inside a META-INF/maven directory,
|
|
// indicating it is archive metadata embedded by the Maven build process rather than a project
|
|
// pom.xml. These embedded pom.xml files are verbatim copies of the original build POM with
|
|
// potentially unresolved ${property} references and dependency versions inherited from parent
|
|
// POMs that are not available inside the archive. The java-archive-cataloger already uses
|
|
// pom.properties (which always contains resolved values) to identify these archives, so the
|
|
// pom cataloger should skip them to avoid creating phantom dependency packages.
|
|
func isArchiveMetaPom(location file.Location) bool {
|
|
p := location.Path()
|
|
// normalize to forward slashes for consistent matching
|
|
p = filepath.ToSlash(p)
|
|
return strings.Contains(p, "META-INF/maven/")
|
|
}
|
|
|
|
func cleanDescription(original string) (cleaned string) {
|
|
descriptionLines := strings.Split(original, "\n")
|
|
for _, line := range descriptionLines {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
cleaned += line + " "
|
|
}
|
|
return strings.TrimSpace(cleaned)
|
|
}
|