clair/ext/vulnsrc/debian/debian.go

271 lines
7.7 KiB
Go
Raw Normal View History

// Copyright 2017 clair authors
2015-11-13 19:11:28 +00:00
//
// 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 debian implements a vulnerability source updater using the Debian
// Security Tracker.
package debian
2015-11-13 19:11:28 +00:00
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
log "github.com/sirupsen/logrus"
2015-11-13 19:11:28 +00:00
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt"
"github.com/coreos/clair/ext/versionfmt/dpkg"
"github.com/coreos/clair/ext/vulnsrc"
2017-01-13 07:08:52 +00:00
"github.com/coreos/clair/pkg/commonerr"
2015-11-13 19:11:28 +00:00
)
const (
url = "https://security-tracker.debian.org/tracker/data/json"
cveURLPrefix = "https://security-tracker.debian.org/tracker"
updaterFlag = "debianUpdater"
2015-11-13 19:11:28 +00:00
)
type jsonData map[string]map[string]jsonVuln
type jsonVuln struct {
Description string `json:"description"`
Releases map[string]jsonRel `json:"releases"`
}
type jsonRel struct {
FixedVersion string `json:"fixed_version"`
Status string `json:"status"`
Urgency string `json:"urgency"`
}
type updater struct{}
2015-11-13 19:11:28 +00:00
func init() {
vulnsrc.RegisterUpdater("debian", &updater{})
2015-11-13 19:11:28 +00:00
}
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
log.WithField("package", "Debian").Info("Start fetching vulnerabilities")
2015-11-13 19:11:28 +00:00
tx, err := datastore.Begin()
2015-11-13 19:11:28 +00:00
if err != nil {
return resp, err
2015-11-13 19:11:28 +00:00
}
// Get the SHA-1 of the latest update's JSON data
latestHash, ok, err := tx.FindKeyValue(updaterFlag)
2015-11-13 19:11:28 +00:00
if err != nil {
return resp, err
}
// NOTE(sida): The transaction won't mutate the database and I want the
// transaction to be short.
if err := tx.Rollback(); err != nil {
return resp, err
}
if !ok {
latestHash = ""
}
// Download JSON.
r, err := http.Get(url)
if err != nil {
log.WithError(err).Error("could not download Debian's update")
return resp, commonerr.ErrCouldNotDownload
}
2015-11-13 19:11:28 +00:00
// Parse the JSON.
resp, err = buildResponse(r.Body, latestHash)
if err != nil {
return resp, err
}
return resp, nil
}
func (u *updater) Clean() {}
func buildResponse(jsonReader io.Reader, latestKnownHash string) (resp vulnsrc.UpdateResponse, err error) {
2015-11-13 19:11:28 +00:00
hash := latestKnownHash
// Defer the addition of flag information to the response.
defer func() {
if err == nil {
resp.FlagName = updaterFlag
2015-11-13 19:11:28 +00:00
resp.FlagValue = hash
}
}()
// Create a TeeReader so that we can unmarshal into JSON and write to a SHA-1
// digest at the same time.
jsonSHA := sha1.New()
teedJSONReader := io.TeeReader(jsonReader, jsonSHA)
// Unmarshal JSON.
var data jsonData
err = json.NewDecoder(teedJSONReader).Decode(&data)
if err != nil {
log.WithError(err).Error("could not unmarshal Debian's JSON")
2017-01-13 07:08:52 +00:00
return resp, commonerr.ErrCouldNotParse
2015-11-13 19:11:28 +00:00
}
// Calculate the hash and skip updating if the hash has been seen before.
hash = hex.EncodeToString(jsonSHA.Sum(nil))
if latestKnownHash == hash {
log.WithField("package", "Debian").Debug("no update")
2015-11-13 19:11:28 +00:00
return resp, nil
}
// Extract vulnerability data from Debian's JSON schema.
var unknownReleases map[string]struct{}
2016-01-20 00:17:08 +00:00
resp.Vulnerabilities, unknownReleases = parseDebianJSON(&data)
2015-11-13 19:11:28 +00:00
// Log unknown releases
for k := range unknownReleases {
note := fmt.Sprintf("Debian %s is not mapped to any version number (eg. Jessie->8). Please update me.", k)
resp.Notes = append(resp.Notes, note)
log.Warning(note)
}
return resp, nil
}
func parseDebianJSON(data *jsonData) (vulnerabilities []database.VulnerabilityWithAffected, unknownReleases map[string]struct{}) {
mvulnerabilities := make(map[string]*database.VulnerabilityWithAffected)
2015-11-13 19:11:28 +00:00
unknownReleases = make(map[string]struct{})
for pkgName, pkgNode := range *data {
for vulnName, vulnNode := range pkgNode {
for releaseName, releaseNode := range vulnNode.Releases {
// Attempt to detect the release number.
if _, isReleaseKnown := database.DebianReleasesMapping[releaseName]; !isReleaseKnown {
unknownReleases[releaseName] = struct{}{}
continue
}
// Skip if the status is not determined or the vulnerability is a temporary one.
// TODO: maybe add "undetermined" as Unknown severity.
if !strings.HasPrefix(vulnName, "CVE-") || releaseNode.Status == "undetermined" {
2015-11-13 19:11:28 +00:00
continue
}
// Get or create the vulnerability.
2016-01-25 19:50:48 +00:00
vulnerability, vulnerabilityAlreadyExists := mvulnerabilities[vulnName]
2015-11-13 19:11:28 +00:00
if !vulnerabilityAlreadyExists {
vulnerability = &database.VulnerabilityWithAffected{
Vulnerability: database.Vulnerability{
Name: vulnName,
Link: strings.Join([]string{cveURLPrefix, "/", vulnName}, ""),
Severity: database.UnknownSeverity,
Description: vulnNode.Description,
},
2015-11-13 19:11:28 +00:00
}
}
// Set the priority of the vulnerability.
// In the JSON, a vulnerability has one urgency per package it affects.
severity := SeverityFromUrgency(releaseNode.Urgency)
if severity.Compare(vulnerability.Severity) > 0 {
// The highest urgency should be the one set.
vulnerability.Severity = severity
2015-11-13 19:11:28 +00:00
}
// Determine the version of the package the vulnerability affects.
var version string
2015-11-13 19:11:28 +00:00
var err error
if releaseNode.Status == "open" {
2015-11-13 19:11:28 +00:00
// Open means that the package is currently vulnerable in the latest
// version of this Debian release.
version = versionfmt.MaxVersion
2015-11-13 19:11:28 +00:00
} else if releaseNode.Status == "resolved" {
// Resolved means that the vulnerability has been fixed in
// "fixed_version" (if affected).
err = versionfmt.Valid(dpkg.ParserName, releaseNode.FixedVersion)
2015-11-13 19:11:28 +00:00
if err != nil {
log.WithError(err).WithField("version", version).Warning("could not parse package version. skipping")
2015-11-13 19:11:28 +00:00
continue
}
// FixedVersion = "0" means that the vulnerability affecting
// current feature is not that important
if releaseNode.FixedVersion != "0" {
version = releaseNode.FixedVersion
}
}
if version == "" {
continue
}
var fixedInVersion string
if version != versionfmt.MaxVersion {
fixedInVersion = version
2015-11-13 19:11:28 +00:00
}
2016-01-20 00:17:08 +00:00
// Create and add the feature version.
pkg := database.AffectedFeature{
FeatureName: pkgName,
AffectedVersion: version,
FixedInVersion: fixedInVersion,
Namespace: database.Namespace{
Name: "debian:" + database.DebianReleasesMapping[releaseName],
VersionFormat: dpkg.ParserName,
},
2015-11-13 19:11:28 +00:00
}
vulnerability.Affected = append(vulnerability.Affected, pkg)
2015-11-13 19:11:28 +00:00
// Store the vulnerability.
2016-01-25 19:50:48 +00:00
mvulnerabilities[vulnName] = vulnerability
2015-11-13 19:11:28 +00:00
}
}
}
// Convert the vulnerabilities map to a slice
for _, v := range mvulnerabilities {
2016-01-20 00:17:08 +00:00
vulnerabilities = append(vulnerabilities, *v)
}
2015-11-13 19:11:28 +00:00
return
}
// SeverityFromUrgency converts the urgency scale used by the Debian Security
// Bug Tracker into a database.Severity.
func SeverityFromUrgency(urgency string) database.Severity {
2015-11-13 19:11:28 +00:00
switch urgency {
case "not yet assigned":
return database.UnknownSeverity
2015-11-13 19:11:28 +00:00
case "end-of-life", "unimportant":
return database.NegligibleSeverity
2015-11-13 19:11:28 +00:00
case "low", "low*", "low**":
return database.LowSeverity
2015-11-13 19:11:28 +00:00
case "medium", "medium*", "medium**":
return database.MediumSeverity
2015-11-13 19:11:28 +00:00
case "high", "high*", "high**":
return database.HighSeverity
2015-11-13 19:11:28 +00:00
default:
log.WithField("urgency", urgency).Warning("could not determine vulnerability severity from urgency")
return database.UnknownSeverity
2015-11-13 19:11:28 +00:00
}
}