providers/rbs/schema/convert.go (171 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"fmt"
"time"
"github.com/facebookincubator/flog"
nvd "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
"github.com/facebookincubator/nvdtools/wfn"
)
const (
cveDataVersion = "4.0"
timeLayout = "2006-01-02T15:04:05Z"
)
func (item *Vulnerability) Convert() (*nvd.NVDCVEFeedJSON10DefCVEItem, error) {
lastModifiedDate, err := convertTime(item.VulndbLastModified)
if err != nil {
return nil, fmt.Errorf("can't convert last modified date: %v", err)
}
publishedDate, err := convertTime(item.VulndbPublishedDate)
if err != nil {
return nil, fmt.Errorf("can't convert published date: %v", err)
}
impact, err := item.makeImpact()
if err != nil {
return nil, fmt.Errorf("can't create impact: %v", err)
}
nvdItem := nvd.NVDCVEFeedJSON10DefCVEItem{
CVE: &nvd.CVEJSON40{
CVEDataMeta: &nvd.CVEJSON40CVEDataMeta{
ID: item.ID(),
ASSIGNER: "rbs",
},
DataFormat: "MITRE",
DataType: "CVE",
DataVersion: cveDataVersion,
Description: &nvd.CVEJSON40Description{
DescriptionData: []*nvd.CVEJSON40LangString{
{Lang: "en", Value: item.Title},
{Lang: "en", Value: item.Description},
},
},
Problemtype: &nvd.CVEJSON40Problemtype{},
References: item.makeReferences(),
},
Configurations: item.makeConfigurations(),
Impact: impact,
LastModifiedDate: lastModifiedDate,
PublishedDate: publishedDate,
}
addNVDData(&nvdItem, item.NVDAdditionalInfo)
return &nvdItem, nil
}
func (item *Vulnerability) ID() string {
return fmt.Sprintf("rbs-%d", item.VulndbID)
}
func (item *Vulnerability) makeReferences() *nvd.CVEJSON40References {
if len(item.ExtReferences) == 0 {
return nil
}
var refsData []*nvd.CVEJSON40Reference
for _, ref := range item.ExtReferences {
refsData = append(refsData, &nvd.CVEJSON40Reference{
Name: ref.Type,
URL: ref.Value,
})
}
return &nvd.CVEJSON40References{
ReferenceData: refsData,
}
}
func (item *Vulnerability) makeConfigurations() *nvd.NVDCVEFeedJSON10DefConfigurations {
var matches []*nvd.NVDCVEFeedJSON10DefCPEMatch
for _, vendor := range item.Vendors {
for _, product := range vendor.Products {
for _, version := range product.Versions {
if version.Affected == "false" {
continue
}
for _, cpe := range version.CPEs {
c, err := normalizeCPE(cpe.CPE)
if err != nil {
flog.Errorf("couldn't normalize cpe %q: %v", cpe.CPE, err)
continue
}
match := &nvd.NVDCVEFeedJSON10DefCPEMatch{
Cpe23Uri: c,
Vulnerable: true,
}
matches = append(matches, match)
}
}
}
}
conf := nvd.NVDCVEFeedJSON10DefConfigurations{
CVEDataVersion: cveDataVersion,
Nodes: []*nvd.NVDCVEFeedJSON10DefNode{
&nvd.NVDCVEFeedJSON10DefNode{
CPEMatch: matches,
Operator: "OR",
},
},
}
return &conf
}
func (item *Vulnerability) makeImpact() (*nvd.NVDCVEFeedJSON10DefImpact, error) {
// TODO they don't have cvss vectors. they do have parts of it so we could construct them
// using our library nvdtools/cvss{2,3}/...
l2 := len(item.CVSSMetrics)
l3 := len(item.CVSS3Metrics)
if l2 == 0 && l3 == 0 {
return nil, fmt.Errorf("no cvss metrics found")
}
var cvssv2 *nvd.CVSSV20
if l2 != 0 {
cvssv2 = &nvd.CVSSV20{BaseScore: item.CVSSMetrics[l2-1].Score}
}
var cvssv3 *nvd.CVSSV30
if l3 != 0 {
cvssv3 = &nvd.CVSSV30{BaseScore: item.CVSS3Metrics[l3-1].Score}
}
impact := nvd.NVDCVEFeedJSON10DefImpact{
BaseMetricV2: &nvd.NVDCVEFeedJSON10DefImpactBaseMetricV2{CVSSV2: cvssv2},
BaseMetricV3: &nvd.NVDCVEFeedJSON10DefImpactBaseMetricV3{CVSSV3: cvssv3},
}
return &impact, nil
}
func convertTime(rbsTime string) (string, error) {
if rbsTime == "" { // handle no time
return "", nil
}
t, err := time.Parse(timeLayout, rbsTime)
if err != nil { // should be parsable
return "", err
}
return t.Format(nvd.TimeLayout), nil
}
func normalizeCPE(cpe string) (string, error) {
attrs, err := wfn.UnbindFmtString(cpe)
if err != nil {
return "", fmt.Errorf("can't unbind CPE URI: %v", err)
}
if attrs.Version == "Unspecified" {
attrs.Version = wfn.Any
}
return attrs.BindToFmtString(), nil
}
func addNVDData(nvdItem *nvd.NVDCVEFeedJSON10DefCVEItem, additional []*NVDAdditionalInfo) {
addRef := func(name, url string) {
nvdItem.CVE.References.ReferenceData = append(
nvdItem.CVE.References.ReferenceData,
&nvd.CVEJSON40Reference{
Name: name,
URL: url,
},
)
}
addCWE := func(cwe string) {
nvdItem.CVE.Problemtype.ProblemtypeData = append(
nvdItem.CVE.Problemtype.ProblemtypeData,
&nvd.CVEJSON40ProblemtypeProblemtypeData{
Description: []*nvd.CVEJSON40LangString{
{Lang: "en", Value: cwe},
},
},
)
}
for _, add := range additional {
addRef(add.CVEID, "")
for _, ref := range add.References {
addRef(ref.Name, ref.URL)
}
addCWE(add.CWEID)
}
}