mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 02:26:42 +01:00
feat: add support for detecting packages in JARs
Support for detection of packages present in a JAR has been added. It can be enabeld via the `DetectContainedPackages` config flag. Signed-off-by: Patrick Pichler <git@patrickpichler.dev>
This commit is contained in:
parent
f12788da78
commit
65e58ba33d
@ -3,7 +3,7 @@ package internal
|
||||
const (
|
||||
// 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.
|
||||
JSONSchemaVersion = "16.1.0"
|
||||
JSONSchemaVersion = "16.1.1"
|
||||
|
||||
// Changelog
|
||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||
|
||||
@ -29,6 +29,9 @@ func TestGlobMatch(t *testing.T) {
|
||||
{"a*a*a*a*b", strings.Repeat("a", 100), false},
|
||||
{"*x", "xxx", true},
|
||||
{"/home/place/**", "/home/place/a/thing", true},
|
||||
{"/org/test/**/*.class", "/org/test/system/files/Hello.class", true},
|
||||
{"**/*.class", "/org/test/system/files/Hello.class", true},
|
||||
{"**/*.class", "Hello.class", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
4266
schema/json/schema-16.1.1.json
Normal file
4266
schema/json/schema-16.1.1.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.0/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.1/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -1648,11 +1648,19 @@
|
||||
},
|
||||
"type": "array",
|
||||
"description": "ArchiveDigests is cryptographic hashes of the archive file"
|
||||
},
|
||||
"containedPackages": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "ContainedPackages is a list of all package names contained in the jar"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"virtualPath"
|
||||
"virtualPath",
|
||||
"containedPackages"
|
||||
],
|
||||
"description": "JavaArchive encapsulates all Java ecosystem metadata for a package as well as an (optional) parent relationship."
|
||||
},
|
||||
|
||||
@ -261,6 +261,12 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
|
||||
pkgPomProject = newPomProject(ctx, j.maven, parsedPom.path, parsedPom.project)
|
||||
}
|
||||
|
||||
var containedPackages []string
|
||||
|
||||
if j.cfg.DetectContainedPackages {
|
||||
containedPackages = j.discoverContainedPackages()
|
||||
}
|
||||
|
||||
return &pkg.Package{
|
||||
// TODO: maybe select name should just have a pom properties in it?
|
||||
Name: name,
|
||||
@ -276,6 +282,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
|
||||
Manifest: manifest,
|
||||
PomProject: pkgPomProject,
|
||||
ArchiveDigests: digests,
|
||||
ContainedPackages: containedPackages,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -880,3 +887,63 @@ func sortedIter[K cmp.Ordered, V any](values map[K]V) iter.Seq2[K, V] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests if the given string is a valid multi-release version as specified by
|
||||
// https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#multi-release-jar-files
|
||||
func isValidMultiReleaseVersion(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if s == "9" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 0 is not allowed
|
||||
if s[0] < '1' || s[0] > '9' {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ony digits are allowed
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return r < '0' || r > '9'
|
||||
}) != -1
|
||||
}
|
||||
|
||||
func (j *archiveParser) discoverContainedPackages() []string {
|
||||
pkgSet := strset.New()
|
||||
|
||||
classes := j.fileManifest.GlobMatch(false, "**/*.class")
|
||||
|
||||
for _, c := range classes {
|
||||
parts := strings.Split(c, "/")
|
||||
|
||||
if len(parts) > 3 && parts[0] == "META-INF" && parts[1] == "versions" && isValidMultiReleaseVersion(parts[2]) {
|
||||
// Strip the version specific prefix, as we are interested in all packages in the JAR.
|
||||
parts = parts[3:]
|
||||
}
|
||||
|
||||
// Ignore any unnamed packages.
|
||||
if len(parts) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip any non version specific classes in META-INF and ignore WEB-INF.
|
||||
if parts[0] == "META-INF" || parts[0] == "WEB-INF" {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgName := strings.Join(parts[:len(parts)-1], ".")
|
||||
|
||||
pkgSet.Add(pkgName)
|
||||
}
|
||||
|
||||
if pkgSet.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkgs := pkgSet.List()
|
||||
slices.Sort(pkgs)
|
||||
|
||||
return pkgs
|
||||
}
|
||||
|
||||
@ -204,6 +204,89 @@ func TestParseJar(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
ContainedPackages: []string{
|
||||
"hello",
|
||||
"net.bytebuddy",
|
||||
"net.bytebuddy.agent.builder",
|
||||
"net.bytebuddy.asm",
|
||||
"net.bytebuddy.build",
|
||||
"net.bytebuddy.description",
|
||||
"net.bytebuddy.description.annotation",
|
||||
"net.bytebuddy.description.enumeration",
|
||||
"net.bytebuddy.description.field",
|
||||
"net.bytebuddy.description.method",
|
||||
"net.bytebuddy.description.modifier",
|
||||
"net.bytebuddy.description.type",
|
||||
"net.bytebuddy.dynamic",
|
||||
"net.bytebuddy.dynamic.loading",
|
||||
"net.bytebuddy.dynamic.scaffold",
|
||||
"net.bytebuddy.dynamic.scaffold.inline",
|
||||
"net.bytebuddy.dynamic.scaffold.subclass",
|
||||
"net.bytebuddy.implementation",
|
||||
"net.bytebuddy.implementation.attribute",
|
||||
"net.bytebuddy.implementation.auxiliary",
|
||||
"net.bytebuddy.implementation.bind",
|
||||
"net.bytebuddy.implementation.bind.annotation",
|
||||
"net.bytebuddy.implementation.bytecode",
|
||||
"net.bytebuddy.implementation.bytecode.assign",
|
||||
"net.bytebuddy.implementation.bytecode.assign.primitive",
|
||||
"net.bytebuddy.implementation.bytecode.assign.reference",
|
||||
"net.bytebuddy.implementation.bytecode.collection",
|
||||
"net.bytebuddy.implementation.bytecode.constant",
|
||||
"net.bytebuddy.implementation.bytecode.member",
|
||||
"net.bytebuddy.jar.asm",
|
||||
"net.bytebuddy.jar.asm.commons",
|
||||
"net.bytebuddy.jar.asm.signature",
|
||||
"net.bytebuddy.matcher",
|
||||
"net.bytebuddy.pool",
|
||||
"net.bytebuddy.utility",
|
||||
"net.bytebuddy.utility.dispatcher",
|
||||
"net.bytebuddy.utility.nullability",
|
||||
"net.bytebuddy.utility.privilege",
|
||||
"net.bytebuddy.utility.visitor",
|
||||
"org.joda.time",
|
||||
"org.joda.time.base",
|
||||
"org.joda.time.chrono",
|
||||
"org.joda.time.convert",
|
||||
"org.joda.time.field",
|
||||
"org.joda.time.format",
|
||||
"org.joda.time.tz",
|
||||
},
|
||||
},
|
||||
},
|
||||
"byte-buddy": {
|
||||
Name: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
PURL: "pkg:maven/net.bytebuddy/byte-buddy@1.17.5",
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
// ensure that nested packages with different names than that of the parent are appended as
|
||||
// a suffix on the virtual path
|
||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar:net.bytebuddy:byte-buddy",
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/net.bytebuddy/byte-buddy/pom.properties",
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
},
|
||||
PomProject: &pkg.JavaPomProject{
|
||||
Path: "META-INF/maven/net.bytebuddy/byte-buddy/pom.xml",
|
||||
Parent: &pkg.JavaPomParent{
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy-parent",
|
||||
Version: "1.17.5",
|
||||
},
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
Name: "Byte Buddy (without dependencies)",
|
||||
Description: "Byte Buddy is a Java library for creating Java classes at run time. " +
|
||||
"This artifact is a build of Byte Buddy with all ASM dependencies repackaged " +
|
||||
"into its own name space.",
|
||||
URL: "",
|
||||
},
|
||||
// PomProject: &pkg.JavaPomProject{
|
||||
// Path: "META-INF/maven/io.jenkins.plugins/example-jenkins-plugin/pom.xml",
|
||||
// Parent: &pkg.JavaPomParent{GroupID: "org.jenkins-ci.plugins", ArtifactID: "plugin", Version: "4.46"},
|
||||
@ -315,6 +398,89 @@ func TestParseJar(t *testing.T) {
|
||||
ArtifactID: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
},
|
||||
ContainedPackages: []string{
|
||||
"hello",
|
||||
"net.bytebuddy",
|
||||
"net.bytebuddy.agent.builder",
|
||||
"net.bytebuddy.asm",
|
||||
"net.bytebuddy.build",
|
||||
"net.bytebuddy.description",
|
||||
"net.bytebuddy.description.annotation",
|
||||
"net.bytebuddy.description.enumeration",
|
||||
"net.bytebuddy.description.field",
|
||||
"net.bytebuddy.description.method",
|
||||
"net.bytebuddy.description.modifier",
|
||||
"net.bytebuddy.description.type",
|
||||
"net.bytebuddy.dynamic",
|
||||
"net.bytebuddy.dynamic.loading",
|
||||
"net.bytebuddy.dynamic.scaffold",
|
||||
"net.bytebuddy.dynamic.scaffold.inline",
|
||||
"net.bytebuddy.dynamic.scaffold.subclass",
|
||||
"net.bytebuddy.implementation",
|
||||
"net.bytebuddy.implementation.attribute",
|
||||
"net.bytebuddy.implementation.auxiliary",
|
||||
"net.bytebuddy.implementation.bind",
|
||||
"net.bytebuddy.implementation.bind.annotation",
|
||||
"net.bytebuddy.implementation.bytecode",
|
||||
"net.bytebuddy.implementation.bytecode.assign",
|
||||
"net.bytebuddy.implementation.bytecode.assign.primitive",
|
||||
"net.bytebuddy.implementation.bytecode.assign.reference",
|
||||
"net.bytebuddy.implementation.bytecode.collection",
|
||||
"net.bytebuddy.implementation.bytecode.constant",
|
||||
"net.bytebuddy.implementation.bytecode.member",
|
||||
"net.bytebuddy.jar.asm",
|
||||
"net.bytebuddy.jar.asm.commons",
|
||||
"net.bytebuddy.jar.asm.signature",
|
||||
"net.bytebuddy.matcher",
|
||||
"net.bytebuddy.pool",
|
||||
"net.bytebuddy.utility",
|
||||
"net.bytebuddy.utility.dispatcher",
|
||||
"net.bytebuddy.utility.nullability",
|
||||
"net.bytebuddy.utility.privilege",
|
||||
"net.bytebuddy.utility.visitor",
|
||||
"org.joda.time",
|
||||
"org.joda.time.base",
|
||||
"org.joda.time.chrono",
|
||||
"org.joda.time.convert",
|
||||
"org.joda.time.field",
|
||||
"org.joda.time.format",
|
||||
"org.joda.time.tz",
|
||||
},
|
||||
},
|
||||
},
|
||||
"byte-buddy": {
|
||||
Name: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
PURL: "pkg:maven/net.bytebuddy/byte-buddy@1.17.5",
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
// ensure that nested packages with different names than that of the parent are appended as
|
||||
// a suffix on the virtual path
|
||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar:net.bytebuddy:byte-buddy",
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/net.bytebuddy/byte-buddy/pom.properties",
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
},
|
||||
PomProject: &pkg.JavaPomProject{
|
||||
Path: "META-INF/maven/net.bytebuddy/byte-buddy/pom.xml",
|
||||
Parent: &pkg.JavaPomParent{
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy-parent",
|
||||
Version: "1.17.5",
|
||||
},
|
||||
GroupID: "net.bytebuddy",
|
||||
ArtifactID: "byte-buddy",
|
||||
Version: "1.17.5",
|
||||
Name: "Byte Buddy (without dependencies)",
|
||||
Description: "Byte Buddy is a Java library for creating Java classes at run time. " +
|
||||
"This artifact is a build of Byte Buddy with all ASM dependencies repackaged " +
|
||||
"into its own name space.",
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
"joda-time": {
|
||||
@ -371,6 +537,7 @@ func TestParseJar(t *testing.T) {
|
||||
cfg := ArchiveCatalogerConfig{
|
||||
UseNetwork: false,
|
||||
UseMavenLocalRepository: false,
|
||||
DetectContainedPackages: true,
|
||||
}
|
||||
parser, cleanupFn, err := newJavaArchiveParser(context.Background(),
|
||||
file.LocationReadCloser{
|
||||
|
||||
@ -33,6 +33,10 @@ type ArchiveCatalogerConfig struct {
|
||||
// ResolveTransitiveDependencies enables resolving transitive dependencies for java packages found within archives.
|
||||
// app-config: java.resolve-transitive-dependencies
|
||||
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
|
||||
|
||||
// DetectContainedPackages enables collecting all package names contained in a jar.
|
||||
// app-config: java.detect-contained-packages
|
||||
DetectContainedPackages bool `yaml:"detect-contained-packages" json:"detect-contained-packages" mapstructure:"detect-contained-packages"`
|
||||
}
|
||||
|
||||
func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
|
||||
@ -45,6 +49,7 @@ func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
|
||||
MavenBaseURL: strings.Join(mavenCfg.Repositories, ","),
|
||||
MaxParentRecursiveDepth: mavenCfg.MaxParentRecursiveDepth,
|
||||
ResolveTransitiveDependencies: false,
|
||||
DetectContainedPackages: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +86,11 @@ func (j ArchiveCatalogerConfig) WithArchiveTraversal(search cataloging.ArchiveSe
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithDetectContainedPackages(detectContainedPackages bool) ArchiveCatalogerConfig {
|
||||
j.DetectContainedPackages = detectContainedPackages
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) mavenConfig() maven.Config {
|
||||
return maven.Config{
|
||||
UseNetwork: j.UseNetwork,
|
||||
|
||||
@ -23,6 +23,7 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
expected: []string{
|
||||
"example-java-app-maven",
|
||||
"joda-time",
|
||||
"byte-buddy",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -30,6 +31,7 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
expected: []string{
|
||||
"example-java-app-maven",
|
||||
"joda-time",
|
||||
"byte-buddy",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ targetCompatibility = 1.8
|
||||
|
||||
dependencies {
|
||||
implementation "joda-time:joda-time:2.2"
|
||||
implementation "net.bytebuddy:byte-buddy:1.17.5"
|
||||
testImplementation "junit:junit:4.12"
|
||||
}
|
||||
// end::dependencies[]
|
||||
@ -37,6 +38,8 @@ jar {
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
// end::jar[]
|
||||
|
||||
|
||||
@ -3,5 +3,6 @@
|
||||
# This file is expected to be part of source control.
|
||||
joda-time:joda-time:2.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
junit:junit:4.12=testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
|
||||
empty=annotationProcessor,testAnnotationProcessor
|
||||
|
||||
@ -14,6 +14,13 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- tag::bytebuddy[] -->
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.17.5</version>
|
||||
</dependency>
|
||||
<!-- end::bytebuddy[] -->
|
||||
<!-- tag::joda[] -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
|
||||
@ -22,6 +22,7 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
expected: []string{
|
||||
"example-java-app-maven",
|
||||
"joda-time",
|
||||
"byte-buddy",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -114,6 +114,9 @@ type JavaArchive struct {
|
||||
// ArchiveDigests is cryptographic hashes of the archive file
|
||||
ArchiveDigests []file.Digest `hash:"ignore" json:"digest,omitempty"`
|
||||
|
||||
// ContainedPackages is a list of all package names contained in the jar
|
||||
ContainedPackages []string `mapstructure:"ContainedPackages" json:"containedPackages"`
|
||||
|
||||
// Parent is reference to parent package (for nested archives)
|
||||
Parent *Package `hash:"ignore" json:"-"`
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user