Improve CPE generation for Jenkins/Jira plugins

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
This commit is contained in:
Dan Luhring 2021-04-18 08:01:22 -04:00
parent b301b56db1
commit 091fd1f0b0
No known key found for this signature in database
GPG Key ID: 9CEE23D079426CEF
2 changed files with 136 additions and 35 deletions

View File

@ -54,6 +54,9 @@ func (s candidateStore) getCandidates(t pkg.Type, key string) []string {
return value
}
const pomPropertiesGroupIDJenkinsPlugins = "com.cloudbees.jenkins.plugins"
const pomPropertiesGroupIDJiraPlugins = "com.atlassian.jira.plugins"
func newCPE(product, vendor, version, targetSW string) wfn.Attributes {
cpe := *(wfn.NewAttributesWithAny())
cpe.Part = "a"
@ -100,7 +103,7 @@ func candidateTargetSoftwareAttrs(p pkg.Package) []string {
var targetSw []string
switch p.Language {
case pkg.Java:
targetSw = append(targetSw, "java", "maven")
targetSw = append(targetSw, candidateTargetSoftwareAttrsForJava(p)...)
case pkg.JavaScript:
targetSw = append(targetSw, "node.js", "nodejs")
case pkg.Ruby:
@ -109,51 +112,130 @@ func candidateTargetSoftwareAttrs(p pkg.Package) []string {
targetSw = append(targetSw, "python")
}
if p.Type == pkg.JenkinsPluginPkg {
targetSw = append(targetSw, "jenkins", "cloudbees_jenkins")
}
return targetSw
}
func isJenkinsPlugin(p pkg.Package) bool {
if p.Type == pkg.JenkinsPluginPkg {
return true
}
if groupID := groupIDFromPomProperties(p); groupID == pomPropertiesGroupIDJenkinsPlugins {
return true
}
return false
}
func candidateTargetSoftwareAttrsForJava(p pkg.Package) []string {
// Use the more specific indicator if available
if isJenkinsPlugin(p) {
return []string{"jenkins", "cloudbees_jenkins"}
}
return []string{"java", "maven"}
}
func candidateVendors(p pkg.Package) []string {
// TODO: Confirm whether using products as vendors is helpful to the matching process
vendors := candidateProducts(p)
switch p.Language {
case pkg.Python:
vendors = append(vendors, fmt.Sprintf("python-%s", p.Name))
case pkg.Java:
if p.MetadataType == pkg.JavaMetadataType {
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
// derive the vendor from the groupID (e.g. org.sonatype.nexus --> sonatype)
if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") {
fields := strings.Split(metadata.PomProperties.GroupID, ".")
if len(fields) >= 3 {
vendors = append(vendors, fields[1])
}
}
}
vendors = append(vendors, candidateVendorsForJava(p)...)
}
}
return vendors
}
func candidateProducts(p pkg.Package) []string {
var products = []string{p.Name}
products := []string{p.Name}
if p.Language == pkg.Java {
if p.MetadataType == pkg.JavaMetadataType {
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
// derive the product from the groupID (e.g. org.sonatype.nexus --> nexus)
if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") {
fields := strings.Split(metadata.PomProperties.GroupID, ".")
if len(fields) >= 3 {
products = append(products, fields[2])
}
}
}
}
products = append(products, candidateProductsForJava(p)...)
}
// return any known product name swaps prepended to the results
return append(productCandidatesByPkgType.getCandidates(p.Type, p.Name), products...)
}
func candidateProductsForJava(p pkg.Package) []string {
if product, _ := productAndVendorFromPomPropertiesGroupID(p); product != "" {
return []string{product}
}
return nil
}
func candidateVendorsForJava(p pkg.Package) []string {
if _, vendor := productAndVendorFromPomPropertiesGroupID(p); vendor != "" {
return []string{vendor}
}
return nil
}
func productAndVendorFromPomPropertiesGroupID(p pkg.Package) (string, string) {
groupID := groupIDFromPomProperties(p)
if !shouldConsiderGroupID(groupID) {
return "", ""
}
if !hasAnyOfPrefixes(groupID, "com", "org") {
return "", ""
}
fields := strings.Split(groupID, ".")
if len(fields) < 3 {
return "", ""
}
product := fields[2]
vendor := fields[1]
return product, vendor
}
func groupIDFromPomProperties(p pkg.Package) string {
metadata, ok := p.Metadata.(pkg.JavaMetadata)
if !ok {
return ""
}
if metadata.PomProperties == nil {
return ""
}
return metadata.PomProperties.GroupID
}
func shouldConsiderGroupID(groupID string) bool {
if groupID == "" {
return false
}
excludedGroupIDs := []string{
pomPropertiesGroupIDJiraPlugins,
pomPropertiesGroupIDJenkinsPlugins,
}
for _, excludedGroupID := range excludedGroupIDs {
if groupID == excludedGroupID {
return false
}
}
return true
}
func hasAnyOfPrefixes(input string, prefixes ...string) bool {
for _, prefix := range prefixes {
if strings.HasPrefix(input, prefix) {
return true
}
}
return false
}

View File

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGenerate(t *testing.T) {
func TestGeneratePackageCPEs(t *testing.T) {
tests := []struct {
name string
p pkg.Package
@ -132,7 +132,7 @@ func TestGenerate(t *testing.T) {
},
},
{
name: "jenkins package",
name: "jenkins package identified via pkg type",
p: pkg.Package{
Name: "name",
Version: "3.2",
@ -142,13 +142,32 @@ func TestGenerate(t *testing.T) {
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
},
},
{
name: "jenkins package identified via groupId",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: "com.cloudbees.jenkins.plugins",
},
},
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
},
@ -165,13 +184,13 @@ func TestGenerate(t *testing.T) {
actualCpeSet.Add(a.BindToFmtString())
}
extra := strset.Difference(expectedCpeSet, actualCpeSet).List()
extra := strset.Difference(actualCpeSet, expectedCpeSet).List()
sort.Strings(extra)
for _, d := range extra {
t.Errorf("extra CPE: %+v", d)
}
missing := strset.Difference(actualCpeSet, expectedCpeSet).List()
missing := strset.Difference(expectedCpeSet, actualCpeSet).List()
sort.Strings(missing)
for _, d := range missing {
t.Errorf("missing CPE: %+v", d)