Integrated a fetcher for openSUSE and for SUSE Linux Enterprise
We extracted oval parser from rhel and used that for opensuse and SUSE Linux Enterpise Signed-off-by: Thomas Boerger <tboerger@suse.de> Signed-off-by: Jordi Massaguer Pla <jmassaguerpla@suse.de>
This commit is contained in:
parent
051564facd
commit
b8ceb0c461
@ -29,7 +29,9 @@ import (
|
||||
_ "github.com/coreos/clair/notifier/notifiers"
|
||||
|
||||
_ "github.com/coreos/clair/updater/fetchers/debian"
|
||||
_ "github.com/coreos/clair/updater/fetchers/opensuse"
|
||||
_ "github.com/coreos/clair/updater/fetchers/rhel"
|
||||
_ "github.com/coreos/clair/updater/fetchers/sle"
|
||||
_ "github.com/coreos/clair/updater/fetchers/ubuntu"
|
||||
_ "github.com/coreos/clair/updater/metadata_fetchers/nvd"
|
||||
|
||||
|
129
updater/fetchers/opensuse/opensuse.go
Normal file
129
updater/fetchers/opensuse/opensuse.go
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright 2015 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 opensuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/coreos/clair/updater"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/sle")
|
||||
|
||||
func init() {
|
||||
opensuseInfo := &OpenSUSEInfo{}
|
||||
|
||||
updater.RegisterFetcher(opensuseInfo.DistName(),
|
||||
&oval.OvalFetcher{OsInfo: opensuseInfo})
|
||||
}
|
||||
|
||||
// OpenSUSEInfo implements oval.OsInfo interface
|
||||
// See oval.OsInfo for more info on what each method is
|
||||
type OpenSUSEInfo struct {
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) SecToken() string {
|
||||
return "CVE"
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) IgnoredCriterions() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) OvalURI() string {
|
||||
return "http://ftp.suse.com/pub/projects/security/oval/"
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) DistName() string {
|
||||
return "opensuse"
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) Namespace() string {
|
||||
return f.DistName()
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) ParseOsVersion(comment string) string {
|
||||
return f.ParseOsVersionR(comment, f.CritSystem())
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) ParseOsVersionR(comment string, exp *regexp.Regexp) string {
|
||||
systemMatch := exp.FindStringSubmatch(comment)
|
||||
if len(systemMatch) < 2 {
|
||||
return ""
|
||||
}
|
||||
osVersion := systemMatch[1]
|
||||
if len(systemMatch) == 4 && systemMatch[3] != "" {
|
||||
sp := systemMatch[3]
|
||||
osVersion = fmt.Sprintf("%s.%s", osVersion, sp)
|
||||
}
|
||||
|
||||
return osVersion
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) ParsePackageNameVersion(comment string) (string, string) {
|
||||
packageMatch := f.CritPackage().FindStringSubmatch(comment)
|
||||
|
||||
if len(packageMatch) != 3 {
|
||||
return "", ""
|
||||
}
|
||||
name := packageMatch[1]
|
||||
version := packageMatch[2]
|
||||
return name, version
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) ParseFilenameDist(line string) string {
|
||||
return f.ParseFilenameDistR(line, f.DistRegexp(), f.DistMinVersion())
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) ParseFilenameDistR(line string, exp *regexp.Regexp, minVersion float64) string {
|
||||
r := exp.FindStringSubmatch(line)
|
||||
if len(r) != 2 {
|
||||
return ""
|
||||
}
|
||||
if r[0] == "" || r[1] == "" {
|
||||
return ""
|
||||
}
|
||||
distVersion, _ := strconv.ParseFloat(r[1], 32)
|
||||
if distVersion < minVersion {
|
||||
return ""
|
||||
}
|
||||
return f.DistFile(r[0])
|
||||
}
|
||||
|
||||
// These are not in the interface
|
||||
|
||||
func (f *OpenSUSEInfo) DistFile(item string) string {
|
||||
return f.OvalURI() + item
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) CritSystem() *regexp.Regexp {
|
||||
return regexp.MustCompile(`openSUSE [^0-9]*(\d+\.\d+)[^0-9]* is installed`)
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) CritPackage() *regexp.Regexp {
|
||||
return regexp.MustCompile(`(.*)-(.*\-[\d\.]+) is installed`)
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) DistRegexp() *regexp.Regexp {
|
||||
return regexp.MustCompile(`opensuse.[^0-9]*(\d+\.\d+).xml`)
|
||||
}
|
||||
|
||||
func (f *OpenSUSEInfo) DistMinVersion() float64 {
|
||||
return 13.1
|
||||
}
|
66
updater/fetchers/opensuse/opensuse_test.go
Normal file
66
updater/fetchers/opensuse/opensuse_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2015 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 opensuse
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOpenSUSEParser(t *testing.T) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
path := filepath.Join(filepath.Dir(filename))
|
||||
|
||||
// Test parsing testdata/fetcher_opensuse_test.1.xml
|
||||
testFile, _ := os.Open(path + "/testdata/fetcher_opensuse_test.1.xml")
|
||||
ov := &oval.OvalFetcher{OsInfo: &OpenSUSEInfo{}}
|
||||
vulnerabilities, err := ov.ParseOval(testFile)
|
||||
if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) {
|
||||
assert.Equal(t, "CVE-2012-2150", vulnerabilities[0].Name)
|
||||
assert.Equal(t, "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2150", vulnerabilities[0].Link)
|
||||
// Severity is not defined for openSUSE
|
||||
assert.Equal(t, types.Unknown, vulnerabilities[0].Severity)
|
||||
assert.Equal(t, `xfs_metadump in xfsprogs before 3.2.4 does not properly obfuscate file data, which allows remote attackers to obtain sensitive information by reading a generated image.`, vulnerabilities[0].Description)
|
||||
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "opensuse:42.1"},
|
||||
Name: "xfsprogs",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.2.1-5.1"),
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "opensuse:42.1"},
|
||||
Name: "xfsprogs-devel",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.2.1-5.1"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, expectedFeatureVersion := range expectedFeatureVersions {
|
||||
assert.Contains(t, vulnerabilities[0].FixedIn, expectedFeatureVersion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
66
updater/fetchers/opensuse/testdata/fetcher_opensuse_test.1.xml
vendored
Normal file
66
updater/fetchers/opensuse/testdata/fetcher_opensuse_test.1.xml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<oval_definitions
|
||||
xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd"
|
||||
xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
|
||||
xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5">
|
||||
<generator>
|
||||
<oval:product_name>Marcus Updateinfo to OVAL Converter</oval:product_name>
|
||||
<oval:schema_version>5.5</oval:schema_version>
|
||||
<oval:timestamp>2016-06-27T04:04:46</oval:timestamp>
|
||||
</generator>
|
||||
<definitions>
|
||||
<definition id="oval:org.opensuse.security:def:20122150" version="1" class="vulnerability">
|
||||
<metadata>
|
||||
<title>CVE-2012-2150</title>
|
||||
<affected family="unix">
|
||||
<platform>openSUSE Leap 42.1</platform>
|
||||
</affected>
|
||||
<reference ref_id="CVE-2012-2150" ref_url="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2150" source="CVE"/>
|
||||
<description>xfs_metadump in xfsprogs before 3.2.4 does not properly obfuscate file data, which allows remote attackers to obtain sensitive information by reading a generated image.</description>
|
||||
</metadata>
|
||||
<criteria operator="AND">
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009117743" comment="openSUSE Leap 42.1 is installed"/>
|
||||
<criteria operator="OR">
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009120999" comment="xfsprogs-3.2.1-5.1 is installed"/>
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009121000" comment="xfsprogs-devel-3.2.1-5.1 is installed"/>
|
||||
</criteria>
|
||||
</criteria>
|
||||
</definition>
|
||||
</definitions>
|
||||
<tests>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009117743" version="1" comment="openSUSE-release is ==42.1" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009031246"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009046321"/>
|
||||
</rpminfo_test>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009120999" version="1" comment="xfsprogs is <3.2.1-5.1" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009032555"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009046736"/>
|
||||
</rpminfo_test>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009121000" version="1" comment="xfsprogs-devel is <3.2.1-5.1" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009032648"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009046736"/>
|
||||
</rpminfo_test>
|
||||
</tests>
|
||||
<objects>
|
||||
<rpminfo_object id="oval:org.opensuse.security:obj:2009032648" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<name>xfsprogs-devel</name>
|
||||
</rpminfo_object>
|
||||
<rpminfo_object id="oval:org.opensuse.security:obj:2009031246" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<name>openSUSE-release</name>
|
||||
</rpminfo_object>
|
||||
<rpminfo_object id="oval:org.opensuse.security:obj:2009032555" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<name>xfsprogs</name>
|
||||
</rpminfo_object>
|
||||
</objects>
|
||||
<states>
|
||||
<rpminfo_state id="oval:org.opensuse.security:ste:2009046736" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<evr datatype="evr_string" operation="less than">0:3.2.1-5.1</evr>
|
||||
</rpminfo_state>
|
||||
<rpminfo_state id="oval:org.opensuse.security:ste:2009046321" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<version operation="equals">42.1</version>
|
||||
</rpminfo_state>
|
||||
</states>
|
||||
</oval_definitions>
|
@ -15,17 +15,12 @@
|
||||
package rhel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/updater"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
@ -34,327 +29,101 @@ const (
|
||||
// Before this RHSA, it deals only with RHEL <= 4.
|
||||
firstRHEL5RHSA = 20070044
|
||||
firstConsideredRHEL = 5
|
||||
|
||||
ovalURI = "https://www.redhat.com/security/data/oval/"
|
||||
rhsaFilePrefix = "com.redhat.rhsa-"
|
||||
updaterFlag = "rhelUpdater"
|
||||
)
|
||||
|
||||
var (
|
||||
ignoredCriterions = []string{
|
||||
rhsaRegexp = regexp.MustCompile(`com.redhat.rhsa-(\d+).xml`)
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/rhel")
|
||||
)
|
||||
|
||||
func init() {
|
||||
rhelInfo := &RHELInfo{}
|
||||
updater.RegisterFetcher(rhelInfo.DistName(),
|
||||
&oval.OvalFetcher{OsInfo: rhelInfo})
|
||||
}
|
||||
|
||||
// RHELInfo implements oval.OsInfo interface
|
||||
// See oval.OsInfo for more info on what each method is
|
||||
type RHELInfo struct {
|
||||
}
|
||||
|
||||
func (f *RHELInfo) DistFile(item string) string {
|
||||
rhsaFilePrefix := "com.redhat.rhsa-"
|
||||
return f.OvalURI() + rhsaFilePrefix + item + ".xml"
|
||||
}
|
||||
|
||||
func (f *RHELInfo) SecToken() string {
|
||||
return "RHSA"
|
||||
}
|
||||
|
||||
func (f *RHELInfo) IgnoredCriterions() []string {
|
||||
return []string{
|
||||
" is signed with Red Hat ",
|
||||
" Client is installed",
|
||||
" Workstation is installed",
|
||||
" ComputeNode is installed",
|
||||
}
|
||||
|
||||
rhsaRegexp = regexp.MustCompile(`com.redhat.rhsa-(\d+).xml`)
|
||||
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/rhel")
|
||||
)
|
||||
|
||||
type oval struct {
|
||||
Definitions []definition `xml:"definitions>definition"`
|
||||
}
|
||||
|
||||
type definition struct {
|
||||
Title string `xml:"metadata>title"`
|
||||
Description string `xml:"metadata>description"`
|
||||
References []reference `xml:"metadata>reference"`
|
||||
Criteria criteria `xml:"criteria"`
|
||||
func (f *RHELInfo) OvalURI() string {
|
||||
return "https://www.redhat.com/security/data/oval/"
|
||||
}
|
||||
|
||||
type reference struct {
|
||||
Source string `xml:"source,attr"`
|
||||
URI string `xml:"ref_url,attr"`
|
||||
func (f *RHELInfo) DistName() string {
|
||||
return "RHEL"
|
||||
}
|
||||
|
||||
type criteria struct {
|
||||
Operator string `xml:"operator,attr"`
|
||||
Criterias []*criteria `xml:"criteria"`
|
||||
Criterions []criterion `xml:"criterion"`
|
||||
func (f *RHELInfo) Namespace() string {
|
||||
// TODO this is where to set different labels for centos and rhel. See:
|
||||
// https://github.com/coreos/clair/commit/ce8d31bbb323471bf2a69427e4a645b3ce8a25c1
|
||||
// https://github.com/coreos/clair/pull/193
|
||||
return "centos"
|
||||
}
|
||||
|
||||
type criterion struct {
|
||||
Comment string `xml:"comment,attr"`
|
||||
func (f *RHELInfo) ParseOsVersion(comment string) string {
|
||||
if !strings.Contains(comment, " is installed") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// RHELFetcher implements updater.Fetcher and gets vulnerability updates from
|
||||
// the Red Hat OVAL definitions.
|
||||
type RHELFetcher struct{}
|
||||
|
||||
func init() {
|
||||
updater.RegisterFetcher("Red Hat", &RHELFetcher{})
|
||||
}
|
||||
|
||||
// FetchUpdate gets vulnerability updates from the Red Hat OVAL definitions.
|
||||
func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
||||
log.Info("fetching Red Hat vulnerabilities")
|
||||
|
||||
// Get the first RHSA we have to manage.
|
||||
flagValue, err := datastore.GetKeyValue(updaterFlag)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
firstRHSA, err := strconv.Atoi(flagValue)
|
||||
if firstRHSA == 0 || err != nil {
|
||||
firstRHSA = firstRHEL5RHSA
|
||||
}
|
||||
|
||||
// Fetch the update list.
|
||||
r, err := http.Get(ovalURI)
|
||||
if err != nil {
|
||||
log.Errorf("could not download RHEL's update list: %s", err)
|
||||
return resp, cerrors.ErrCouldNotDownload
|
||||
}
|
||||
|
||||
// Get the list of RHSAs that we have to process.
|
||||
var rhsaList []int
|
||||
scanner := bufio.NewScanner(r.Body)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
r := rhsaRegexp.FindStringSubmatch(line)
|
||||
if len(r) == 2 {
|
||||
rhsaNo, _ := strconv.Atoi(r[1])
|
||||
if rhsaNo > firstRHSA {
|
||||
rhsaList = append(rhsaList, rhsaNo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, rhsa := range rhsaList {
|
||||
// Download the RHSA's XML file.
|
||||
r, err := http.Get(ovalURI + rhsaFilePrefix + strconv.Itoa(rhsa) + ".xml")
|
||||
if err != nil {
|
||||
log.Errorf("could not download RHEL's update file: %s", err)
|
||||
return resp, cerrors.ErrCouldNotDownload
|
||||
}
|
||||
|
||||
// Parse the XML.
|
||||
vs, err := parseRHSA(r.Body)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Collect vulnerabilities.
|
||||
for _, v := range vs {
|
||||
resp.Vulnerabilities = append(resp.Vulnerabilities, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the flag if we found anything.
|
||||
if len(rhsaList) > 0 {
|
||||
resp.FlagName = updaterFlag
|
||||
resp.FlagValue = strconv.Itoa(rhsaList[len(rhsaList)-1])
|
||||
} else {
|
||||
log.Debug("no Red Hat update.")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
||||
// Decode the XML.
|
||||
var ov oval
|
||||
err = xml.NewDecoder(ovalReader).Decode(&ov)
|
||||
if err != nil {
|
||||
log.Errorf("could not decode RHEL's XML: %s", err)
|
||||
err = cerrors.ErrCouldNotParse
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate over the definitions and collect any vulnerabilities that affect
|
||||
// at least one package.
|
||||
for _, definition := range ov.Definitions {
|
||||
pkgs := toFeatureVersions(definition.Criteria)
|
||||
if len(pkgs) > 0 {
|
||||
vulnerability := database.Vulnerability{
|
||||
Name: name(definition),
|
||||
Link: link(definition),
|
||||
Severity: priority(definition),
|
||||
Description: description(definition),
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
vulnerability.FixedIn = append(vulnerability.FixedIn, p)
|
||||
}
|
||||
vulnerabilities = append(vulnerabilities, vulnerability)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getCriterions(node criteria) [][]criterion {
|
||||
// Filter useless criterions.
|
||||
var criterions []criterion
|
||||
for _, c := range node.Criterions {
|
||||
ignored := false
|
||||
|
||||
for _, ignoredItem := range ignoredCriterions {
|
||||
if strings.Contains(c.Comment, ignoredItem) {
|
||||
ignored = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ignored {
|
||||
criterions = append(criterions, c)
|
||||
}
|
||||
}
|
||||
|
||||
if node.Operator == "AND" {
|
||||
return [][]criterion{criterions}
|
||||
} else if node.Operator == "OR" {
|
||||
var possibilities [][]criterion
|
||||
for _, c := range criterions {
|
||||
possibilities = append(possibilities, []criterion{c})
|
||||
}
|
||||
return possibilities
|
||||
}
|
||||
|
||||
return [][]criterion{}
|
||||
}
|
||||
|
||||
func getPossibilities(node criteria) [][]criterion {
|
||||
if len(node.Criterias) == 0 {
|
||||
return getCriterions(node)
|
||||
}
|
||||
|
||||
var possibilitiesToCompose [][][]criterion
|
||||
for _, criteria := range node.Criterias {
|
||||
possibilitiesToCompose = append(possibilitiesToCompose, getPossibilities(*criteria))
|
||||
}
|
||||
if len(node.Criterions) > 0 {
|
||||
possibilitiesToCompose = append(possibilitiesToCompose, getCriterions(node))
|
||||
}
|
||||
|
||||
var possibilities [][]criterion
|
||||
if node.Operator == "AND" {
|
||||
for _, possibility := range possibilitiesToCompose[0] {
|
||||
possibilities = append(possibilities, possibility)
|
||||
}
|
||||
|
||||
for _, possibilityGroup := range possibilitiesToCompose[1:] {
|
||||
var newPossibilities [][]criterion
|
||||
|
||||
for _, possibility := range possibilities {
|
||||
for _, possibilityInGroup := range possibilityGroup {
|
||||
var p []criterion
|
||||
p = append(p, possibility...)
|
||||
p = append(p, possibilityInGroup...)
|
||||
newPossibilities = append(newPossibilities, p)
|
||||
}
|
||||
}
|
||||
|
||||
possibilities = newPossibilities
|
||||
}
|
||||
} else if node.Operator == "OR" {
|
||||
for _, possibilityGroup := range possibilitiesToCompose {
|
||||
for _, possibility := range possibilityGroup {
|
||||
possibilities = append(possibilities, possibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return possibilities
|
||||
}
|
||||
|
||||
func toFeatureVersions(criteria criteria) []database.FeatureVersion {
|
||||
// There are duplicates in Red Hat .xml files.
|
||||
// This map is for deduplication.
|
||||
featureVersionParameters := make(map[string]database.FeatureVersion)
|
||||
|
||||
possibilities := getPossibilities(criteria)
|
||||
for _, criterions := range possibilities {
|
||||
var (
|
||||
featureVersion database.FeatureVersion
|
||||
osVersion int
|
||||
err error
|
||||
)
|
||||
|
||||
// Attempt to parse package data from trees of criterions.
|
||||
for _, c := range criterions {
|
||||
if strings.Contains(c.Comment, " is installed") {
|
||||
const prefixLen = len("Red Hat Enterprise Linux ")
|
||||
osVersion, err = strconv.Atoi(strings.TrimSpace(c.Comment[prefixLen : prefixLen+strings.Index(c.Comment[prefixLen:], " ")]))
|
||||
if err != nil {
|
||||
log.Warningf("could not parse Red Hat release version from: '%s'.", c.Comment)
|
||||
osVersion := strings.TrimSpace(comment[prefixLen : prefixLen+strings.Index(comment[prefixLen:], " ")])
|
||||
if !f.ValidOsVersion(osVersion) {
|
||||
return ""
|
||||
}
|
||||
return osVersion
|
||||
}
|
||||
|
||||
func (f *RHELInfo) ParsePackageNameVersion(comment string) (string, string) {
|
||||
if !strings.Contains(comment, " is earlier than ") {
|
||||
return "", ""
|
||||
}
|
||||
} else if strings.Contains(c.Comment, " is earlier than ") {
|
||||
const prefixLen = len(" is earlier than ")
|
||||
featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
|
||||
featureVersion.Version, err = types.NewVersion(c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:])
|
||||
name := strings.TrimSpace(comment[:strings.Index(comment, " is earlier than ")])
|
||||
version := comment[strings.Index(comment, " is earlier than ")+prefixLen:]
|
||||
return name, version
|
||||
}
|
||||
|
||||
func (f *RHELInfo) ParseFilenameDist(line string) string {
|
||||
r := rhsaRegexp.FindStringSubmatch(line)
|
||||
if len(r) != 2 {
|
||||
return ""
|
||||
}
|
||||
rhsaNo, _ := strconv.Atoi(r[1])
|
||||
if rhsaNo <= firstRHEL5RHSA {
|
||||
return ""
|
||||
}
|
||||
return f.DistFile(r[1])
|
||||
}
|
||||
|
||||
// Not in the interface
|
||||
|
||||
func (f *RHELInfo) ValidOsVersion(osVersion string) bool {
|
||||
version, err := strconv.Atoi(osVersion)
|
||||
if err != nil {
|
||||
log.Warningf("could not parse package version '%s': %s. skipping", c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:], err.Error())
|
||||
return false
|
||||
}
|
||||
_, err = types.NewVersion(osVersion)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return version >= firstConsideredRHEL
|
||||
}
|
||||
|
||||
if osVersion >= firstConsideredRHEL {
|
||||
// TODO(vbatts) this is where features need multiple labels ('centos' and 'rhel')
|
||||
featureVersion.Feature.Namespace.Name = "centos" + ":" + strconv.Itoa(osVersion)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version.String() != "" {
|
||||
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||
} else {
|
||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the map to slice.
|
||||
var featureVersionParametersArray []database.FeatureVersion
|
||||
for _, fv := range featureVersionParameters {
|
||||
featureVersionParametersArray = append(featureVersionParametersArray, fv)
|
||||
}
|
||||
|
||||
return featureVersionParametersArray
|
||||
}
|
||||
|
||||
func description(def definition) (desc string) {
|
||||
// It is much more faster to proceed like this than using a Replacer.
|
||||
desc = strings.Replace(def.Description, "\n\n\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\n\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\n", " ", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func name(def definition) string {
|
||||
return strings.TrimSpace(def.Title[:strings.Index(def.Title, ": ")])
|
||||
}
|
||||
|
||||
func link(def definition) (link string) {
|
||||
for _, reference := range def.References {
|
||||
if reference.Source == "RHSA" {
|
||||
link = reference.URI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func priority(def definition) types.Priority {
|
||||
// Parse the priority.
|
||||
priority := strings.TrimSpace(def.Title[strings.LastIndex(def.Title, "(")+1 : len(def.Title)-1])
|
||||
|
||||
// Normalize the priority.
|
||||
switch priority {
|
||||
case "Low":
|
||||
return types.Low
|
||||
case "Moderate":
|
||||
return types.Medium
|
||||
case "Important":
|
||||
return types.High
|
||||
case "Critical":
|
||||
return types.Critical
|
||||
default:
|
||||
log.Warning("could not determine vulnerability priority from: %s.", priority)
|
||||
return types.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Clean deletes any allocated resources.
|
||||
func (f *RHELFetcher) Clean() {}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -31,7 +32,9 @@ func TestRHELParser(t *testing.T) {
|
||||
|
||||
// Test parsing testdata/fetcher_rhel_test.1.xml
|
||||
testFile, _ := os.Open(path + "/testdata/fetcher_rhel_test.1.xml")
|
||||
vulnerabilities, err := parseRHSA(testFile)
|
||||
rhInfo := &RHELInfo{}
|
||||
ov := &oval.OvalFetcher{OsInfo: rhInfo}
|
||||
vulnerabilities, err := ov.ParseOval(testFile)
|
||||
if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) {
|
||||
assert.Equal(t, "RHSA-2015:1193", vulnerabilities[0].Name)
|
||||
assert.Equal(t, "https://rhn.redhat.com/errata/RHSA-2015-1193.html", vulnerabilities[0].Link)
|
||||
@ -69,7 +72,7 @@ func TestRHELParser(t *testing.T) {
|
||||
|
||||
// Test parsing testdata/fetcher_rhel_test.2.xml
|
||||
testFile, _ = os.Open(path + "/testdata/fetcher_rhel_test.2.xml")
|
||||
vulnerabilities, err = parseRHSA(testFile)
|
||||
vulnerabilities, err = ov.ParseOval(testFile)
|
||||
if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) {
|
||||
assert.Equal(t, "RHSA-2015:1207", vulnerabilities[0].Name)
|
||||
assert.Equal(t, "https://rhn.redhat.com/errata/RHSA-2015-1207.html", vulnerabilities[0].Link)
|
||||
|
88
updater/fetchers/sle/sle.go
Normal file
88
updater/fetchers/sle/sle.go
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2015 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 sle
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/coreos/clair/updater"
|
||||
"github.com/coreos/clair/updater/fetchers/opensuse"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/sle")
|
||||
var opensuseInfo = &opensuse.OpenSUSEInfo{}
|
||||
|
||||
func init() {
|
||||
sleInfo := &SLEInfo{}
|
||||
|
||||
updater.RegisterFetcher(sleInfo.DistName(),
|
||||
&oval.OvalFetcher{OsInfo: sleInfo})
|
||||
}
|
||||
|
||||
// SLEInfo implements oval.OsInfo interface
|
||||
// See oval.OsInfo for more info on what each method is
|
||||
// SLE and openSUSE shares most of the code, there are just subtle diffs on
|
||||
// the name and versions of the distribution
|
||||
type SLEInfo struct {
|
||||
}
|
||||
|
||||
func (f *SLEInfo) SecToken() string {
|
||||
return opensuseInfo.SecToken()
|
||||
}
|
||||
|
||||
func (f *SLEInfo) IgnoredCriterions() []string {
|
||||
return opensuseInfo.IgnoredCriterions()
|
||||
}
|
||||
|
||||
func (f *SLEInfo) OvalURI() string {
|
||||
return opensuseInfo.OvalURI()
|
||||
}
|
||||
|
||||
// This differs from openSUSE
|
||||
func (f *SLEInfo) DistName() string {
|
||||
return "sle"
|
||||
}
|
||||
|
||||
func (f *SLEInfo) Namespace() string {
|
||||
return f.DistName()
|
||||
}
|
||||
|
||||
func (f *SLEInfo) ParseOsVersion(comment string) string {
|
||||
return opensuseInfo.ParseOsVersionR(comment, f.CritSystem())
|
||||
}
|
||||
|
||||
func (f *SLEInfo) ParsePackageNameVersion(comment string) (string, string) {
|
||||
return opensuseInfo.ParsePackageNameVersion(comment)
|
||||
}
|
||||
|
||||
func (f *SLEInfo) ParseFilenameDist(line string) string {
|
||||
return opensuseInfo.ParseFilenameDistR(line, f.DistRegexp(), f.DistMinVersion())
|
||||
}
|
||||
|
||||
// These are diffs with openSUSE
|
||||
|
||||
func (f *SLEInfo) CritSystem() *regexp.Regexp {
|
||||
return regexp.MustCompile(`SUSE Linux Enterprise Server [^0-9]*(\d+)\s*(SP(\d+)|) is installed`)
|
||||
}
|
||||
|
||||
func (f *SLEInfo) DistRegexp() *regexp.Regexp {
|
||||
return regexp.MustCompile(`suse.linux.enterprise.(\d+).xml`)
|
||||
}
|
||||
|
||||
func (f *SLEInfo) DistMinVersion() float64 {
|
||||
return 11.4
|
||||
}
|
67
updater/fetchers/sle/sle_test.go
Normal file
67
updater/fetchers/sle/sle_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2015 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 sle
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils/oval"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSLEParser(t *testing.T) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
path := filepath.Join(filepath.Dir(filename))
|
||||
|
||||
// Test parsing testdata/fetcher_sle_test.1.xml
|
||||
testFile, _ := os.Open(path + "/testdata/fetcher_sle_test.1.xml")
|
||||
ov := &oval.OvalFetcher{OsInfo: &SLEInfo{}}
|
||||
vulnerabilities, err := ov.ParseOval(testFile)
|
||||
if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) {
|
||||
assert.Equal(t, "CVE-2012-2150", vulnerabilities[0].Name)
|
||||
assert.Equal(t, "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2150", vulnerabilities[0].Link)
|
||||
// Severity is not defined for SLE
|
||||
assert.Equal(t, types.Unknown, vulnerabilities[0].Severity)
|
||||
assert.Equal(t, `xfs_metadump in xfsprogs before 3.2.4 does not properly obfuscate file data, which allows remote attackers to obtain sensitive information by reading a generated image.`, vulnerabilities[0].Description)
|
||||
|
||||
expectedFeatureVersions := []database.FeatureVersion{
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "sle:12"},
|
||||
Name: "xfsprogs",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.2.1-3.5"),
|
||||
},
|
||||
{
|
||||
Feature: database.Feature{
|
||||
Namespace: database.Namespace{Name: "sle:12.1"},
|
||||
Name: "xfsprogs",
|
||||
},
|
||||
Version: types.NewVersionUnsafe("3.2.1-3.5"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, expectedFeatureVersion := range expectedFeatureVersions {
|
||||
assert.Contains(t, vulnerabilities[0].FixedIn, expectedFeatureVersion)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
69
updater/fetchers/sle/testdata/fetcher_sle_test.1.xml
vendored
Normal file
69
updater/fetchers/sle/testdata/fetcher_sle_test.1.xml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<oval_definitions
|
||||
xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd"
|
||||
xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
|
||||
xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5">
|
||||
<generator>
|
||||
<oval:product_name>Marcus Updateinfo to OVAL Converter</oval:product_name>
|
||||
<oval:schema_version>5.5</oval:schema_version>
|
||||
<oval:timestamp>2016-06-27T04:04:46</oval:timestamp>
|
||||
</generator>
|
||||
<definitions>
|
||||
<definition id="oval:org.opensuse.security:def:20122150" version="1" class="vulnerability">
|
||||
<metadata>
|
||||
<title>CVE-2012-2150</title>
|
||||
<affected family="unix">
|
||||
<platform>SUSE Linux Enterprise Server 12</platform>
|
||||
</affected>
|
||||
<reference ref_id="CVE-2012-2150" ref_url="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2150" source="CVE"/>
|
||||
<description>xfs_metadump in xfsprogs before 3.2.4 does not properly obfuscate file data, which allows remote attackers to obtain sensitive information by reading a generated image.</description>
|
||||
</metadata>
|
||||
<criteria operator="OR">
|
||||
<criteria operator="AND">
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009116126" comment="SUSE Linux Enterprise Server 12 is installed"/>
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009116182" comment="xfsprogs-3.2.1-3.5 is installed"/>
|
||||
</criteria>
|
||||
<criteria operator="AND">
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009118803" comment="SUSE Linux Enterprise Server 12 SP1 is installed"/>
|
||||
<criterion test_ref="oval:org.opensuse.security:tst:2009116182" comment="xfsprogs-3.2.1-3.5 is installed"/>
|
||||
</criteria>
|
||||
</criteria>
|
||||
</definition>
|
||||
</definitions>
|
||||
<tests>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009116126" version="1" comment="sles-release is ==12" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009030884"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009045919"/>
|
||||
</rpminfo_test>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009116126" version="1" comment="sles-release is ==12.1" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009030884"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009045920"/>
|
||||
</rpminfo_test>
|
||||
<rpminfo_test id="oval:org.opensuse.security:tst:2009116182" version="1" comment="xfsprogs is <3.2.1-3.5" check="at least one" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<object object_ref="oval:org.opensuse.security:obj:2009032555"/>
|
||||
<state state_ref="oval:org.opensuse.security:ste:2009046736"/>
|
||||
</rpminfo_test>
|
||||
</tests>
|
||||
<objects>
|
||||
<rpminfo_object id="oval:org.opensuse.security:obj:2009030884" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<name>sles-release</name>
|
||||
</rpminfo_object>
|
||||
<rpminfo_object id="oval:org.opensuse.security:obj:2009032555" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<name>xfsprogs</name>
|
||||
</rpminfo_object>
|
||||
</objects>
|
||||
<states>
|
||||
<rpminfo_state id="oval:org.opensuse.security:ste:2009046736" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<evr datatype="evr_string" operation="less than">0:3.2.1-3.5</evr>
|
||||
</rpminfo_state>
|
||||
<rpminfo_state id="oval:org.opensuse.security:ste:2009045919" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<version operation="equals">12</version>
|
||||
</rpminfo_state>
|
||||
<rpminfo_state id="oval:org.opensuse.security:ste:2009045920" version="1" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux">
|
||||
<version operation="equals">12.1</version>
|
||||
</rpminfo_state>
|
||||
</states>
|
||||
</oval_definitions>
|
429
utils/oval/oval.go
Normal file
429
utils/oval/oval.go
Normal file
@ -0,0 +1,429 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// This package contains the OvalFetcher definition which is being used
|
||||
// for fetching update information on OVAL format
|
||||
// see: https://oval.mitre.org/about/faqs.html#a1
|
||||
//
|
||||
// Example of an oval definition
|
||||
// <oval_definitions xmlns=.....>
|
||||
// <definitions>
|
||||
// <definition>
|
||||
// <metadata>
|
||||
// <title>CVE-1111-11</title>
|
||||
// <description>blablabla</description>
|
||||
// <reference source="CVE" ref_id="CVE-1111-11" ref_url="http...."/>
|
||||
// <reference source="RHSA" ref_id="RHSA-111:11" ref_url="http...."/>
|
||||
// </metadata>
|
||||
// <criteria operator="AND">
|
||||
// <criterion test_ref="123" comment="glibc is ....">
|
||||
// </criterion>
|
||||
// <criterion test_ref="456" comment=".... is signed with Red Hat....">
|
||||
// </criterion>
|
||||
// </criteria>
|
||||
// </definition>
|
||||
// </definitions>
|
||||
// <tests>
|
||||
// ...
|
||||
// </tests>
|
||||
// <objects>
|
||||
// ...
|
||||
// </objects>
|
||||
// <states>
|
||||
// ...
|
||||
// </states>
|
||||
// </oval_definitions>
|
||||
// see more complete examples here
|
||||
// https://oval.mitre.org/language/about/definition.html
|
||||
// The methods here use an interface (see below) that must be implemented for
|
||||
// each Distribution in updated/fetchers/
|
||||
package oval
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/updater"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/utils/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
type oval struct {
|
||||
Definitions []definition `xml:"definitions>definition"`
|
||||
}
|
||||
|
||||
type definition struct {
|
||||
Title string `xml:"metadata>title"`
|
||||
Description string `xml:"metadata>description"`
|
||||
References []reference `xml:"metadata>reference"`
|
||||
Criteria criteria `xml:"criteria"`
|
||||
}
|
||||
|
||||
type reference struct {
|
||||
Source string `xml:"source,attr"`
|
||||
URI string `xml:"ref_url,attr"`
|
||||
}
|
||||
|
||||
type criteria struct {
|
||||
Operator string `xml:"operator,attr"`
|
||||
Criterias []*criteria `xml:"criteria"`
|
||||
Criterions []criterion `xml:"criterion"`
|
||||
}
|
||||
|
||||
type criterion struct {
|
||||
Comment string `xml:"comment,attr"`
|
||||
}
|
||||
|
||||
// OvalFetcher implements updater.Fetcher.
|
||||
type OvalFetcher struct {
|
||||
// OsInfo contains specifics to each Linux Distribution (see below)
|
||||
OsInfo OSInfo
|
||||
}
|
||||
|
||||
// OSInfo interface contains specifics methods for parsing OVAL definitions
|
||||
// that must be implemented by each Linux Distribution that uses OVAL
|
||||
// i.e. Red Hat and SUSE
|
||||
type OSInfo interface {
|
||||
// ParsePackageNameVersion should, given a comment in a criterion, return
|
||||
// the name and the version of the package.
|
||||
// For example, if the comment is
|
||||
// glibc is earlier than 3.2
|
||||
// it should return glibc and 3.2.
|
||||
//
|
||||
// This is based on the assumption that the distributions generate the
|
||||
// comments automatically and they won't change (I know, not very
|
||||
// reliable...).
|
||||
ParsePackageNameVersion(comment string) (string, string)
|
||||
|
||||
// ParseOsVersion should, given a comment in a criterion, return the
|
||||
// version of the Operating System.
|
||||
// For example, if the comment is
|
||||
// SUSE Linux Enterpise Server 12 is installed
|
||||
// should return 12
|
||||
//
|
||||
// This is based on the assumption that the distributions generate the
|
||||
// comments automatically and they won't change it (I know, not very
|
||||
// reliable...).
|
||||
ParseOsVersion(comment string) string
|
||||
|
||||
// Given a line, parse for the xml file that contains the oval definition
|
||||
// and returns the filename.
|
||||
// For example if the line contains
|
||||
// com.redhat.rhsa-2003.xml, this will be returned.
|
||||
//
|
||||
// This is being used in conjunction with OvalUri (see below). Oval Uri
|
||||
// contains a list of files, and you need ParseFilenameDist to get the
|
||||
// right ones.
|
||||
ParseFilenameDist(line string) string
|
||||
|
||||
// OvalUri returns the url where the oval definitions are stored for given
|
||||
// distributions. See examples:
|
||||
// https://www.redhat.com/security/data/oval/
|
||||
// http://ftp.suse.com/pub/projects/security/oval/
|
||||
OvalURI() string
|
||||
|
||||
// DistName returns the distribution name. Mostly used for debugging
|
||||
// purposes.
|
||||
DistName() string
|
||||
|
||||
// IgnoredCriterions returns a list of strings that must be ignored when
|
||||
// parsing the criterions.
|
||||
// Oval parses parses all criterions by default trying to identify either
|
||||
// package name and version or distribution version.
|
||||
IgnoredCriterions() []string
|
||||
|
||||
// SecToken returns a string that is compared with the value of
|
||||
// reference.source in order to know if that is a security reference for,
|
||||
// for example, using its url value.
|
||||
// Example return values: CVE, RHSA.
|
||||
SecToken() string
|
||||
|
||||
// Namespace stores the namespace that will be used in clair to store the
|
||||
// vulnerabilities.
|
||||
Namespace() string
|
||||
}
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "utils/oval")
|
||||
)
|
||||
|
||||
// FetchUpdate gets vulnerability updates from the OVAL definitions.
|
||||
func (f *OvalFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
||||
log.Info("fetching %s vulnerabilities", f.OsInfo.DistName())
|
||||
|
||||
r, err := http.Get(f.OsInfo.OvalURI())
|
||||
if err != nil {
|
||||
log.Errorf("could not download %s's update list: %s", f.OsInfo.DistName(), err)
|
||||
return resp, cerrors.ErrCouldNotDownload
|
||||
}
|
||||
|
||||
var distList []string
|
||||
scanner := bufio.NewScanner(r.Body)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
filename := f.OsInfo.ParseFilenameDist(line)
|
||||
if filename != "" {
|
||||
distList = append(distList, filename)
|
||||
}
|
||||
}
|
||||
|
||||
for _, filename := range distList {
|
||||
r, err := http.Get(filename)
|
||||
if err != nil {
|
||||
log.Errorf("could not download %s's update file: %s", f.OsInfo.DistName(), err)
|
||||
return resp, cerrors.ErrCouldNotDownload
|
||||
}
|
||||
|
||||
vs, err := f.ParseOval(r.Body)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.Vulnerabilities = append(resp.Vulnerabilities, vs...)
|
||||
}
|
||||
|
||||
// Set the flag if we found anything.
|
||||
if len(distList) > 0 {
|
||||
resp.FlagName = f.OsInfo.DistName() + "_updater"
|
||||
resp.FlagValue = distList[len(distList)-1]
|
||||
} else {
|
||||
log.Debug("no files to parse found for %s", f.OsInfo.DistName())
|
||||
log.Debug("in %s", f.OsInfo.OvalURI())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Clean deletes any allocated resources.
|
||||
func (f *OvalFetcher) Clean() {}
|
||||
|
||||
// Parse criterions into an array of FeatureVersion for storing into the database
|
||||
func (f *OvalFetcher) ToFeatureVersions(possibilities [][]criterion) []database.FeatureVersion {
|
||||
featureVersionParameters := make(map[string]database.FeatureVersion)
|
||||
|
||||
for _, criterions := range possibilities {
|
||||
var (
|
||||
featureVersion database.FeatureVersion
|
||||
osVersion string
|
||||
)
|
||||
|
||||
for _, c := range criterions {
|
||||
if osVersion != "" && featureVersion.Feature.Name != "" &&
|
||||
featureVersion.Version.String() != "" {
|
||||
break
|
||||
}
|
||||
tmp_v := f.OsInfo.ParseOsVersion(c.Comment)
|
||||
if tmp_v != "" {
|
||||
osVersion = tmp_v
|
||||
continue
|
||||
}
|
||||
|
||||
tmp_p_name, tmp_p_version := f.OsInfo.ParsePackageNameVersion(c.Comment)
|
||||
if tmp_p_version != "" && tmp_p_name != "" {
|
||||
featureVersion.Feature.Name = tmp_p_name
|
||||
featureVersion.Version, _ = types.NewVersion(tmp_p_version)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Warningf("could not parse criteria: '%s'.", c.Comment)
|
||||
}
|
||||
|
||||
if osVersion == "" {
|
||||
log.Warning("No OS version found for criterions")
|
||||
log.Warning(criterions)
|
||||
continue
|
||||
}
|
||||
|
||||
featureVersion.Feature.Namespace.Name = fmt.Sprintf("%s:%s", f.OsInfo.Namespace(), osVersion)
|
||||
|
||||
if featureVersion.Feature.Name != "" && featureVersion.Version.String() != "" {
|
||||
featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion
|
||||
} else {
|
||||
log.Warningf("could not determine a valid package from criterions: %v", criterions)
|
||||
}
|
||||
}
|
||||
|
||||
var featureVersionParametersArray []database.FeatureVersion
|
||||
for _, fv := range featureVersionParameters {
|
||||
featureVersionParametersArray = append(featureVersionParametersArray, fv)
|
||||
}
|
||||
|
||||
return featureVersionParametersArray
|
||||
}
|
||||
|
||||
// Parse an Oval file.
|
||||
func (f *OvalFetcher) ParseOval(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
||||
var ov oval
|
||||
err = xml.NewDecoder(ovalReader).Decode(&ov)
|
||||
if err != nil {
|
||||
log.Errorf("could not decode %s's XML: %s", f.OsInfo.DistName(), err)
|
||||
return vulnerabilities, cerrors.ErrCouldNotParse
|
||||
}
|
||||
|
||||
for _, definition := range ov.Definitions {
|
||||
pkgs := f.ToFeatureVersions(f.Possibilities(definition.Criteria))
|
||||
|
||||
if len(pkgs) > 0 {
|
||||
vulnerability := database.Vulnerability{
|
||||
Name: name(definition),
|
||||
Link: link(definition, f.OsInfo.SecToken()),
|
||||
Severity: priority(definition),
|
||||
Description: description(definition),
|
||||
}
|
||||
|
||||
vulnerability.FixedIn = append(vulnerability.FixedIn, pkgs...)
|
||||
|
||||
vulnerabilities = append(vulnerabilities, vulnerability)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get the description from a definition element
|
||||
func description(def definition) (desc string) {
|
||||
desc = strings.Replace(def.Description, "\n\n\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\n\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\n", " ", -1)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get the name form a definition element
|
||||
func name(def definition) string {
|
||||
title := def.Title
|
||||
index := strings.Index(title, ": ")
|
||||
if index == -1 {
|
||||
index = len(title)
|
||||
}
|
||||
return strings.TrimSpace(title[:index])
|
||||
}
|
||||
|
||||
// Get the link from a definition element where reference.source matches the secToken
|
||||
func link(def definition, secToken string) (link string) {
|
||||
for _, reference := range def.References {
|
||||
if reference.Source == secToken {
|
||||
link = reference.URI
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get priority from a definition
|
||||
func priority(def definition) types.Priority {
|
||||
// Parse the priority.
|
||||
priority := strings.TrimSpace(def.Title[strings.LastIndex(def.Title, "(")+1 : len(def.Title)-1])
|
||||
|
||||
// Normalize the priority.
|
||||
switch priority {
|
||||
case "Low":
|
||||
return types.Low
|
||||
case "Moderate":
|
||||
return types.Medium
|
||||
case "Important":
|
||||
return types.High
|
||||
case "Critical":
|
||||
return types.Critical
|
||||
default:
|
||||
log.Warning("could not determine vulnerability priority from: %s.", priority)
|
||||
return types.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Get Criterions elements from a criteria element
|
||||
func (f *OvalFetcher) Criterions(node criteria) [][]criterion {
|
||||
var criterions []criterion
|
||||
|
||||
for _, c := range node.Criterions {
|
||||
ignored := false
|
||||
for _, ignoredItem := range f.OsInfo.IgnoredCriterions() {
|
||||
if strings.Contains(c.Comment, ignoredItem) {
|
||||
ignored = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ignored {
|
||||
criterions = append(criterions, c)
|
||||
}
|
||||
}
|
||||
|
||||
if node.Operator == "AND" {
|
||||
return [][]criterion{criterions}
|
||||
} else if node.Operator == "OR" {
|
||||
var possibilities [][]criterion
|
||||
|
||||
for _, c := range criterions {
|
||||
possibilities = append(possibilities, []criterion{c})
|
||||
}
|
||||
|
||||
return possibilities
|
||||
}
|
||||
|
||||
return [][]criterion{}
|
||||
}
|
||||
|
||||
// Get Possibilities from a criteria element
|
||||
func (f *OvalFetcher) Possibilities(node criteria) [][]criterion {
|
||||
if len(node.Criterias) == 0 {
|
||||
return f.Criterions(node)
|
||||
}
|
||||
|
||||
var possibilitiesToCompose [][][]criterion
|
||||
|
||||
for _, criteria := range node.Criterias {
|
||||
possibilitiesToCompose = append(possibilitiesToCompose, f.Possibilities(*criteria))
|
||||
}
|
||||
|
||||
if len(node.Criterions) > 0 {
|
||||
possibilitiesToCompose = append(possibilitiesToCompose, f.Criterions(node))
|
||||
}
|
||||
|
||||
var possibilities [][]criterion
|
||||
|
||||
if node.Operator == "AND" {
|
||||
possibilities = append(possibilities, possibilitiesToCompose[0]...)
|
||||
|
||||
for _, possibilityGroup := range possibilitiesToCompose[1:] {
|
||||
var newPossibilities [][]criterion
|
||||
|
||||
for _, possibility := range possibilities {
|
||||
for _, possibilityInGroup := range possibilityGroup {
|
||||
var p []criterion
|
||||
|
||||
p = append(p, possibility...)
|
||||
p = append(p, possibilityInGroup...)
|
||||
|
||||
newPossibilities = append(newPossibilities, p)
|
||||
}
|
||||
}
|
||||
|
||||
possibilities = newPossibilities
|
||||
}
|
||||
} else if node.Operator == "OR" {
|
||||
for _, possibilityGroup := range possibilitiesToCompose {
|
||||
possibilities = append(possibilities, possibilityGroup...)
|
||||
}
|
||||
}
|
||||
return possibilities
|
||||
}
|
Loading…
Reference in New Issue
Block a user