clair/ext/vulnsrc/arch/arch.go
Heitham Omar ccb4ca4185 Introduce image scanning for Arch Linux
Handle scanning of Arch Linux images.
2018-04-11 20:00:11 +01:00

203 lines
5.3 KiB
Go

// Copyright 2017-2018 clair authors
//
// 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 arch implements a vulnerability source updater using the
// Arch Linux Security Tracker.
package arch
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"strings"
log "github.com/sirupsen/logrus"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt/rpm"
"github.com/coreos/clair/ext/vulnsrc"
"github.com/coreos/clair/pkg/commonerr"
)
const (
url = "https://security.archlinux.org/all.json"
cveURLPrefix = "https://security.archlinux.org"
updaterFlag = "archUpdater"
)
type jsonData []jsonVuln
type jsonVuln struct {
Name string `json:"name"`
Packages []string `json:"packages"`
Status string `json:"status"`
Severity string `json:"severity"`
Type string `json:"type"`
Affected string `json:"affected"`
Fixed string `json:"fixed"`
Ticket string `json:"ticket"`
Issues []string `json:"issues"`
Advisories []string `json:"advisories"`
}
type updater struct{}
func init() {
vulnsrc.RegisterUpdater("arch", &updater{})
}
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
log.WithField("package", "Arch Linux").Info("Start fetching vulnerabilities")
tx, err := datastore.Begin()
if err != nil {
return resp, err
}
// Get the SHA-1 of the latest update's JSON data
latestHash, ok, err := tx.FindKeyValue(updaterFlag)
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 Arch Linux update")
return resp, commonerr.ErrCouldNotDownload
}
// 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) {
hash := latestKnownHash
// Defer the addition of flag information to the response.
defer func() {
if err == nil {
resp.FlagName = updaterFlag
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 Arch Linux JSON")
return resp, commonerr.ErrCouldNotParse
}
// 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", "Arch Linux").Debug("no update")
return resp, nil
}
// Extract vulnerability data from Arch Linux JSON schema.
resp.Vulnerabilities = parseArchLinuxJSON(&data)
return resp, nil
}
func parseArchLinuxJSON(data *jsonData) (vulnerabilities []database.VulnerabilityWithAffected) {
mvulnerabilities := make(map[string]*database.VulnerabilityWithAffected)
for _, vulnNode := range *data {
for _, vulnName := range vulnNode.Issues {
// Get or create the vulnerability.
vulnerability, vulnerabilityAlreadyExists := mvulnerabilities[vulnName]
if !vulnerabilityAlreadyExists {
vulnerability = &database.VulnerabilityWithAffected{
Vulnerability: database.Vulnerability{
Name: vulnName,
Link: strings.Join([]string{cveURLPrefix, "/", vulnName}, ""),
Severity: database.UnknownSeverity,
Description: vulnNode.Type,
},
}
}
vulnerability.Severity = severity(vulnNode.Severity)
for _, pkgName := range vulnNode.Packages {
// Create and add the feature version.
pkg := database.AffectedFeature{
FeatureName: pkgName,
AffectedVersion: vulnNode.Affected,
FixedInVersion: vulnNode.Fixed,
Namespace: database.Namespace{
Name: "arch",
VersionFormat: rpm.ParserName,
},
}
vulnerability.Affected = append(vulnerability.Affected, pkg)
}
// Store the vulnerability.
mvulnerabilities[vulnName] = vulnerability
}
}
// Convert the vulnerabilities map to a slice
for _, v := range mvulnerabilities {
vulnerabilities = append(vulnerabilities, *v)
}
return
}
func severity(severity string) database.Severity {
switch severity {
case "Low":
return database.LowSeverity
case "Medium":
return database.MediumSeverity
case "High":
return database.HighSeverity
case "Critical":
return database.CriticalSeverity
default:
log.Warning("could not determine vulnerability severity from: %s.", severity)
return database.UnknownSeverity
}
}