From 343e24eb7eb6336dca94df7b43499dfef08ee4fe Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Sun, 15 Jan 2017 10:52:13 -0500 Subject: [PATCH] clair: remove `types` package This removes the `types` package instead moving the contents to the top-level clair package. This change also renames the `Priority` type to `Severity` in order to reduce confusion. This change also removes the IsValid method and replaces it with a safe constructor to avoid the creation of invalid values. Many docstrings were tweaked in the making of this commit. --- api/v1/models.go | 10 +- clair.go | 75 -------- cmd/clair/main.go | 98 ++++++++--- database/database.go | 163 +++++++++++------- database/models.go | 6 +- database/pgsql/complex_test.go | 6 +- database/pgsql/layer_test.go | 4 +- database/pgsql/notification_test.go | 4 +- database/pgsql/vulnerability.go | 12 +- database/pgsql/vulnerability_test.go | 22 +-- ext/vulnmdsrc/driver.go | 4 +- ext/vulnmdsrc/nvd/nvd.go | 42 ++--- ext/vulnsrc/alpine/alpine.go | 6 +- ext/vulnsrc/debian/debian.go | 30 ++-- ext/vulnsrc/debian/debian_test.go | 10 +- ext/vulnsrc/oracle/oracle.go | 29 ++-- ext/vulnsrc/oracle/oracle_test.go | 8 +- ext/vulnsrc/rhel/rhel.go | 24 ++- ext/vulnsrc/rhel/rhel_test.go | 8 +- ext/vulnsrc/ubuntu/ubuntu.go | 28 +-- ext/vulnsrc/ubuntu/ubuntu_test.go | 6 +- utils/types/priority.go => severity.go | 99 +++++++---- .../priority_test.go => severity_test.go | 21 ++- updater/updater.go | 6 +- 24 files changed, 364 insertions(+), 357 deletions(-) delete mode 100644 clair.go rename utils/types/priority.go => severity.go (56%) rename utils/types/priority_test.go => severity_test.go (59%) diff --git a/api/v1/models.go b/api/v1/models.go index bb480ac1..e3b1e1fb 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -1,4 +1,4 @@ -// Copyright 2015 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import ( "github.com/coreos/pkg/capnslog" "github.com/fernet/fernet-go" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" - "github.com/coreos/clair/utils/types" ) var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1") @@ -109,9 +109,9 @@ type Vulnerability struct { } func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) { - severity := types.Priority(v.Severity) - if !severity.IsValid() { - return database.Vulnerability{}, errors.New("Invalid severity") + severity, err := clair.NewSeverity(v.Severity) + if err != nil { + return database.Vulnerability{}, err } var dbFeatures []database.FeatureVersion diff --git a/clair.go b/clair.go deleted file mode 100644 index 788ab617..00000000 --- a/clair.go +++ /dev/null @@ -1,75 +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 clair implements the ability to boot Clair with your own imports -// that can dynamically register additional functionality. -package clair - -import ( - "math/rand" - "os" - "os/signal" - "syscall" - "time" - - "github.com/coreos/clair/api" - "github.com/coreos/clair/api/context" - "github.com/coreos/clair/config" - "github.com/coreos/clair/database" - "github.com/coreos/clair/notifier" - "github.com/coreos/clair/updater" - "github.com/coreos/clair/utils" - "github.com/coreos/pkg/capnslog" -) - -var log = capnslog.NewPackageLogger("github.com/coreos/clair", "main") - -// Boot starts Clair. By exporting this function, anyone can import their own -// custom fetchers/updaters into their own package and then call clair.Boot. -func Boot(config *config.Config) { - rand.Seed(time.Now().UnixNano()) - st := utils.NewStopper() - - // Open database - db, err := database.Open(config.Database) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - // Start notifier - st.Begin() - go notifier.Run(config.Notifier, db, st) - - // Start API - st.Begin() - go api.Run(config.API, &context.RouteContext{db, config.API}, st) - st.Begin() - go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st) - - // Start updater - st.Begin() - go updater.Run(config.Updater, db, st) - - // Wait for interruption and shutdown gracefully. - waitForSignals(syscall.SIGINT, syscall.SIGTERM) - log.Info("Received interruption, gracefully stopping ...") - st.Stop() -} - -func waitForSignals(signals ...os.Signal) { - interrupts := make(chan os.Signal, 1) - signal.Notify(interrupts, signals...) - <-interrupts -} diff --git a/cmd/clair/main.go b/cmd/clair/main.go index 74e764cf..22000c66 100644 --- a/cmd/clair/main.go +++ b/cmd/clair/main.go @@ -16,14 +16,23 @@ package main import ( "flag" + "math/rand" "os" + "os/signal" "runtime/pprof" "strings" + "syscall" + "time" "github.com/coreos/pkg/capnslog" - "github.com/coreos/clair" + "github.com/coreos/clair/api" + "github.com/coreos/clair/api/context" "github.com/coreos/clair/config" + "github.com/coreos/clair/database" + "github.com/coreos/clair/notifier" + "github.com/coreos/clair/updater" + "github.com/coreos/clair/utils" // Register database driver. _ "github.com/coreos/clair/database/pgsql" @@ -50,30 +59,10 @@ import ( var log = capnslog.NewPackageLogger("github.com/coreos/clair/cmd/clair", "main") -func main() { - // Parse command-line arguments - flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) - flagConfigPath := flag.String("config", "/etc/clair/config.yaml", "Load configuration from the specified file.") - flagCPUProfilePath := flag.String("cpu-profile", "", "Write a CPU profile to the specified file before exiting.") - flagLogLevel := flag.String("log-level", "info", "Define the logging level.") - flag.Parse() - // Load configuration - config, err := config.Load(*flagConfigPath) - if err != nil { - log.Fatalf("failed to load configuration: %s", err) - } - - // Initialize logging system - logLevel, err := capnslog.ParseLevel(strings.ToUpper(*flagLogLevel)) - capnslog.SetGlobalLogLevel(logLevel) - capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, false)) - - // Enable CPU Profiling if specified - if *flagCPUProfilePath != "" { - defer stopCPUProfiling(startCPUProfiling(*flagCPUProfilePath)) - } - - clair.Boot(config) +func waitForSignals(signals ...os.Signal) { + interrupts := make(chan os.Signal, 1) + signal.Notify(interrupts, signals...) + <-interrupts } func startCPUProfiling(path string) *os.File { @@ -97,3 +86,62 @@ func stopCPUProfiling(f *os.File) { f.Close() log.Info("stopped CPU profiling") } + +// Boot starts Clair instance with the provided config. +func Boot(config *config.Config) { + rand.Seed(time.Now().UnixNano()) + st := utils.NewStopper() + + // Open database + db, err := database.Open(config.Database) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + // Start notifier + st.Begin() + go notifier.Run(config.Notifier, db, st) + + // Start API + st.Begin() + go api.Run(config.API, &context.RouteContext{db, config.API}, st) + st.Begin() + go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st) + + // Start updater + st.Begin() + go updater.Run(config.Updater, db, st) + + // Wait for interruption and shutdown gracefully. + waitForSignals(syscall.SIGINT, syscall.SIGTERM) + log.Info("Received interruption, gracefully stopping ...") + st.Stop() +} + +func main() { + // Parse command-line arguments + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + flagConfigPath := flag.String("config", "/etc/clair/config.yaml", "Load configuration from the specified file.") + flagCPUProfilePath := flag.String("cpu-profile", "", "Write a CPU profile to the specified file before exiting.") + flagLogLevel := flag.String("log-level", "info", "Define the logging level.") + flag.Parse() + + // Load configuration + config, err := config.Load(*flagConfigPath) + if err != nil { + log.Fatalf("failed to load configuration: %s", err) + } + + // Initialize logging system + logLevel, err := capnslog.ParseLevel(strings.ToUpper(*flagLogLevel)) + capnslog.SetGlobalLogLevel(logLevel) + capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, false)) + + // Enable CPU Profiling if specified + if *flagCPUProfilePath != "" { + defer stopCPUProfiling(startCPUProfiling(*flagCPUProfilePath)) + } + + Boot(config) +} diff --git a/database/database.go b/database/database.go index 4ca13e42..0e57921f 100644 --- a/database/database.go +++ b/database/database.go @@ -1,4 +1,4 @@ -// Copyright 2015 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,7 +29,8 @@ var ( ErrBackendException = errors.New("database: an error occured when querying the backend") // ErrInconsistent is an error that occurs when a database consistency check - // fails (ie. when an entity which is supposed to be unique is detected twice) + // fails (i.e. when an entity which is supposed to be unique is detected + // twice) ErrInconsistent = errors.New("database: inconsistent database") ) @@ -62,123 +63,151 @@ func Open(cfg config.RegistrableComponentConfig) (Datastore, error) { return driver(cfg) } -// Datastore is the interface that describes a database backend implementation. +// Datastore represents the required operations on a persistent data store for +// a Clair deployment. type Datastore interface { - // # Namespace // ListNamespaces returns the entire list of known Namespaces. ListNamespaces() ([]Namespace, error) - // # Layer // InsertLayer stores a Layer in the database. - // A Layer is uniquely identified by its Name. The Name and EngineVersion fields are mandatory. - // If a Parent is specified, it is expected that it has been retrieved using FindLayer. - // If a Layer that already exists is inserted and the EngineVersion of the given Layer is higher - // than the stored one, the stored Layer should be updated. - // The function has to be idempotent, inserting a layer that already exists shouln'd return an - // error. + // + // A Layer is uniquely identified by its Name. + // The Name and EngineVersion fields are mandatory. + // If a Parent is specified, it is expected that it has been retrieved using + // FindLayer. + // If a Layer that already exists is inserted and the EngineVersion of the + // given Layer is higher than the stored one, the stored Layer should be + // updated. + // The function has to be idempotent, inserting a layer that already exists + // shouldn't return an error. InsertLayer(Layer) error // FindLayer retrieves a Layer from the database. - // withFeatures specifies whether the Features field should be filled. When withVulnerabilities is - // true, the Features field should be filled and their AffectedBy fields should contain every - // vulnerabilities that affect them. + // + // When `withFeatures` is true, the Features field should be filled. + // When `withVulnerabilities` is true, the Features field should be filled + // and their AffectedBy fields should contain every vulnerabilities that + // affect them. FindLayer(name string, withFeatures, withVulnerabilities bool) (Layer, error) - // DeleteLayer deletes a Layer from the database and every layers that are based on it, - // recursively. + // DeleteLayer deletes a Layer from the database and every layers that are + // based on it, recursively. DeleteLayer(name string) error - // # Vulnerability - // ListVulnerabilities returns the list of vulnerabilies of a certain Namespace. + // ListVulnerabilities returns the list of vulnerabilities of a particular + // Namespace. + // // The Limit and page parameters are used to paginate the return list. - // The first given page should be 0. The function will then return the next available page. - // If there is no more page, -1 has to be returned. + // The first given page should be 0. + // The function should return the next available page. If there are no more + // pages, -1 has to be returned. ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, int, error) - // InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if - // necessary. A vulnerability is uniquely identified by its Namespace and its Name. - // The FixedIn field may only contain a partial list of Features that are affected by the - // Vulnerability, along with the version in which the vulnerability is fixed. It is the - // responsibility of the implementation to update the list properly. A version equals to - // types.MinVersion means that the given Feature is not being affected by the Vulnerability at - // all and thus, should be removed from the list. It is important that Features should be unique - // in the FixedIn list. For example, it doesn't make sense to have two `openssl` Feature listed as - // a Vulnerability can only be fixed in one Version. This is true because Vulnerabilities and - // Features are Namespaced (i.e. specific to one operating system). - // Each vulnerability insertion or update has to create a Notification that will contain the - // old and the updated Vulnerability, unless createNotification equals to true. + // InsertVulnerabilities stores the given Vulnerabilities in the database, + // updating them if necessary. + // + // A vulnerability is uniquely identified by its Namespace and its Name. + // The FixedIn field may only contain a partial list of Features that are + // affected by the Vulnerability, along with the version in which the + // vulnerability is fixed. It is the responsibility of the implementation to + // update the list properly. + // A version equals to versionfmt.MinVersion means that the given Feature is + // not being affected by the Vulnerability at all and thus, should be removed + // from the list. + // It is important that Features should be unique in the FixedIn list. For + // example, it doesn't make sense to have two `openssl` Feature listed as a + // Vulnerability can only be fixed in one Version. This is true because + // Vulnerabilities and Features are namespaced (i.e. specific to one + // operating system). + // Each vulnerability insertion or update has to create a Notification that + // will contain the old and the updated Vulnerability, unless + // createNotification equals to true. InsertVulnerabilities(vulnerabilities []Vulnerability, createNotification bool) error - // FindVulnerability retrieves a Vulnerability from the database, including the FixedIn list. + // FindVulnerability retrieves a Vulnerability from the database, including + // the FixedIn list. FindVulnerability(namespaceName, name string) (Vulnerability, error) // DeleteVulnerability removes a Vulnerability from the database. + // // It has to create a Notification that will contain the old Vulnerability. DeleteVulnerability(namespaceName, name string) error - // InsertVulnerabilityFixes adds new FixedIn Feature or update the Versions of existing ones to - // the specified Vulnerability in the database. - // It has has to create a Notification that will contain the old and the updated Vulnerability. + // InsertVulnerabilityFixes adds new FixedIn Feature or update the Versions + // of existing ones to the specified Vulnerability in the database. + // + // It has has to create a Notification that will contain the old and the + // updated Vulnerability. InsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName string, fixes []FeatureVersion) error - // DeleteVulnerabilityFix removes a FixedIn Feature from the specified Vulnerability in the - // database. It can be used to store the fact that a Vulnerability no longer affects the given - // Feature in any Version. + // DeleteVulnerabilityFix removes a FixedIn Feature from the specified + // Vulnerability in the database. It can be used to store the fact that a + // Vulnerability no longer affects the given Feature in any Version. + // // It has has to create a Notification that will contain the old and the updated Vulnerability. DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName string) error - // # Notification - // GetAvailableNotification returns the Name, Created, Notified and Deleted fields of a - // Notification that should be handled. The renotify interval defines how much time after being - // marked as Notified by SetNotificationNotified, a Notification that hasn't been deleted should - // be returned again by this function. A Notification for which there is a valid Lock with the - // same Name should not be returned. + // GetAvailableNotification returns the Name, Created, Notified and Deleted + // fields of a Notification that should be handled. + // + // The renotify interval defines how much time after being marked as Notified + // by SetNotificationNotified, a Notification that hasn't been deleted should + // be returned again by this function. + // A Notification for which there is a valid Lock with the same Name should + // not be returned. GetAvailableNotification(renotifyInterval time.Duration) (VulnerabilityNotification, error) - // GetNotification returns a Notification, including its OldVulnerability and NewVulnerability - // fields. On these Vulnerabilities, LayersIntroducingVulnerability should be filled with - // every Layer that introduces the Vulnerability (i.e. adds at least one affected FeatureVersion). - // The Limit and page parameters are used to paginate LayersIntroducingVulnerability. The first - // given page should be VulnerabilityNotificationFirstPage. The function will then return the next - // availage page. If there is no more page, NoVulnerabilityNotificationPage has to be returned. + // GetNotification returns a Notification, including its OldVulnerability and + // NewVulnerability fields. + // + // On these Vulnerabilities, LayersIntroducingVulnerability should be filled + // with every Layer that introduces the Vulnerability (i.e. adds at least one + // affected FeatureVersion). + // The Limit and page parameters are used to paginate + // LayersIntroducingVulnerability. The first given page should be + // VulnerabilityNotificationFirstPage. The function will then return the next + // available page. If there is no more page, NoVulnerabilityNotificationPage + // has to be returned. GetNotification(name string, limit int, page VulnerabilityNotificationPageNumber) (VulnerabilityNotification, VulnerabilityNotificationPageNumber, error) - // SetNotificationNotified marks a Notification as notified and thus, makes it unavailable for - // GetAvailableNotification, until the renotify duration is elapsed. + // SetNotificationNotified marks a Notification as notified and thus, makes + // it unavailable for GetAvailableNotification, until the renotify duration + // is elapsed. SetNotificationNotified(name string) error - // DeleteNotification marks a Notification as deleted, and thus, makes it unavailable for - // GetAvailableNotification. + // DeleteNotification marks a Notification as deleted, and thus, makes it + // unavailable for GetAvailableNotification. DeleteNotification(name string) error - // # Key/Value // InsertKeyValue stores or updates a simple key/value pair in the database. InsertKeyValue(key, value string) error // GetKeyValue retrieves a value from the database from the given key. + // // It returns an empty string if there is no such key. GetKeyValue(key string) (string, error) - // # Lock - // Lock creates or renew a Lock in the database with the given name, owner and duration. - // After the specified duration, the Lock expires by itself if it hasn't been unlocked, and thus, - // let other users create a Lock with the same name. However, the owner can renew its Lock by - // setting renew to true. Lock should not block, it should instead returns whether the Lock has - // been successfully acquired/renewed. If it's the case, the expiration time of that Lock is - // returned as well. + // Lock creates or renew a Lock in the database with the given name, owner + // and duration. + // + // After the specified duration, the Lock expires by itself if it hasn't been + // unlocked, and thus, let other users create a Lock with the same name. + // However, the owner can renew its Lock by setting renew to true. + // Lock should not block, it should instead returns whether the Lock has been + // successfully acquired/renewed. If it's the case, the expiration time of + // that Lock is returned as well. Lock(name string, owner string, duration time.Duration, renew bool) (bool, time.Time) // Unlock releases an existing Lock. Unlock(name, owner string) - // FindLock returns the owner of a Lock specified by the name, and its experation time if it - // exists. + // FindLock returns the owner of a Lock specified by the name, and its + // expiration time if it exists. FindLock(name string) (string, time.Time, error) - // # Miscellaneous // Ping returns the health status of the database. Ping() bool - // Close closes the database and free any allocated resource. + // Close closes the database and frees any allocated resource. Close() } diff --git a/database/models.go b/database/models.go index 898d2c9a..86a14096 100644 --- a/database/models.go +++ b/database/models.go @@ -1,4 +1,4 @@ -// Copyright 2015 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import ( "encoding/json" "time" - "github.com/coreos/clair/utils/types" + "github.com/coreos/clair" ) // ID is only meant to be used by database implementations and should never be used for anything else. @@ -70,7 +70,7 @@ type Vulnerability struct { Description string Link string - Severity types.Priority + Severity clair.Severity Metadata MetadataMap diff --git a/database/pgsql/complex_test.go b/database/pgsql/complex_test.go index b49903fb..407d8b2a 100644 --- a/database/pgsql/complex_test.go +++ b/database/pgsql/complex_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ import ( "github.com/pborman/uuid" "github.com/stretchr/testify/assert" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/utils" - "github.com/coreos/clair/utils/types" ) const ( @@ -93,7 +93,7 @@ func TestRaceAffects(t *testing.T) { Version: strconv.Itoa(version), }, }, - Severity: types.Unknown, + Severity: clair.Unknown, } vulnerabilities[version] = append(vulnerabilities[version], vulnerability) diff --git a/database/pgsql/layer_test.go b/database/pgsql/layer_test.go index 3aa6e622..ad6ae1d7 100644 --- a/database/pgsql/layer_test.go +++ b/database/pgsql/layer_test.go @@ -20,10 +20,10 @@ import ( "github.com/stretchr/testify/assert" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) func TestFindLayer(t *testing.T) { @@ -91,7 +91,7 @@ func TestFindLayer(t *testing.T) { if assert.Len(t, featureVersion.AffectedBy, 1) { assert.Equal(t, "debian:7", featureVersion.AffectedBy[0].Namespace.Name) assert.Equal(t, "CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Name) - assert.Equal(t, types.High, featureVersion.AffectedBy[0].Severity) + assert.Equal(t, clair.High, featureVersion.AffectedBy[0].Severity) assert.Equal(t, "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", featureVersion.AffectedBy[0].Description) assert.Equal(t, "http://google.com/#q=CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Link) assert.Equal(t, "2.0", featureVersion.AffectedBy[0].FixedBy) diff --git a/database/pgsql/notification_test.go b/database/pgsql/notification_test.go index 5dab2c22..4e53e217 100644 --- a/database/pgsql/notification_test.go +++ b/database/pgsql/notification_test.go @@ -20,11 +20,11 @@ import ( "github.com/stretchr/testify/assert" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) func TestNotification(t *testing.T) { @@ -169,7 +169,7 @@ func TestNotification(t *testing.T) { // Update a vulnerability and ensure that the old/new vulnerabilities are correct. v1b := v1 - v1b.Severity = types.High + v1b.Severity = clair.High v1b.FixedIn = []database.FeatureVersion{ { Feature: f1, diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index 17461374..3d791af4 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -17,7 +17,6 @@ package pgsql import ( "database/sql" "encoding/json" - "fmt" "reflect" "time" @@ -197,11 +196,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on if vulnerability.Name == "" || vulnerability.Namespace.Name == "" { return commonerr.NewBadRequestError("insertVulnerability needs at least the Name and the Namespace") } - if !onlyFixedIn && !vulnerability.Severity.IsValid() { - msg := fmt.Sprintf("could not insert a vulnerability that has an invalid Severity: %s", vulnerability.Severity) - log.Warning(msg) - return commonerr.NewBadRequestError(msg) - } + for i := 0; i < len(vulnerability.FixedIn); i++ { fifv := &vulnerability.FixedIn[i] @@ -271,8 +266,9 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on return handleError("removeVulnerability", err) } } else { - // The vulnerability is new, we don't want to have any types.MinVersion as they are only used - // for diffing existing vulnerabilities. + // The vulnerability is new, we don't want to have any + // versionfmt.MinVersion as they are only used for diffing existing + // vulnerabilities. var fixedIn []database.FeatureVersion for _, fv := range vulnerability.FixedIn { if fv.Version != versionfmt.MinVersion { diff --git a/database/pgsql/vulnerability_test.go b/database/pgsql/vulnerability_test.go index 788856ba..006a7fda 100644 --- a/database/pgsql/vulnerability_test.go +++ b/database/pgsql/vulnerability_test.go @@ -20,11 +20,11 @@ import ( "github.com/stretchr/testify/assert" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) func TestFindVulnerability(t *testing.T) { @@ -44,7 +44,7 @@ func TestFindVulnerability(t *testing.T) { Name: "CVE-OPENSSL-1-DEB7", Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7", - Severity: types.High, + Severity: clair.High, Namespace: database.Namespace{ Name: "debian:7", VersionFormat: dpkg.ParserName, @@ -74,7 +74,7 @@ func TestFindVulnerability(t *testing.T) { Name: "debian:7", VersionFormat: dpkg.ParserName, }, - Severity: types.Unknown, + Severity: clair.Unknown, } v2f, err := datastore.FindVulnerability("debian:7", "CVE-NOPE") @@ -180,30 +180,24 @@ func TestInsertVulnerability(t *testing.T) { Name: "", Namespace: n1, FixedIn: []database.FeatureVersion{f1}, - Severity: types.Unknown, + Severity: clair.Unknown, }, { Name: "TestInsertVulnerability0", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, - Severity: types.Unknown, + Severity: clair.Unknown, }, { Name: "TestInsertVulnerability0-", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, }, - { - Name: "TestInsertVulnerability0", - Namespace: n1, - FixedIn: []database.FeatureVersion{f1}, - Severity: types.Priority(""), - }, { Name: "TestInsertVulnerability0", Namespace: n1, FixedIn: []database.FeatureVersion{f2}, - Severity: types.Unknown, + Severity: clair.Unknown, }, } { err := datastore.InsertVulnerabilities([]database.Vulnerability{vulnerability}, true) @@ -223,7 +217,7 @@ func TestInsertVulnerability(t *testing.T) { Name: "TestInsertVulnerability1", Namespace: n1, FixedIn: []database.FeatureVersion{f1, f3, f6, f7}, - Severity: types.Low, + Severity: clair.Low, Description: "TestInsertVulnerabilityDescription1", Link: "TestInsertVulnerabilityLink1", Metadata: v1meta, @@ -239,7 +233,7 @@ func TestInsertVulnerability(t *testing.T) { // Update vulnerability. v1.Description = "TestInsertVulnerabilityLink2" v1.Link = "TestInsertVulnerabilityLink2" - v1.Severity = types.High + v1.Severity = clair.High // Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, // removes fixed in f7 by adding f8 which is f7 but with MinVersion, and // add fixed by f5 a second time (duplicated). diff --git a/ext/vulnmdsrc/driver.go b/ext/vulnmdsrc/driver.go index 06d5cb78..4de7f358 100644 --- a/ext/vulnmdsrc/driver.go +++ b/ext/vulnmdsrc/driver.go @@ -19,8 +19,8 @@ package vulnmdsrc import ( "sync" + "github.com/coreos/clair" "github.com/coreos/clair/database" - "github.com/coreos/clair/utils/types" ) var ( @@ -29,7 +29,7 @@ var ( ) // AppendFunc is the type of a callback provided to an Appender. -type AppendFunc func(metadataKey string, metadata interface{}, severity types.Priority) +type AppendFunc func(metadataKey string, metadata interface{}, severity clair.Severity) // Appender represents anything that can fetch vulnerability metadata and // append it to a Vulnerability. diff --git a/ext/vulnmdsrc/nvd/nvd.go b/ext/vulnmdsrc/nvd/nvd.go index 1b98f609..1c6b7559 100644 --- a/ext/vulnmdsrc/nvd/nvd.go +++ b/ext/vulnmdsrc/nvd/nvd.go @@ -28,15 +28,14 @@ import ( "os" "strconv" "strings" - "sync" "time" "github.com/coreos/pkg/capnslog" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/vulnmdsrc" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) const ( @@ -52,7 +51,6 @@ type appender struct { localPath string dataFeedHashes map[string]string metadata map[string]NVDMetadata - sync.Mutex } type NVDMetadata struct { @@ -69,9 +67,6 @@ func init() { } func (a *appender) BuildCache(datastore database.Datastore) error { - a.Lock() - defer a.Unlock() - var err error a.metadata = make(map[string]NVDMetadata) @@ -115,27 +110,18 @@ func (a *appender) BuildCache(datastore database.Datastore) error { } func (a *appender) Append(vulnName string, appendFunc vulnmdsrc.AppendFunc) error { - a.Lock() - defer a.Unlock() - if nvdMetadata, ok := a.metadata[vulnName]; ok { - appendFunc(appenderName, nvdMetadata, scoreToPriority(nvdMetadata.CVSSv2.Score)) + appendFunc(appenderName, nvdMetadata, SeverityFromCVSS(nvdMetadata.CVSSv2.Score)) } return nil } func (a *appender) PurgeCache() { - a.Lock() - defer a.Unlock() - a.metadata = nil } func (a *appender) Clean() { - a.Lock() - defer a.Unlock() - os.RemoveAll(a.localPath) } @@ -232,23 +218,23 @@ func getHashFromMetaURL(metaURL string) (string, error) { return "", errors.New("invalid .meta file format") } -// scoreToPriority converts the CVSS Score (0.0 - 10.0) into user-friendy -// types.Priority following the qualitative rating scale available in the -// CVSS v3.0 specification (https://www.first.org/cvss/specification-document), -// Table 14. The Negligible level is set for CVSS scores between [0, 1), -// replacing the specified None level, originally used for a score of 0. -func scoreToPriority(score float64) types.Priority { +// SeverityFromCVSS converts the CVSS Score (0.0 - 10.0) into a clair.Severity +// following the qualitative rating scale available in the CVSS v3.0 +// specification (https://www.first.org/cvss/specification-document), Table 14. +// The Negligible level is set for CVSS scores between [0, 1), replacing the +// specified None level, originally used for a score of 0. +func SeverityFromCVSS(score float64) clair.Severity { switch { case score < 1.0: - return types.Negligible + return clair.Negligible case score < 3.9: - return types.Low + return clair.Low case score < 6.9: - return types.Medium + return clair.Medium case score < 8.9: - return types.High + return clair.High case score <= 10: - return types.Critical + return clair.Critical } - return types.Unknown + return clair.Unknown } diff --git a/ext/vulnsrc/alpine/alpine.go b/ext/vulnsrc/alpine/alpine.go index 20a59033..1ddf2aa5 100644 --- a/ext/vulnsrc/alpine/alpine.go +++ b/ext/vulnsrc/alpine/alpine.go @@ -27,13 +27,13 @@ import ( "github.com/coreos/pkg/capnslog" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/pkg/commonerr" "github.com/coreos/clair/utils" - "github.com/coreos/clair/utils/types" ) const ( @@ -225,7 +225,7 @@ func parse33YAML(r io.Reader) (vulns []database.Vulnerability, err error) { vulns = append(vulns, database.Vulnerability{ Name: fix, - Severity: types.Unknown, + Severity: clair.Unknown, Link: nvdURLPrefix + fix, FixedIn: []database.FeatureVersion{ { @@ -279,7 +279,7 @@ func parse34YAML(r io.Reader) (vulns []database.Vulnerability, err error) { for _, vulnStr := range vulnStrs { var vuln database.Vulnerability - vuln.Severity = types.Unknown + vuln.Severity = clair.Unknown vuln.Name = vulnStr vuln.Link = nvdURLPrefix + vulnStr vuln.FixedIn = []database.FeatureVersion{ diff --git a/ext/vulnsrc/debian/debian.go b/ext/vulnsrc/debian/debian.go index a0388e72..26860b58 100644 --- a/ext/vulnsrc/debian/debian.go +++ b/ext/vulnsrc/debian/debian.go @@ -27,12 +27,12 @@ import ( "github.com/coreos/pkg/capnslog" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) const ( @@ -158,17 +158,17 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability, vulnerability = &database.Vulnerability{ Name: vulnName, Link: strings.Join([]string{cveURLPrefix, "/", vulnName}, ""), - Severity: types.Unknown, + Severity: clair.Unknown, Description: vulnNode.Description, } } // Set the priority of the vulnerability. // In the JSON, a vulnerability has one urgency per package it affects. - // The highest urgency should be the one set. - urgency := urgencyToSeverity(releaseNode.Urgency) - if urgency.Compare(vulnerability.Severity) > 0 { - vulnerability.Severity = urgency + severity := SeverityFromUrgency(releaseNode.Urgency) + if severity.Compare(vulnerability.Severity) > 0 { + // The highest urgency should be the one set. + vulnerability.Severity = severity } // Determine the version of the package the vulnerability affects. @@ -219,39 +219,41 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability, return } -func urgencyToSeverity(urgency string) types.Priority { +// SeverityFromUrgency converts the urgency scale used by the Debian Security +// Bug Tracker into a clair.Severity. +func SeverityFromUrgency(urgency string) clair.Severity { switch urgency { case "not yet assigned": - return types.Unknown + return clair.Unknown case "end-of-life": fallthrough case "unimportant": - return types.Negligible + return clair.Negligible case "low": fallthrough case "low*": fallthrough case "low**": - return types.Low + return clair.Low case "medium": fallthrough case "medium*": fallthrough case "medium**": - return types.Medium + return clair.Medium case "high": fallthrough case "high*": fallthrough case "high**": - return types.High + return clair.High default: - log.Warningf("could not determine vulnerability priority from: %s", urgency) - return types.Unknown + log.Warningf("could not determine vulnerability severity from: %s", urgency) + return clair.Unknown } } diff --git a/ext/vulnsrc/debian/debian_test.go b/ext/vulnsrc/debian/debian_test.go index 37cb0fe0..3a4c0d70 100644 --- a/ext/vulnsrc/debian/debian_test.go +++ b/ext/vulnsrc/debian/debian_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,10 +20,10 @@ import ( "runtime" "testing" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" - "github.com/coreos/clair/utils/types" "github.com/stretchr/testify/assert" ) @@ -37,7 +37,7 @@ func TestDebianParser(t *testing.T) { for _, vulnerability := range response.Vulnerabilities { if vulnerability.Name == "CVE-2015-1323" { assert.Equal(t, "https://security-tracker.debian.org/tracker/CVE-2015-1323", vulnerability.Link) - assert.Equal(t, types.Low, vulnerability.Severity) + assert.Equal(t, clair.Low, vulnerability.Severity) assert.Equal(t, "This vulnerability is not very dangerous.", vulnerability.Description) expectedFeatureVersions := []database.FeatureVersion{ @@ -68,7 +68,7 @@ func TestDebianParser(t *testing.T) { } } else if vulnerability.Name == "CVE-2003-0779" { assert.Equal(t, "https://security-tracker.debian.org/tracker/CVE-2003-0779", vulnerability.Link) - assert.Equal(t, types.High, vulnerability.Severity) + assert.Equal(t, clair.High, vulnerability.Severity) assert.Equal(t, "But this one is very dangerous.", vulnerability.Description) expectedFeatureVersions := []database.FeatureVersion{ @@ -109,7 +109,7 @@ func TestDebianParser(t *testing.T) { } } else if vulnerability.Name == "CVE-2013-2685" { assert.Equal(t, "https://security-tracker.debian.org/tracker/CVE-2013-2685", vulnerability.Link) - assert.Equal(t, types.Negligible, vulnerability.Severity) + assert.Equal(t, clair.Negligible, vulnerability.Severity) assert.Equal(t, "Un-affected packages.", vulnerability.Description) expectedFeatureVersions := []database.FeatureVersion{ diff --git a/ext/vulnsrc/oracle/oracle.go b/ext/vulnsrc/oracle/oracle.go index 569752ad..e82d18f9 100644 --- a/ext/vulnsrc/oracle/oracle.go +++ b/ext/vulnsrc/oracle/oracle.go @@ -25,13 +25,14 @@ import ( "strconv" "strings" + "github.com/coreos/pkg/capnslog" + + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/rpm" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" - "github.com/coreos/pkg/capnslog" ) const ( @@ -172,7 +173,7 @@ func parseELSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, vulnerability := database.Vulnerability{ Name: name(definition), Link: link(definition), - Severity: priority(definition), + Severity: severity(definition), Description: description(definition), } for _, p := range pkgs { @@ -336,24 +337,20 @@ func link(def definition) (link string) { return } -func priority(def definition) types.Priority { - // Parse the priority. - priority := strings.ToLower(def.Severity) - - // Normalize the priority. - switch priority { +func severity(def definition) clair.Severity { + switch strings.ToLower(def.Severity) { case "n/a": - return types.Negligible + return clair.Negligible case "low": - return types.Low + return clair.Low case "moderate": - return types.Medium + return clair.Medium case "important": - return types.High + return clair.High case "critical": - return types.Critical + return clair.Critical default: - log.Warningf("could not determine vulnerability priority from: %s.", priority) - return types.Unknown + log.Warningf("could not determine vulnerability severity from: %s.", def.Severity) + return clair.Unknown } } diff --git a/ext/vulnsrc/oracle/oracle_test.go b/ext/vulnsrc/oracle/oracle_test.go index d23ef866..2a33207c 100644 --- a/ext/vulnsrc/oracle/oracle_test.go +++ b/ext/vulnsrc/oracle/oracle_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import ( "runtime" "testing" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt/rpm" - "github.com/coreos/clair/utils/types" "github.com/stretchr/testify/assert" ) @@ -38,7 +38,7 @@ func TestOracleParser(t *testing.T) { if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) { assert.Equal(t, "ELSA-2015-1193", vulnerabilities[0].Name) assert.Equal(t, "http://linux.oracle.com/errata/ELSA-2015-1193.html", vulnerabilities[0].Link) - assert.Equal(t, types.Medium, vulnerabilities[0].Severity) + assert.Equal(t, clair.Medium, vulnerabilities[0].Severity) assert.Equal(t, ` [3.1.1-7] Resolves: rhbz#1217104 CVE-2015-0252 `, vulnerabilities[0].Description) expectedFeatureVersions := []database.FeatureVersion{ @@ -86,7 +86,7 @@ func TestOracleParser(t *testing.T) { if assert.Nil(t, err) && assert.Len(t, vulnerabilities, 1) { assert.Equal(t, "ELSA-2015-1207", vulnerabilities[0].Name) assert.Equal(t, "http://linux.oracle.com/errata/ELSA-2015-1207.html", vulnerabilities[0].Link) - assert.Equal(t, types.Critical, vulnerabilities[0].Severity) + assert.Equal(t, clair.Critical, vulnerabilities[0].Severity) assert.Equal(t, ` [38.1.0-1.0.1.el7_1] - Add firefox-oracle-default-prefs.js and remove the corresponding Red Hat file [38.1.0-1] - Update to 38.1.0 ESR [38.0.1-2] - Fixed rhbz#1222807 by removing preun section `, vulnerabilities[0].Description) expectedFeatureVersions := []database.FeatureVersion{ { diff --git a/ext/vulnsrc/rhel/rhel.go b/ext/vulnsrc/rhel/rhel.go index f1d88986..9dd66bcc 100644 --- a/ext/vulnsrc/rhel/rhel.go +++ b/ext/vulnsrc/rhel/rhel.go @@ -27,12 +27,12 @@ import ( "github.com/coreos/pkg/capnslog" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/rpm" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/pkg/commonerr" - "github.com/coreos/clair/utils/types" ) const ( @@ -175,7 +175,7 @@ func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, vulnerability := database.Vulnerability{ Name: name(definition), Link: link(definition), - Severity: priority(definition), + Severity: severity(definition), Description: description(definition), } for _, p := range pkgs { @@ -344,22 +344,18 @@ func link(def definition) (link string) { 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 { +func severity(def definition) clair.Severity { + switch strings.TrimSpace(def.Title[strings.LastIndex(def.Title, "(")+1 : len(def.Title)-1]) { case "Low": - return types.Low + return clair.Low case "Moderate": - return types.Medium + return clair.Medium case "Important": - return types.High + return clair.High case "Critical": - return types.Critical + return clair.Critical default: - log.Warning("could not determine vulnerability priority from: %s.", priority) - return types.Unknown + log.Warning("could not determine vulnerability severity from: %s.", def.Title) + return clair.Unknown } } diff --git a/ext/vulnsrc/rhel/rhel_test.go b/ext/vulnsrc/rhel/rhel_test.go index 2b87eec9..7aa13938 100644 --- a/ext/vulnsrc/rhel/rhel_test.go +++ b/ext/vulnsrc/rhel/rhel_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import ( "runtime" "testing" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt/rpm" - "github.com/coreos/clair/utils/types" "github.com/stretchr/testify/assert" ) @@ -36,7 +36,7 @@ func TestRHELParser(t *testing.T) { 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) - assert.Equal(t, types.Medium, vulnerabilities[0].Severity) + assert.Equal(t, clair.Medium, vulnerabilities[0].Severity) assert.Equal(t, `Xerces-C is a validating XML parser written in a portable subset of C++. A flaw was found in the way the Xerces-C XML parser processed certain XML documents. A remote attacker could provide specially crafted XML input that, when parsed by an application using Xerces-C, would cause that application to crash.`, vulnerabilities[0].Description) expectedFeatureVersions := []database.FeatureVersion{ @@ -83,7 +83,7 @@ func TestRHELParser(t *testing.T) { 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) - assert.Equal(t, types.Critical, vulnerabilities[0].Severity) + assert.Equal(t, clair.Critical, vulnerabilities[0].Severity) assert.Equal(t, `Mozilla Firefox is an open source web browser. XULRunner provides the XUL Runtime environment for Mozilla Firefox. Several flaws were found in the processing of malformed web content. A web page containing malicious content could cause Firefox to crash or, potentially, execute arbitrary code with the privileges of the user running Firefox.`, vulnerabilities[0].Description) expectedFeatureVersions := []database.FeatureVersion{ diff --git a/ext/vulnsrc/ubuntu/ubuntu.go b/ext/vulnsrc/ubuntu/ubuntu.go index 0a74db4a..d74b6122 100644 --- a/ext/vulnsrc/ubuntu/ubuntu.go +++ b/ext/vulnsrc/ubuntu/ubuntu.go @@ -29,13 +29,13 @@ import ( "github.com/coreos/pkg/capnslog" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt/dpkg" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/pkg/commonerr" "github.com/coreos/clair/utils" - "github.com/coreos/clair/utils/types" ) const ( @@ -301,7 +301,7 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability priority = priority[:strings.Index(priority, " ")] } - vulnerability.Severity = ubuntuPriorityToSeverity(priority) + vulnerability.Severity = SeverityFromPriority(priority) continue } @@ -388,28 +388,30 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability // If no priority has been provided (CVE-2007-0667 for instance), set the priority to Unknown if vulnerability.Severity == "" { - vulnerability.Severity = types.Unknown + vulnerability.Severity = clair.Unknown } return } -func ubuntuPriorityToSeverity(priority string) types.Priority { +// SeverityFromPriority converts an priority from the Ubuntu CVE Tracker into +// a clair.Severity. +func SeverityFromPriority(priority string) clair.Severity { switch priority { case "untriaged": - return types.Unknown + return clair.Unknown case "negligible": - return types.Negligible + return clair.Negligible case "low": - return types.Low + return clair.Low case "medium": - return types.Medium + return clair.Medium case "high": - return types.High + return clair.High case "critical": - return types.Critical + return clair.Critical + default: + log.Warning("could not determine a vulnerability severity from: %s", priority) + return clair.Unknown } - - log.Warning("Could not determine a vulnerability priority from: %s", priority) - return types.Unknown } diff --git a/ext/vulnsrc/ubuntu/ubuntu_test.go b/ext/vulnsrc/ubuntu/ubuntu_test.go index e1c84637..9ebeba7a 100644 --- a/ext/vulnsrc/ubuntu/ubuntu_test.go +++ b/ext/vulnsrc/ubuntu/ubuntu_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,9 +22,9 @@ import ( "github.com/stretchr/testify/assert" + "github.com/coreos/clair" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/versionfmt" - "github.com/coreos/clair/utils/types" ) func TestUbuntuParser(t *testing.T) { @@ -37,7 +37,7 @@ func TestUbuntuParser(t *testing.T) { vulnerability, unknownReleases, err := parseUbuntuCVE(testData) if assert.Nil(t, err) { assert.Equal(t, "CVE-2015-4471", vulnerability.Name) - assert.Equal(t, types.Medium, vulnerability.Severity) + assert.Equal(t, clair.Medium, vulnerability.Severity) assert.Equal(t, "Off-by-one error in the lzxd_decompress function in lzxd.c in libmspack before 0.5 allows remote attackers to cause a denial of service (buffer under-read and application crash) via a crafted CAB archive.", vulnerability.Description) // Unknown release (line 28) diff --git a/utils/types/priority.go b/severity.go similarity index 56% rename from utils/types/priority.go rename to severity.go index aac56d40..46bb9927 100644 --- a/utils/types/priority.go +++ b/severity.go @@ -1,4 +1,4 @@ -// Copyright 2015 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,80 +12,104 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package types defines useful types that are used in database models. -package types +package clair import ( "database/sql/driver" "errors" - "fmt" + "strings" ) -// Priority defines a vulnerability priority -type Priority string +var ( + // ErrFailedToParseSeverity is the error returned when a severity could not + // be parsed from a string. + ErrFailedToParseSeverity = errors.New("failed to parse Severity from input") +) + +// Severity defines a standard scale for measuring the severity of a +// vulnerability. +type Severity string const ( // Unknown is either a security problem that has not been // assigned to a priority yet or a priority that our system - // did not recognize - Unknown Priority = "Unknown" + // did not recognize. + Unknown Severity = "Unknown" + // Negligible is technically a security problem, but is // only theoretical in nature, requires a very special // situation, has almost no install base, or does no real // damage. These tend not to get backport from upstreams, // and will likely not be included in security updates unless // there is an easy fix and some other issue causes an update. - Negligible Priority = "Negligible" + Negligible Severity = "Negligible" + // Low is a security problem, but is hard to // exploit due to environment, requires a user-assisted // attack, a small install base, or does very little damage. // These tend to be included in security updates only when // higher priority issues require an update, or if many // low priority issues have built up. - Low Priority = "Low" + Low Severity = "Low" + // Medium is a real security problem, and is exploitable // for many people. Includes network daemon denial of service // attacks, cross-site scripting, and gaining user privileges. // Updates should be made soon for this priority of issue. - Medium Priority = "Medium" + Medium Severity = "Medium" + // High is a real problem, exploitable for many people in a default // installation. Includes serious remote denial of services, // local root privilege escalations, or data loss. - High Priority = "High" + High Severity = "High" + // Critical is a world-burning problem, exploitable for nearly all people // in a default installation of Linux. Includes remote root // privilege escalations, or massive data loss. - Critical Priority = "Critical" + Critical Severity = "Critical" + // Defcon1 is a Critical problem which has been manually highlighted by // the team. It requires an immediate attention. - Defcon1 Priority = "Defcon1" + Defcon1 Severity = "Defcon1" ) -// Priorities lists all known priorities, ordered from lower to higher -var Priorities = []Priority{Unknown, Negligible, Low, Medium, High, Critical, Defcon1} +// Severities lists all known severities, ordered from lowest to highest. +var Severities = []Severity{ + Unknown, + Negligible, + Low, + Medium, + High, + Critical, + Defcon1, +} -// IsValid determines if the priority is a valid one -func (p Priority) IsValid() bool { - for _, pp := range Priorities { - if p == pp { - return true +// NewSeverity attempts to parse a string into a standard Severity value. +func NewSeverity(s string) (Severity, error) { + for _, ss := range Severities { + if strings.EqualFold(s, string(ss)) { + return ss, nil } } - return false + return Unknown, ErrFailedToParseSeverity } -// Compare compares two priorities -func (p Priority) Compare(p2 Priority) int { +// Compare determines the equality of two severities. +// +// If the severities are equal, returns 0. +// If the receiever is less, returns -1. +// If the receiver is greater, returns 1. +func (s Severity) Compare(s2 Severity) int { var i1, i2 int - for i1 = 0; i1 < len(Priorities); i1 = i1 + 1 { - if p == Priorities[i1] { + for i1 = 0; i1 < len(Severities); i1 = i1 + 1 { + if s == Severities[i1] { break } } - for i2 = 0; i2 < len(Priorities); i2 = i2 + 1 { - if p2 == Priorities[i2] { + for i2 = 0; i2 < len(Severities); i2 = i2 + 1 { + if s2 == Severities[i2] { break } } @@ -93,18 +117,23 @@ func (p Priority) Compare(p2 Priority) int { return i1 - i2 } -func (p *Priority) Scan(value interface{}) error { +// Scan implements the database/sql.Scanner interface. +func (s *Severity) Scan(value interface{}) error { val, ok := value.([]byte) if !ok { - return errors.New("could not scan a Priority from a non-string input") + return errors.New("could not scan a Severity from a non-string input") } - *p = Priority(string(val)) - if !p.IsValid() { - return fmt.Errorf("could not scan an invalid Priority (%v)", p) + + var err error + *s, err = NewSeverity(string(val)) + if err != nil { + return err } + return nil } -func (p *Priority) Value() (driver.Value, error) { - return string(*p), nil +// Value implements the database/sql/driver.Valuer interface. +func (s Severity) Value() (driver.Value, error) { + return string(s), nil } diff --git a/utils/types/priority_test.go b/severity_test.go similarity index 59% rename from utils/types/priority_test.go rename to severity_test.go index 15558d5a..6bb180ef 100644 --- a/utils/types/priority_test.go +++ b/severity_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 clair authors +// Copyright 2017 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package types +package clair import ( "testing" @@ -20,13 +20,16 @@ import ( "github.com/stretchr/testify/assert" ) -func TestComparePriority(t *testing.T) { - assert.Equal(t, Medium.Compare(Medium), 0, "Priority comparison failed") - assert.True(t, Medium.Compare(High) < 0, "Priority comparison failed") - assert.True(t, Critical.Compare(Low) > 0, "Priority comparison failed") +func TestCompareSeverity(t *testing.T) { + assert.Equal(t, Medium.Compare(Medium), 0, "Severity comparison failed") + assert.True(t, Medium.Compare(High) < 0, "Severity comparison failed") + assert.True(t, Critical.Compare(Low) > 0, "Severity comparison failed") } -func TestIsValid(t *testing.T) { - assert.False(t, Priority("Test").IsValid()) - assert.True(t, Unknown.IsValid()) +func TestParseSeverity(t *testing.T) { + _, err := NewSeverity("Test") + assert.Equal(t, ErrFailedToParseSeverity, err) + + _, err = NewSeverity("Unknown") + assert.Nil(t, err) } diff --git a/updater/updater.go b/updater/updater.go index a7b4e558..0ae34970 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -27,12 +27,12 @@ import ( "github.com/pborman/uuid" "github.com/prometheus/client_golang/prometheus" + "github.com/coreos/clair" "github.com/coreos/clair/config" "github.com/coreos/clair/database" "github.com/coreos/clair/ext/vulnmdsrc" "github.com/coreos/clair/ext/vulnsrc" "github.com/coreos/clair/utils" - "github.com/coreos/clair/utils/types" ) const ( @@ -316,7 +316,7 @@ type lockableVulnerability struct { sync.Mutex } -func (lv *lockableVulnerability) appendFunc(metadataKey string, metadata interface{}, severity types.Priority) { +func (lv *lockableVulnerability) appendFunc(metadataKey string, metadata interface{}, severity clair.Severity) { lv.Lock() defer lv.Unlock() @@ -329,7 +329,7 @@ func (lv *lockableVulnerability) appendFunc(metadataKey string, metadata interfa lv.Metadata[metadataKey] = metadata // If necessary, provide a severity for the vulnerability. - if lv.Severity == "" || lv.Severity == types.Unknown { + if lv.Severity == clair.Unknown { lv.Severity = severity } }