diff --git a/updater/fetchers.go b/updater/fetchers.go index af09eeb9..f0295559 100644 --- a/updater/fetchers.go +++ b/updater/fetchers.go @@ -49,3 +49,38 @@ func RegisterFetcher(name string, f Fetcher) { fetchers[name] = f } + +// DoVulnerabilityNamespacing is an helper function for fetchers. +// +// It takes a Vulnerability that doesn't have a Namespace and split it into +// potentially multiple vulnerabilities that have a Namespace and only contains the FixedIn +// FeatureVersions corresponding to their Namespace. +// +// It helps simplifying the fetchers that share the same metadata about a Vulnerability regardless +// of their actual namespace (ie. same vulnerability information for every version of a distro). +func DoVulnerabilityNamespacing(v database.Vulnerability) []database.Vulnerability { + vulnerabilitiesMap := make(map[string]*database.Vulnerability) + + featureVersions := v.FixedIn + v.FixedIn = []database.FeatureVersion{} + + for _, fv := range featureVersions { + if vulnerability, ok := vulnerabilitiesMap[fv.Feature.Namespace.Name]; !ok { + newVulnerability := v + newVulnerability.Namespace.Name = fv.Feature.Namespace.Name + newVulnerability.FixedIn = []database.FeatureVersion{fv} + + vulnerabilitiesMap[fv.Feature.Namespace.Name] = &newVulnerability + } else { + vulnerability.FixedIn = append(vulnerability.FixedIn, fv) + } + } + + // Convert map into a slice. + var vulnerabilities []database.Vulnerability + for _, vulnerability := range vulnerabilitiesMap { + vulnerabilities = append(vulnerabilities, *vulnerability) + } + + return vulnerabilities +} diff --git a/updater/fetchers/fetchers.go b/updater/fetchers/fetchers.go deleted file mode 100644 index 7ed54770..00000000 --- a/updater/fetchers/fetchers.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 fetchers implements vulnerability fetchers for several sources. -package fetchers - -import ( - "errors" - - "github.com/coreos/pkg/capnslog" -) - -var ( - log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers") - - // ErrCouldNotParse is returned when a fetcher fails to parse the update data. - ErrCouldNotParse = errors.New("updater/fetchers: could not parse") - - // ErrFilesystem is returned when a fetcher fails to interact with the local filesystem. - ErrFilesystem = errors.New("updater/fetchers: something went wrong when interacting with the fs") -) diff --git a/updater/fetchers/rhel/rhel.go b/updater/fetchers/rhel/rhel.go index 1cfc851d..faa8818d 100644 --- a/updater/fetchers/rhel/rhel.go +++ b/updater/fetchers/rhel/rhel.go @@ -140,8 +140,10 @@ func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.Fe return resp, err } - // Collect vulnerabilities. - resp.Vulnerabilities = append(resp.Vulnerabilities, vs...) + // Collect vulnerabilities, splitting them by Namespaces. + for _, v := range vs { + resp.Vulnerabilities = append(resp.Vulnerabilities, updater.DoVulnerabilityNamespacing(v)...) + } } // Set the flag if we found anything. diff --git a/updater/fetchers/ubuntu/ubuntu.go b/updater/fetchers/ubuntu/ubuntu.go index e4598d01..c932a5a3 100644 --- a/updater/fetchers/ubuntu/ubuntu.go +++ b/updater/fetchers/ubuntu/ubuntu.go @@ -132,8 +132,8 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up return resp, err } - // Parse and add the vulnerabilities. for cvePath := range modifiedCVE { + // Open the CVE file. file, err := os.Open(repositoryLocalPath + "/" + cvePath) if err != nil { // This can happen when a file is modified and then moved in another @@ -141,14 +141,14 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up continue } + // Parse the vulnerability. v, unknownReleases, err := parseUbuntuCVE(file) if err != nil { return resp, err } - if len(v.FixedIn) > 0 { - resp.Vulnerabilities = append(resp.Vulnerabilities, v) - } + // Add the vulnerability to the response, splitting it by Namespaces. + resp.Vulnerabilities = append(resp.Vulnerabilities, updater.DoVulnerabilityNamespacing(v)...) // Log any unknown releases. for k := range unknownReleases { diff --git a/updater/fetchers_test.go b/updater/fetchers_test.go new file mode 100644 index 00000000..51dc92f0 --- /dev/null +++ b/updater/fetchers_test.go @@ -0,0 +1,55 @@ +package updater + +import ( + "testing" + + "github.com/coreos/clair/database" + "github.com/coreos/clair/utils/types" + "github.com/stretchr/testify/assert" +) + +func TestDoVulnerabilityNamespacing(t *testing.T) { + fv1 := database.FeatureVersion{ + Feature: database.Feature{ + Namespace: database.Namespace{Name: "Namespace1"}, + Name: "Feature1", + }, + Version: types.NewVersionUnsafe("0.1"), + } + + fv2 := database.FeatureVersion{ + Feature: database.Feature{ + Namespace: database.Namespace{Name: "Namespace2"}, + Name: "Feature1", + }, + Version: types.NewVersionUnsafe("0.2"), + } + + fv3 := database.FeatureVersion{ + Feature: database.Feature{ + Namespace: database.Namespace{Name: "Namespace2"}, + Name: "Feature2", + }, + Version: types.NewVersionUnsafe("0.3"), + } + + vulnerability := database.Vulnerability{ + Name: "DoVulnerabilityNamespacing", + FixedIn: []database.FeatureVersion{fv1, fv2, fv3}, + } + + vulnerabilities := DoVulnerabilityNamespacing(vulnerability) + for _, vulnerability := range vulnerabilities { + switch vulnerability.Namespace.Name { + case fv1.Feature.Namespace.Name: + assert.Len(t, vulnerability.FixedIn, 1) + assert.Contains(t, vulnerability.FixedIn, fv1) + case fv2.Feature.Namespace.Name: + assert.Len(t, vulnerability.FixedIn, 2) + assert.Contains(t, vulnerability.FixedIn, fv2) + assert.Contains(t, vulnerability.FixedIn, fv3) + default: + t.Errorf("Should not have a Vulnerability with '%s' as its Namespace.", vulnerability.Namespace.Name) + } + } +}