Add JVM cataloger (#3217)

* add jvm cataloger

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

* simplify version selection

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

* CPEs from JVM cataloger should be declared

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

* ensure package overlap is enabled for sensitive use cases

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

* more permissive glob

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2024-09-23 17:21:38 -04:00
committed by GitHub
parent 7815d8e4d9
commit 01de99b253
25 changed files with 4271 additions and 81 deletions
+6
View File
@@ -43,3 +43,9 @@ func NewGradleLockfileCataloger() pkg.Cataloger {
return generic.NewCataloger("java-gradle-lockfile-cataloger").
WithParserByGlobs(parseGradleLockfile, gradleLockfileGlob)
}
// NewJvmDistributionCataloger returns packages representing JDK/JRE installations (of multiple distribution types).
func NewJvmDistributionCataloger() pkg.Cataloger {
return generic.NewCataloger("java-jvm-cataloger").
WithParserByGlobs(parseJVMRelease, jvmReleaseGlob)
}
+113
View File
@@ -4,6 +4,9 @@ import (
"testing"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
@@ -102,3 +105,113 @@ func Test_POMCataloger_Globs(t *testing.T) {
})
}
}
func TestJvmDistributionCataloger(t *testing.T) {
cases := []struct {
name string
fixture string
expected pkg.Package
}{
{
name: "valid 1.8.0",
fixture: "test-fixtures/jvm-installs/oracle-jdk-se-8",
expected: pkg.Package{
Name: "jdk",
Version: "1.8.0_411-b25",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("usr/lib/jvm/jdk-1.8-oracle-x64/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jre:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
cpe.Must("cpe:2.3:a:oracle:jdk:1.8.0:update411:*:*:*:*:*:*", cpe.DeclaredSource),
},
PURL: "pkg:generic/oracle/jdk@1.8.0_411-b25",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
JavaRuntimeVersion: "1.8.0_411-b25",
JavaVersion: "1.8.0_411",
OsArch: "amd64",
OsName: "Linux",
OsVersion: "2.6",
Source: ".:git:71ec2089cf8c+",
BuildType: "commercial",
},
Files: []string{
"usr/lib/jvm/jdk-1.8-oracle-x64/bin/javac",
"usr/lib/jvm/jdk-1.8-oracle-x64/release",
},
},
},
},
{
name: "valid post-jep223",
fixture: "test-fixtures/jvm-installs/valid-post-jep223",
expected: pkg.Package{
Name: "openjdk",
Version: "21.0.4+7-LTS",
FoundBy: "java-jvm-cataloger",
Locations: file.NewLocationSet(file.NewLocation("jvm/openjdk/release")),
Licenses: pkg.NewLicenseSet(),
Type: pkg.BinaryPkg,
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:oracle:openjdk:21.0.4:*:*:*:*:*:*:*", cpe.DeclaredSource)},
PURL: "pkg:generic/oracle/openjdk@21.0.4%2B7-LTS?repository_url=https://github.com/adoptium/jdk21u.git",
Metadata: pkg.JavaVMInstallation{
Release: pkg.JavaVMRelease{
Implementor: "Eclipse Adoptium",
ImplementorVersion: "Temurin-21.0.4+7",
JavaRuntimeVersion: "21.0.4+7-LTS",
JavaVersion: "21.0.4",
JavaVersionDate: "2024-07-16",
Libc: "gnu",
Modules: []string{
"java.base", "java.compiler", "java.datatransfer", "java.xml", "java.prefs",
"java.desktop", "java.instrument", "java.logging", "java.management",
"java.security.sasl", "java.naming", "java.rmi", "java.management.rmi",
"java.net.http", "java.scripting", "java.security.jgss",
"java.transaction.xa", "java.sql", "java.sql.rowset", "java.xml.crypto", "java.se",
"java.smartcardio", "jdk.accessibility", "jdk.internal.jvmstat", "jdk.attach",
"jdk.charsets", "jdk.internal.opt", "jdk.zipfs", "jdk.compiler", "jdk.crypto.ec",
"jdk.crypto.cryptoki", "jdk.dynalink", "jdk.internal.ed", "jdk.editpad", "jdk.hotspot.agent",
"jdk.httpserver", "jdk.incubator.vector", "jdk.internal.le", "jdk.internal.vm.ci",
"jdk.internal.vm.compiler", "jdk.internal.vm.compiler.management", "jdk.jartool",
"jdk.javadoc", "jdk.jcmd", "jdk.management", "jdk.management.agent", "jdk.jconsole",
"jdk.jdeps", "jdk.jdwp.agent", "jdk.jdi", "jdk.jfr", "jdk.jlink", "jdk.jpackage", "jdk.jshell",
"jdk.jsobject", "jdk.jstatd", "jdk.localedata", "jdk.management.jfr", "jdk.naming.dns",
"jdk.naming.rmi", "jdk.net", "jdk.nio.mapmode", "jdk.random", "jdk.sctp", "jdk.security.auth",
"jdk.security.jgss", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom",
},
OsArch: "aarch64",
OsName: "Linux",
Source: ".:git:13710926b798",
BuildSource: "git:1271f10a26c47e1489a814dd2731f936a588d621",
BuildSourceRepo: "https://github.com/adoptium/temurin-build.git",
SourceRepo: "https://github.com/adoptium/jdk21u.git",
FullVersion: "21.0.4+7-LTS",
SemanticVersion: "21.0.4+7",
BuildInfo: "OS: Linux Version: 5.4.0-150-generic",
JvmVariant: "Hotspot",
JvmVersion: "21.0.4+7-LTS",
ImageType: "JDK",
},
Files: []string{
"jvm/openjdk/release",
"jvm/openjdk/sibling/child/file1.txt",
},
},
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
p := tt.expected
p.SetID()
pkgtest.TestCataloger(t, tt.fixture, NewJvmDistributionCataloger(), []pkg.Package{p}, nil)
})
}
}
@@ -0,0 +1,423 @@
package java
import (
"bufio"
"context"
"fmt"
"io"
"path"
"sort"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/anchore/packageurl-go"
stereoFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const (
// this is a very permissive glob that will match more than just the JVM release file.
// we started with "**/{java,jvm}/*/release", but this prevents scanning JVM archive contents (e.g. jdk8u402.zip).
// this approach lets us check more files for JVM release info, but be rather silent about errors.
jvmReleaseGlob = "**/release"
oracleVendor = "oracle"
openJdkProduct = "openjdk"
jre = "jre"
jdk = "jdk"
)
// the /opt/java/openjdk/release file (and similar paths) is a file that is present in the multiple OpenJDK distributions
// here's an example of the contents of the file:
//
// IMPLEMENTOR="Eclipse Adoptium"
// IMPLEMENTOR_VERSION="Temurin-21.0.4+7"
// JAVA_RUNTIME_VERSION="21.0.4+7-LTS"
// JAVA_VERSION="21.0.4"
// JAVA_VERSION_DATE="2024-07-16"
// LIBC="gnu"
// MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.jvmstat jdk.attach jdk.charsets jdk.internal.opt jdk.zipfs jdk.compiler jdk.crypto.ec jdk.crypto.cryptoki jdk.dynalink jdk.internal.ed jdk.editpad jdk.hotspot.agent jdk.httpserver jdk.incubator.vector jdk.internal.le jdk.internal.vm.ci jdk.internal.vm.compiler jdk.internal.vm.compiler.management jdk.jartool jdk.javadoc jdk.jcmd jdk.management jdk.management.agent jdk.jconsole jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.jpackage jdk.jshell jdk.jsobject jdk.jstatd jdk.localedata jdk.management.jfr jdk.naming.dns jdk.naming.rmi jdk.net jdk.nio.mapmode jdk.random jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom"
// OS_ARCH="aarch64"
// OS_NAME="Linux"
// SOURCE=".:git:13710926b798"
// BUILD_SOURCE="git:1271f10a26c47e1489a814dd2731f936a588d621"
// BUILD_SOURCE_REPO="https://github.com/adoptium/temurin-build.git"
// SOURCE_REPO="https://github.com/adoptium/jdk21u.git"
// FULL_VERSION="21.0.4+7-LTS"
// SEMANTIC_VERSION="21.0.4+7"
// BUILD_INFO="OS: Linux Version: 5.4.0-150-generic"
// JVM_VARIANT="Hotspot"
// JVM_VERSION="21.0.4+7-LTS"
// IMAGE_TYPE="JDK"
//
// In terms of the temurin flavor, these are controlled by:
// - config: https://github.com/adoptium/temurin-build/blob/v2023.01.03/sbin/common/config_init.sh
// - build script: https://github.com/adoptium/temurin-build/blob/v2023.01.03/sbin/build.sh#L1584-L1796
type jvmCpeInfo struct {
vendor, product, version string
}
func parseJVMRelease(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
ri, err := parseJvmReleaseInfo(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse JVM release info %q: %w", reader.Path(), err)
}
if ri == nil {
// TODO: known-unknown: expected JDK installation package
return nil, nil, nil
}
version := jvmPackageVersion(ri)
// TODO: detect old and new version format from multiple fields
licenses := jvmLicenses(resolver, ri)
locations := file.NewLocationSet(reader.Location)
for _, lic := range licenses.ToSlice() {
locations.Add(lic.Locations.ToSlice()...)
}
installDir := path.Dir(reader.Path())
files, hasJdk := findJvmFiles(resolver, installDir)
vendor, product := jvmPrimaryVendorProduct(ri.Implementor, reader.Path(), ri.ImageType, hasJdk)
p := pkg.Package{
Name: product,
Locations: locations,
Version: version,
CPEs: jvmCpes(version, vendor, product, ri.ImageType, hasJdk),
PURL: jvmPurl(*ri, version, vendor, product),
Licenses: licenses,
Type: pkg.BinaryPkg,
Metadata: pkg.JavaVMInstallation{
Release: *ri,
Files: files,
},
}
p.SetID()
return []pkg.Package{p}, nil, nil
}
func jvmLicenses(_ file.Resolver, _ *pkg.JavaVMRelease) pkg.LicenseSet {
// TODO: get this from the dir(<RELEASE>)/legal/**/LICENSE files when we start cataloging license content
// see https://github.com/anchore/syft/issues/656
return pkg.NewLicenseSet()
}
func findJvmFiles(resolver file.Resolver, installDir string) ([]string, bool) {
ownedLocations, err := resolver.FilesByGlob(installDir + "/**")
if err != nil {
// TODO: known-unknowns
log.WithFields("path", installDir, "error", err).Trace("unable to find installed JVM files")
}
var results []string
var hasJdk bool
for _, loc := range ownedLocations {
p := loc.Path()
results = append(results, p)
if !hasJdk && strings.HasSuffix(p, "bin/javac") {
hasJdk = true
}
}
sort.Strings(results)
return results, hasJdk
}
func jvmPurl(ri pkg.JavaVMRelease, version, vendor, product string) string {
var qualifiers []packageurl.Qualifier
if ri.SourceRepo != "" {
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "repository_url",
Value: ri.SourceRepo,
})
} else if ri.BuildSourceRepo != "" {
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "repository_url",
Value: ri.BuildSourceRepo,
})
}
pURL := packageurl.NewPackageURL(
packageurl.TypeGeneric,
vendor,
product,
version,
qualifiers,
"")
return pURL.ToString()
}
func jvmPrimaryVendorProduct(implementor, path, imageType string, hasJdk bool) (string, string) {
implementor = strings.ReplaceAll(strings.ToLower(implementor), " ", "")
pickProduct := func() string {
if hasJdk || jvmProjectByType(imageType) == jdk {
return jdk
}
return jre
}
switch {
case strings.Contains(implementor, "azul") || strings.Contains(path, "zulu"):
return "azul", "zulu"
case strings.Contains(implementor, "sun"):
return "sun", pickProduct()
case strings.Contains(implementor, "oracle") || strings.Contains(path, "oracle"):
return oracleVendor, pickProduct()
}
return oracleVendor, openJdkProduct
}
func jvmCpes(version, primaryVendor, primaryProduct, imageType string, hasJdk bool) []cpe.CPE {
// see https://github.com/anchore/syft/issues/2422 for more context
var candidates []jvmCpeInfo
newCandidate := func(ven, prod, ver string) {
candidates = append(candidates, jvmCpeInfo{
vendor: ven,
product: prod,
version: ver,
})
}
newEnterpriseCandidate := func(ven, ver string) {
newCandidate(ven, jre, ver)
if hasJdk || jvmProjectByType(imageType) == jdk {
newCandidate(ven, jdk, ver)
}
}
switch {
case primaryVendor == "azul":
newCandidate(primaryVendor, "zulu", version)
newCandidate(oracleVendor, openJdkProduct, version)
case primaryVendor == "sun":
newEnterpriseCandidate(primaryVendor, version)
case primaryVendor == oracleVendor && primaryProduct != openJdkProduct:
newCandidate(primaryVendor, "java_se", version)
newEnterpriseCandidate(primaryVendor, version)
default:
newCandidate(primaryVendor, primaryProduct, version)
}
var cpes []cpe.CPE
for _, candidate := range candidates {
c := newJvmCpe(candidate)
if c == nil {
continue
}
cpes = append(cpes, *c)
}
return cpes
}
func getJVMVersionAndUpdate(version string) (string, string) {
hasPlus := strings.Contains(version, "+")
hasUnderscore := strings.Contains(version, "_")
switch {
case hasUnderscore:
// assume legacy version strings are provided
// example: 1.8.0_302-b08
fields := strings.Split(version, "_")
if len(fields) == 2 {
shortVer := fields[0]
fields = strings.Split(fields[1], "-")
return shortVer, fields[0]
}
case hasPlus:
// assume JEP 223 version strings are provided
// example: 9.0.1+20
fields := strings.Split(version, "+")
return fields[0], ""
}
// this could be a legacy or modern string that does not have an update
return version, ""
}
func newJvmCpe(candidate jvmCpeInfo) *cpe.CPE {
if candidate.vendor == "" || candidate.product == "" || candidate.version == "" {
return nil
}
shortVer, update := getJVMVersionAndUpdate(candidate.version)
if shortVer == "" {
return nil
}
if update != "" && !strings.Contains(strings.ToLower(update), "update") {
update = "update" + trim0sFromLeft(update)
}
return &cpe.CPE{
Attributes: cpe.Attributes{
Part: "a",
Vendor: candidate.vendor,
Product: candidate.product,
Version: shortVer,
Update: update,
},
// note: we must use a declared source here. Though we are not directly raising up raw CPEs from cataloged material,
// these are vastly more reliable and accurate than what would be generated from the cpe generator logic.
// We want these CPEs to override any generated CPEs (and in fact prevent the generation of CPEs for these packages altogether).
Source: cpe.DeclaredSource,
}
}
func jvmProjectByType(ty string) string {
if strings.Contains(strings.ToLower(ty), jre) {
return jre
}
return jdk
}
// jvmPackageVersion attempts to extract the correct version value for the JVM given a platter of version strings to choose
// from, and makes special consideration to what a valid version is relative to JEP 223.
//
// example version values (openjdk >8):
//
// IMPLEMENTOR_VERSION "Temurin-21.0.4+7"
// JAVA_RUNTIME_VERSION "21.0.4+7-LTS"
// FULL_VERSION "21.0.4+7-LTS"
// SEMANTIC_VERSION "21.0.4+7"
// JAVA_VERSION "21.0.4"
//
// example version values (openjdk 8):
//
// JAVA_VERSION "1.8.0_422"
// FULL_VERSION "1.8.0_422-b05"
// SEMANTIC_VERSION "8.0.422+5"
//
// example version values (openjdk 8, but older):
//
// JAVA_VERSION "1.8.0_302"
// FULL_VERSION "1.8.0_302-b08"
// SEMANTIC_VERSION "8.0.302+8"
//
// example version values (oracle):
//
// IMPLEMENTOR_VERSION (missing)
// JAVA_RUNTIME_VERSION "22.0.2+9-70"
// JAVA_VERSION "22.0.2"
//
// example version values (mariner):
//
// IMPLEMENTOR_VERSION "Microsoft-9889599"
// JAVA_RUNTIME_VERSION "17.0.12+7-LTS"
// JAVA_VERSION "17.0.12"
//
// example version values (amazon):
//
// IMPLEMENTOR_VERSION "Corretto-17.0.12.7.1"
// JAVA_RUNTIME_VERSION "17.0.12+7-LTS"
// JAVA_VERSION "17.0.12"
//
// JEP 223 changes to JVM version string in the following way:
//
// Pre JEP 223 Post JEP 223
// Release Type long short long short
// ------------ -------------------- --------------------
// Early Access 1.9.0-ea-b19 9-ea 9-ea+19 9-ea
// Major 1.9.0-b100 9 9+100 9
// Security #1 1.9.0_5-b20 9u5 9.0.1+20 9.0.1
// Security #2 1.9.0_11-b12 9u11 9.0.2+12 9.0.2
// Minor #1 1.9.0_20-b62 9u20 9.1.2+62 9.1.2
// Security #3 1.9.0_25-b15 9u25 9.1.3+15 9.1.3
// Security #4 1.9.0_31-b08 9u31 9.1.4+8 9.1.4
// Minor #2 1.9.0_40-b45 9u40 9.2.4+45 9.2.4
//
// What does this mean for us? In terms of the version selected, use semver-compliant strings when available.
//
// In terms of where to get the version:
//
// SEMANTIC_VERSION Reasonably prevalent, but most accurate in terms of comparable versions
// JAVA_RUNTIME_VERSION Reasonable prevalent, but difficult to distinguish pre-release info vs aux info (jep 223 sensitive)
// FULL_VERSION Reasonable prevalent, but difficult to distinguish pre-release info vs aux info (jep 223 sensitive)
// JAVA_VERSION Most prevalent, but least specific (jep 223 sensitive)
// IMPLEMENTOR_VERSION Unusable or missing in some cases
func jvmPackageVersion(ri *pkg.JavaVMRelease) string {
var version string
switch {
case ri.JavaRuntimeVersion != "":
return ri.JavaRuntimeVersion
case ri.FullVersion != "":
// if the full version major version matches the java version major version, then use the full version
fullMajor := strings.Split(ri.FullVersion, ".")[0]
javaMajor := strings.Split(ri.JavaVersion, ".")[0]
if fullMajor == javaMajor {
return ri.FullVersion
}
fallthrough
case ri.JavaVersion != "":
return ri.JavaVersion
}
return version
}
func trim0sFromLeft(v string) string {
if v == "0" {
return v
}
return strings.TrimLeft(v, "0")
}
func parseJvmReleaseInfo(r io.ReadCloser) (*pkg.JavaVMRelease, error) {
defer r.Close()
data := make(map[string]any)
scanner := bufio.NewScanner(io.LimitReader(r, 500*stereoFile.KB))
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := parts[0]
value := strings.Trim(parts[1], `"`)
if key == "MODULES" {
data[key] = strings.Split(value, " ")
} else {
data[key] = value
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
// if we're missing key fields, then we don't have a JVM release file
if data["JAVA_VERSION"] == nil && data["JAVA_RUNTIME_VERSION"] == nil {
return nil, nil
}
var ri pkg.JavaVMRelease
if err := mapstructure.Decode(data, &ri); err != nil {
return nil, err
}
return &ri, nil
}
@@ -0,0 +1,438 @@
package java
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
func TestJvmCpes(t *testing.T) {
tests := []struct {
name string
pkgVersion string
primaryVendor string
primaryProduct string
imageType string
hasJdk bool
expected []cpe.CPE
}{
{
name: "zulu release",
pkgVersion: "9.0.1+20",
primaryVendor: "azul",
primaryProduct: "zulu",
imageType: "jdk",
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "azul",
Product: "zulu",
Version: "9.0.1",
Update: "",
},
Source: cpe.DeclaredSource,
},
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "openjdk",
Version: "9.0.1",
Update: "",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "sun release",
pkgVersion: "1.6.0_322-b002",
primaryVendor: "sun",
primaryProduct: "jre",
imageType: "jre",
hasJdk: true,
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "sun",
Product: "jre",
Version: "1.6.0",
Update: "update322",
},
Source: cpe.DeclaredSource,
},
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "sun",
Product: "jdk",
Version: "1.6.0",
Update: "update322",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "oracle se release",
pkgVersion: "1.8.0_322-b02",
primaryVendor: "oracle",
primaryProduct: "java_se",
imageType: "jdk",
hasJdk: true,
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "java_se",
Version: "1.8.0",
Update: "update322",
},
Source: cpe.DeclaredSource,
},
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "jre",
Version: "1.8.0",
Update: "update322",
},
Source: cpe.DeclaredSource,
},
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "jdk",
Version: "1.8.0",
Update: "update322",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "JEP 223 version with build info",
pkgVersion: "9.0.1+20",
primaryVendor: "oracle",
primaryProduct: "openjdk",
imageType: "openjdk",
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "openjdk",
Version: "9.0.1",
Update: "",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "JEP 223 version without build info",
pkgVersion: "11.0.9",
primaryVendor: "oracle",
primaryProduct: "openjdk",
imageType: "openjdk",
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "openjdk",
Version: "11.0.9",
Update: "",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "no plus sign in version string",
pkgVersion: "1.8.0",
primaryVendor: "oracle",
primaryProduct: "openjdk",
imageType: "openjdk",
expected: []cpe.CPE{
{
Attributes: cpe.Attributes{
Part: "a",
Vendor: "oracle",
Product: "openjdk",
Version: "1.8.0",
Update: "",
},
Source: cpe.DeclaredSource,
},
},
},
{
name: "empty version string",
pkgVersion: "",
primaryVendor: "oracle",
primaryProduct: "",
imageType: "",
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := jvmCpes(tt.pkgVersion, tt.primaryVendor, tt.primaryProduct, tt.imageType, tt.hasJdk)
assert.Equal(t, tt.expected, result)
})
}
}
func TestJvmVersion(t *testing.T) {
tests := []struct {
name string
input *pkg.JavaVMRelease
expected string
}{
{
name: "JavaRuntimeVersion fallback",
input: &pkg.JavaVMRelease{
JavaRuntimeVersion: "21.0.4+7-LTS",
JavaVersion: "bogus",
FullVersion: "bogus",
SemanticVersion: "bogus",
},
expected: "21.0.4+7-LTS",
},
{
name: "JavaVersion fallback",
input: &pkg.JavaVMRelease{
JavaVersion: "21.0.4",
FullVersion: "bogus",
SemanticVersion: "bogus",
},
expected: "21.0.4",
},
{
// there is an example of this in eclipse-temurin:8u312-b07-jdk
name: "FullVersion is more accurate",
input: &pkg.JavaVMRelease{
JavaVersion: "1.8.0_131",
FullVersion: "1.8.0_131+b08",
},
expected: "1.8.0_131+b08",
},
{
name: "empty input fields",
input: &pkg.JavaVMRelease{},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := jvmPackageVersion(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetJVMVersionAndUpdate(t *testing.T) {
tests := []struct {
name string
version string
expectedVer string
expectedUpdate string
}{
{
name: "legacy version with underscore and build",
version: "1.8.0_302-b08",
expectedVer: "1.8.0",
expectedUpdate: "302",
},
{
name: "legacy version with underscore but no build",
version: "1.8.0_302",
expectedVer: "1.8.0",
expectedUpdate: "302",
},
{
name: "JEP 223 version with plus sign",
version: "9.0.1+20",
expectedVer: "9.0.1",
expectedUpdate: "",
},
{
name: "JEP 223 version with plus but no update",
version: "11.0.9+",
expectedVer: "11.0.9",
expectedUpdate: "",
},
{
name: "modern version without plus or underscore",
version: "11.0.9",
expectedVer: "11.0.9",
expectedUpdate: "",
},
{
name: "legacy version without underscore or plus",
version: "1.7.0",
expectedVer: "1.7.0",
expectedUpdate: "",
},
{
name: "empty version string",
version: "",
expectedVer: "",
expectedUpdate: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ver, update := getJVMVersionAndUpdate(tt.version)
assert.Equal(t, tt.expectedVer, ver)
assert.Equal(t, tt.expectedUpdate, update)
})
}
}
func TestJvmPrimaryVendorProduct(t *testing.T) {
tests := []struct {
name string
implementor string
path string
imageType string
hasJdk bool
expectedVendor string
expectedProduct string
}{
{
name: "Azul implementor with Zulu in path",
implementor: "Azul Systems",
path: "/usr/lib/jvm/zulu-11-amd64/release",
imageType: "JDK",
hasJdk: true,
expectedVendor: "azul",
expectedProduct: "zulu",
},
{
name: "Sun implementor with JDK",
implementor: "Sun Microsystems",
path: "/usr/lib/jvm/jdk-1.8-sun-amd64/release",
imageType: "JDK",
hasJdk: true,
expectedVendor: "sun",
expectedProduct: "jdk",
},
{
name: "Oracle implementor with JRE",
implementor: "Oracle Corporation",
path: "/usr/lib/jvm/jdk-1.8-oracle-x64/release",
imageType: "JRE",
hasJdk: false,
expectedVendor: "oracle",
expectedProduct: "jre",
},
{
name: "Oracle vendor with JDK in path",
implementor: "",
path: "/usr/lib/jvm/jdk-1.8-oracle-x64/release",
imageType: "JDK",
hasJdk: true,
expectedVendor: "oracle",
expectedProduct: "jdk",
},
{
name: "OpenJDK with JDK",
implementor: "OpenJDK",
path: "/opt/java/openjdk/release",
imageType: "JDK",
hasJdk: true,
expectedVendor: "oracle", // like temurin
expectedProduct: "openjdk",
},
{
name: "Amazon Corretto with JDK",
implementor: "Amazon Corretto",
path: "/usr/lib/jvm/java-17-amazon-corretto/release",
imageType: "JDK",
hasJdk: true,
expectedVendor: "oracle", // corretto upstream is oracle openjdk
expectedProduct: "openjdk",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
vendor, product := jvmPrimaryVendorProduct(tt.implementor, tt.path, tt.imageType, tt.hasJdk)
assert.Equal(t, tt.expectedVendor, vendor)
assert.Equal(t, tt.expectedProduct, product)
})
}
}
func TestJvmPurl(t *testing.T) {
tests := []struct {
name string
ri pkg.JavaVMRelease
version string
vendor string
product string
expectedPURL string
}{
{
name: "build source repo provided",
ri: pkg.JavaVMRelease{
BuildSourceRepo: "https://github.com/adoptium/temurin-build.git",
},
version: "21.0.4",
vendor: "oracle",
product: "jdk",
expectedPURL: "pkg:generic/oracle/jdk@21.0.4?repository_url=https://github.com/adoptium/temurin-build.git",
},
{
name: "source repo provided, no build source repo",
ri: pkg.JavaVMRelease{
SourceRepo: "https://github.com/adoptium/jdk21u.git",
},
version: "21.0.4",
vendor: "azul",
product: "zulu",
expectedPURL: "pkg:generic/azul/zulu@21.0.4?repository_url=https://github.com/adoptium/jdk21u.git",
},
{
name: "no repository URLs provided",
ri: pkg.JavaVMRelease{
// No repository URLs provided
},
version: "17.0.2",
vendor: "oracle",
product: "jdk",
expectedPURL: "pkg:generic/oracle/jdk@17.0.2",
},
{
name: "JRE with source repo",
ri: pkg.JavaVMRelease{
SourceRepo: "https://github.com/adoptium/jre-repo.git",
},
version: "1.8.0_302",
vendor: "oracle",
product: "jre",
expectedPURL: "pkg:generic/oracle/jre@1.8.0_302?repository_url=https://github.com/adoptium/jre-repo.git",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actualPURL := jvmPurl(tt.ri, tt.version, tt.vendor, tt.product)
assert.Equal(t, tt.expectedPURL, actualPURL)
})
}
}
@@ -0,0 +1 @@
!/jdk-1.8-oracle-x64/bin
@@ -0,0 +1,7 @@
JAVA_VERSION="1.8.0_411"
JAVA_RUNTIME_VERSION="1.8.0_411-b25"
OS_NAME="Linux"
OS_VERSION="2.6"
OS_ARCH="amd64"
SOURCE=".:git:71ec2089cf8c+"
BUILD_TYPE="commercial"
@@ -0,0 +1,19 @@
IMPLEMENTOR="Eclipse Adoptium"
IMPLEMENTOR_VERSION="Temurin-21.0.4+7"
JAVA_RUNTIME_VERSION="21.0.4+7-LTS"
JAVA_VERSION="21.0.4"
JAVA_VERSION_DATE="2024-07-16"
LIBC="gnu"
MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.jvmstat jdk.attach jdk.charsets jdk.internal.opt jdk.zipfs jdk.compiler jdk.crypto.ec jdk.crypto.cryptoki jdk.dynalink jdk.internal.ed jdk.editpad jdk.hotspot.agent jdk.httpserver jdk.incubator.vector jdk.internal.le jdk.internal.vm.ci jdk.internal.vm.compiler jdk.internal.vm.compiler.management jdk.jartool jdk.javadoc jdk.jcmd jdk.management jdk.management.agent jdk.jconsole jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.jpackage jdk.jshell jdk.jsobject jdk.jstatd jdk.localedata jdk.management.jfr jdk.naming.dns jdk.naming.rmi jdk.net jdk.nio.mapmode jdk.random jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom"
OS_ARCH="aarch64"
OS_NAME="Linux"
SOURCE=".:git:13710926b798"
BUILD_SOURCE="git:1271f10a26c47e1489a814dd2731f936a588d621"
BUILD_SOURCE_REPO="https://github.com/adoptium/temurin-build.git"
SOURCE_REPO="https://github.com/adoptium/jdk21u.git"
FULL_VERSION="21.0.4+7-LTS"
SEMANTIC_VERSION="21.0.4+7"
BUILD_INFO="OS: Linux Version: 5.4.0-150-generic"
JVM_VARIANT="Hotspot"
JVM_VERSION="21.0.4+7-LTS"
IMAGE_TYPE="JDK"