parent
db6379bc9e
commit
8397359296
@ -0,0 +1,198 @@
|
||||
package grafeas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/grafeas/client-go/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
Addr string
|
||||
ProjectId string
|
||||
}
|
||||
|
||||
type Grafeas struct {
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewGrafeas(config *Config) Grafeas {
|
||||
return Grafeas{config}
|
||||
}
|
||||
|
||||
func (g *Grafeas) Export(datastore database.Datastore) error {
|
||||
if !g.Config.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("exporting vulnerabilities to Grafeas")
|
||||
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
vulnerabilities, err := tx.ListVulnerabilities()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pID := g.Config.ProjectId
|
||||
client := v1alpha1.NewGrafeasApiWithBasePath(g.Config.Addr)
|
||||
for _, vuln := range vulnerabilities {
|
||||
nID := vuln.Name
|
||||
score, nistVectors := extractMetadata(vuln.Metadata)
|
||||
note, _, err := client.GetNote(pID, nID)
|
||||
createNewNote := false
|
||||
if err != nil {
|
||||
n := noteWithoutDetails(pID, nID, vuln.Description, string(vuln.Severity), nistVectors, score)
|
||||
note = &n
|
||||
createNewNote = true
|
||||
}
|
||||
|
||||
containsUpdatedDetail := false
|
||||
for _, affected := range vuln.Affected {
|
||||
cpeUri := createCpeUri(affected.Namespace.Name)
|
||||
detail := detail(cpeUri, affected.FeatureName, vuln.Description, string(vuln.Severity), affected.FixedInVersion)
|
||||
index := findDetail(note.VulnerabilityType.Details, detail)
|
||||
if index == -1 {
|
||||
note.VulnerabilityType.Details = append(note.VulnerabilityType.Details, detail)
|
||||
containsUpdatedDetail = true
|
||||
} else if !reflect.DeepEqual(note.VulnerabilityType.Details[index], detail) {
|
||||
note.VulnerabilityType.Details[index] = detail
|
||||
containsUpdatedDetail = true
|
||||
}
|
||||
}
|
||||
|
||||
if createNewNote {
|
||||
_, _, err = client.CreateNote(pID, nID, *note)
|
||||
} else if containsUpdatedDetail {
|
||||
_, _, err = client.UpdateNote(pID, nID, *note)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Error creating note %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("export done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractMetadata(metadata map[string]interface{}) (score float32, vectors string) {
|
||||
if nvd, ok := metadata["NVD"].(map[string]interface{}); ok {
|
||||
if cvss, ok := nvd["CVSSv2"].(map[string]interface{}); ok {
|
||||
score = float32(cvss["Score"].(float64))
|
||||
vectors = cvss["Vectors"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
return score, vectors
|
||||
}
|
||||
|
||||
// Clair does not report cpe uri:s so we'll have to create one from the namespace.
|
||||
func createCpeUri(namespaceName string) string {
|
||||
ss := strings.Split(namespaceName, ":")
|
||||
if len(ss) != 2 {
|
||||
return "CPE_UNSPECIFIED"
|
||||
}
|
||||
os := ss[0]
|
||||
ver := ss[1]
|
||||
|
||||
switch os {
|
||||
case "alpine":
|
||||
return "cpe:/o:alpine:alpine_linux:" + ver
|
||||
case "debian":
|
||||
return "cpe:/o:debian:debian_linux:" + ver
|
||||
case "ubuntu":
|
||||
return "cpe:/o:canonical:ubuntu_linux:" + ver
|
||||
case "centos":
|
||||
return "cpe:/o:centos:centos:" + ver
|
||||
case "rhel":
|
||||
return "cpe:/o:redhat:enterprise_linux:" + ver
|
||||
case "fedora":
|
||||
return "cpe:/o:fedoraproject:fedora:" + ver
|
||||
}
|
||||
|
||||
return "CPE_UNSPECIFIED"
|
||||
}
|
||||
|
||||
func findDetail(details []v1alpha1.Detail, detail v1alpha1.Detail) int {
|
||||
for i, d := range details {
|
||||
if d.CpeUri == detail.CpeUri && d.Package_ == detail.Package_ {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func fixedLocation(fixedBy, cpeUri, pkg string) v1alpha1.VulnerabilityLocation {
|
||||
var version v1alpha1.Version
|
||||
if fixedBy == "" {
|
||||
version = v1alpha1.Version{
|
||||
Kind: "MAXIMUM",
|
||||
}
|
||||
} else {
|
||||
// EVR: Epoch:Version.Revision
|
||||
evrRegexp := regexp.MustCompile(`(?:(\d*):)?([\w~]+[\w.~]*)-(~?\w+[\w.]*)`)
|
||||
matches := evrRegexp.FindStringSubmatch(fixedBy)
|
||||
if len(matches) != 0 {
|
||||
versionEpoch, _ := strconv.ParseInt(matches[1], 10, 32)
|
||||
versionName := matches[2]
|
||||
versionRev := matches[3]
|
||||
version = v1alpha1.Version{
|
||||
Epoch: int32(versionEpoch),
|
||||
Name: versionName,
|
||||
Revision: versionRev,
|
||||
}
|
||||
} else {
|
||||
version = v1alpha1.Version{
|
||||
Name: fixedBy,
|
||||
}
|
||||
}
|
||||
}
|
||||
return v1alpha1.VulnerabilityLocation{
|
||||
CpeUri: cpeUri,
|
||||
Package_: pkg,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
func detail(cpeUri, packageName, description, severity, fixedBy string) v1alpha1.Detail {
|
||||
return v1alpha1.Detail{
|
||||
CpeUri: cpeUri,
|
||||
Package_: packageName,
|
||||
Description: description,
|
||||
MinAffectedVersion: v1alpha1.Version{
|
||||
Kind: "MINIMUM",
|
||||
},
|
||||
SeverityName: severity,
|
||||
FixedLocation: fixedLocation(fixedBy, cpeUri, packageName),
|
||||
}
|
||||
}
|
||||
|
||||
func noteWithoutDetails(pID, name, description, severity, nistVectors string, score float32) v1alpha1.Note {
|
||||
var longDescription string
|
||||
if nistVectors != "" {
|
||||
longDescription = fmt.Sprintf("NIST vectors: %v", nistVectors)
|
||||
}
|
||||
return v1alpha1.Note{
|
||||
Name: fmt.Sprintf("projects/%v/notes/%v", pID, name),
|
||||
ShortDescription: name,
|
||||
LongDescription: longDescription,
|
||||
Kind: "PACKAGE_VULNERABILITY",
|
||||
VulnerabilityType: v1alpha1.VulnerabilityType{
|
||||
CvssScore: score,
|
||||
Severity: severity,
|
||||
Details: []v1alpha1.Detail{},
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in new issue