add pom.xml cataloger (#1055)

Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Jonas Xavier 2022-06-22 11:19:10 -07:00 committed by GitHub
parent 3f6afd572a
commit 1d14f22e45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 270 additions and 29 deletions

View File

@ -66,6 +66,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
deb.NewDpkgdbCataloger(), deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(), rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(cfg.Java()), java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(), apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(), golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(), golang.NewGoModFileCataloger(),
@ -88,6 +89,7 @@ func AllCatalogers(cfg Config) []Cataloger {
deb.NewDpkgdbCataloger(), deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(), rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(cfg.Java()), java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(), apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(), golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(), golang.NewGoModFileCataloger(),

View File

@ -335,7 +335,7 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri
projectByParentPath := make(map[string]pkg.PomProject) projectByParentPath := make(map[string]pkg.PomProject)
for filePath, fileContents := range contentsOfMavenProjectFiles { for filePath, fileContents := range contentsOfMavenProjectFiles {
pomProject, err := parsePomXML(filePath, strings.NewReader(fileContents)) pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents))
if err != nil { if err != nil {
log.Warnf("failed to parse pom.xml virtualPath=%q path=%q: %+v", virtualPath, filePath, err) log.Warnf("failed to parse pom.xml virtualPath=%q path=%q: %+v", virtualPath, filePath, err)
continue continue

View File

@ -6,34 +6,79 @@ import (
"io" "io"
"strings" "strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/vifraa/gopom" "github.com/vifraa/gopom"
"golang.org/x/net/html/charset" "golang.org/x/net/html/charset"
) )
const pomXMLGlob = "*pom.xml" const pomXMLGlob = "*pom.xml"
const pomXMLDirGlob = "**/pom.xml"
func parsePomXML(path string, reader io.Reader) (*pkg.PomProject, error) { func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
var project gopom.Project pom, err := decodePomXML(content)
if err != nil {
decoder := xml.NewDecoder(reader) return nil, nil, err
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil"
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&project); err != nil {
return nil, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
} }
var pkgs []*pkg.Package
for _, dep := range pom.Dependencies {
p := newPackageFromPom(dep)
if p.Name == "" {
continue
}
pkgs = append(pkgs, p)
}
return pkgs, nil, nil
}
func parsePomXMLProject(path string, reader io.Reader) (*pkg.PomProject, error) {
project, err := decodePomXML(reader)
if err != nil {
return nil, err
}
return newPomProject(path, project), nil
}
func newPomProject(path string, p gopom.Project) *pkg.PomProject {
return &pkg.PomProject{ return &pkg.PomProject{
Path: path, Path: path,
Parent: pomParent(project.Parent), Parent: pomParent(p.Parent),
GroupID: project.GroupID, GroupID: p.GroupID,
ArtifactID: project.ArtifactID, ArtifactID: p.ArtifactID,
Version: project.Version, Version: p.Version,
Name: project.Name, Name: p.Name,
Description: cleanDescription(project.Description), Description: cleanDescription(p.Description),
URL: project.URL, URL: p.URL,
}, nil }
}
func newPackageFromPom(dep gopom.Dependency) *pkg.Package {
p := &pkg.Package{
Name: dep.ArtifactID,
Version: dep.Version,
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?
MetadataType: pkg.JavaMetadataType,
FoundBy: javaPomCataloger,
}
p.Metadata = pkg.JavaMetadata{PURL: packageURL(*p)}
return p
}
func decodePomXML(content io.Reader) (project gopom.Project, err error) {
decoder := xml.NewDecoder(content)
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil"
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&project); err != nil {
return project, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
}
return project, nil
} }
func pomParent(parent gopom.Parent) (result *pkg.PomParent) { func pomParent(parent gopom.Parent) (result *pkg.PomParent) {

View File

@ -10,7 +10,54 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_parsePomXML(t *testing.T) { func Test_parserPomXML(t *testing.T) {
tests := []struct {
input string
expected []*pkg.Package
}{
{
input: "test-fixtures/pom/pom.xml",
expected: []*pkg.Package{
{
Name: "joda-time",
Version: "2.9.2",
FoundBy: javaPomCataloger,
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
},
},
{
Name: "junit",
Version: "4.12",
FoundBy: "java-pom-cataloger",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/junit/junit@4.12",
},
},
},
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
fixture, err := os.Open(test.input)
assert.NoError(t, err)
actual, relationships, err := parserPomXML(fixture.Name(), fixture)
assert.NoError(t, err)
assert.Nil(t, relationships)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_parsePomXMLProject(t *testing.T) {
tests := []struct { tests := []struct {
expected pkg.PomProject expected pkg.PomProject
}{ }{
@ -37,7 +84,7 @@ func Test_parsePomXML(t *testing.T) {
fixture, err := os.Open(test.expected.Path) fixture, err := os.Open(test.expected.Path)
assert.NoError(t, err) assert.NoError(t, err)
actual, err := parsePomXML(fixture.Name(), fixture) actual, err := parsePomXMLProject(fixture.Name(), fixture)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &test.expected, actual) assert.Equal(t, &test.expected, actual)

View File

@ -0,0 +1,17 @@
package java
import "github.com/anchore/syft/syft/pkg/cataloger/common"
const javaPomCataloger = "java-pom-cataloger"
// NewJavaPomCataloger returns a cataloger capable of parsing
// dependencies from a pom.xml file.
// Pom files list dependencies that maybe not be locally installed yet.
func NewJavaPomCataloger() *common.GenericCataloger {
globParsers := make(map[string]common.ParserFn)
// java project files
globParsers[pomXMLDirGlob] = parserPomXML
return common.NewGenericCataloger(nil, globParsers, javaPomCataloger)
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.anchore</groupId>
<artifactId>example-java-app-maven</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- tag::joda[] -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.2</version>
</dependency>
<!-- end::joda[] -->
<!-- tag::junit[] -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- end::junit[] -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>hello.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -60,6 +60,15 @@ var imageOnlyTestCases = []testCase{
"libc-utils": "0.7.2-r0", "libc-utils": "0.7.2-r0",
}, },
}, },
{
name: "find java packages excluding pom.xml", // image scans can not include packages that have yet to be installed
pkgType: pkg.JavaPkg,
pkgLanguage: pkg.Java,
pkgInfo: map[string]string{
"example-java-app-maven": "0.1.0",
"joda-time": "2.9.2",
},
},
} }
var dirOnlyTestCases = []testCase{ var dirOnlyTestCases = []testCase{
@ -218,6 +227,17 @@ var dirOnlyTestCases = []testCase{
"System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0",
}, },
}, },
{
name: "find java packages including pom.xml", // directory scans can include packages that have yet to be installed
pkgType: pkg.JavaPkg,
pkgLanguage: pkg.Java,
duplicates: 1, // joda-time is included in both pom.xml AND the .jar collection
pkgInfo: map[string]string{
"example-java-app-maven": "0.1.0",
"joda-time": "2.9.2",
"junit": "4.12",
},
},
} }
var commonTestCases = []testCase{ var commonTestCases = []testCase{
@ -244,15 +264,7 @@ var commonTestCases = []testCase{
"netbase": "5.4", "netbase": "5.4",
}, },
}, },
{
name: "find java packages",
pkgType: pkg.JavaPkg,
pkgLanguage: pkg.Java,
pkgInfo: map[string]string{
"example-java-app-maven": "0.1.0",
"joda-time": "2.9.2",
},
},
{ {
name: "find jenkins plugins", name: "find jenkins plugins",
pkgType: pkg.JenkinsPluginPkg, pkgType: pkg.JenkinsPluginPkg,

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.anchore</groupId>
<artifactId>example-java-app-maven</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- tag::joda[] -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.2</version>
</dependency>
<!-- end::joda[] -->
<!-- tag::junit[] -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- end::junit[] -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>hello.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>