refactor: move updaters and notifier into ext
This commit is contained in:
parent
f66103c773
commit
4a990372ff
@ -25,15 +25,14 @@ import (
|
|||||||
"github.com/coreos/clair"
|
"github.com/coreos/clair"
|
||||||
"github.com/coreos/clair/config"
|
"github.com/coreos/clair/config"
|
||||||
|
|
||||||
// Register components
|
// Register extensions.
|
||||||
_ "github.com/coreos/clair/notifier/notifiers"
|
_ "github.com/coreos/clair/ext/notification/webhook"
|
||||||
|
_ "github.com/coreos/clair/ext/vulnmdsrc/nvd"
|
||||||
_ "github.com/coreos/clair/updater/fetchers/alpine"
|
_ "github.com/coreos/clair/ext/vulnsrc/alpine"
|
||||||
_ "github.com/coreos/clair/updater/fetchers/debian"
|
_ "github.com/coreos/clair/ext/vulnsrc/debian"
|
||||||
_ "github.com/coreos/clair/updater/fetchers/oracle"
|
_ "github.com/coreos/clair/ext/vulnsrc/oracle"
|
||||||
_ "github.com/coreos/clair/updater/fetchers/rhel"
|
_ "github.com/coreos/clair/ext/vulnsrc/rhel"
|
||||||
_ "github.com/coreos/clair/updater/fetchers/ubuntu"
|
_ "github.com/coreos/clair/ext/vulnsrc/ubuntu"
|
||||||
_ "github.com/coreos/clair/updater/metadata_fetchers/nvd"
|
|
||||||
|
|
||||||
_ "github.com/coreos/clair/worker/detectors/data/aci"
|
_ "github.com/coreos/clair/worker/detectors/data/aci"
|
||||||
_ "github.com/coreos/clair/worker/detectors/data/docker"
|
_ "github.com/coreos/clair/worker/detectors/data/docker"
|
||||||
|
65
ext/notification/driver.go
Normal file
65
ext/notification/driver.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 notification fetches notifications from the database and informs the
|
||||||
|
// specified remote handler about their existences, inviting the third party to
|
||||||
|
// actively query the API about it.
|
||||||
|
|
||||||
|
// Package notification exposes functions to dynamically register methods to
|
||||||
|
// deliver notifications from the Clair database.
|
||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/config"
|
||||||
|
"github.com/coreos/clair/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/notification")
|
||||||
|
|
||||||
|
// Senders is the list of registered Senders.
|
||||||
|
Senders = make(map[string]Sender)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sender represents anything that can transmit notifications.
|
||||||
|
type Sender interface {
|
||||||
|
// Configure attempts to initialize the notifier with the provided configuration.
|
||||||
|
// It returns whether the notifier is enabled or not.
|
||||||
|
Configure(*config.NotifierConfig) (bool, error)
|
||||||
|
|
||||||
|
// Send informs the existence of the specified notification.
|
||||||
|
Send(notification database.VulnerabilityNotification) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSender makes a Sender available by the provided name.
|
||||||
|
//
|
||||||
|
// If RegisterSender is called twice with the same name, the name is blank, or
|
||||||
|
// if the provided Sender is nil, this function panics.
|
||||||
|
func RegisterSender(name string, s Sender) {
|
||||||
|
if name == "" {
|
||||||
|
panic("notification: could not register a Sender with an empty name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
panic("notification: could not register a nil Sender")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, dup := Senders[name]; dup {
|
||||||
|
panic("notification: RegisterSender called twice for " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Senders[name] = s
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,8 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package notifiers implements several kinds of notifier.Notifier
|
// Package webhook implements a notification sender for HTTP JSON webhooks.
|
||||||
package notifiers
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -31,19 +31,18 @@ import (
|
|||||||
|
|
||||||
"github.com/coreos/clair/config"
|
"github.com/coreos/clair/config"
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/notifier"
|
"github.com/coreos/clair/ext/notification"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeout = 5 * time.Second
|
const timeout = 5 * time.Second
|
||||||
|
|
||||||
// A WebhookNotifier dispatches notifications to a webhook endpoint.
|
type sender struct {
|
||||||
type WebhookNotifier struct {
|
|
||||||
endpoint string
|
endpoint string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// A WebhookNotifierConfiguration represents the configuration of a WebhookNotifier.
|
// Config represents the configuration of a Webhook Sender.
|
||||||
type WebhookNotifierConfiguration struct {
|
type Config struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
ServerName string
|
ServerName string
|
||||||
CertFile string
|
CertFile string
|
||||||
@ -53,12 +52,12 @@ type WebhookNotifierConfiguration struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
notifier.RegisterNotifier("webhook", &WebhookNotifier{})
|
notification.RegisterSender("webhook", &sender{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebhookNotifier) Configure(config *config.NotifierConfig) (bool, error) {
|
func (s *sender) Configure(config *config.NotifierConfig) (bool, error) {
|
||||||
// Get configuration
|
// Get configuration
|
||||||
var httpConfig WebhookNotifierConfiguration
|
var httpConfig Config
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -81,11 +80,11 @@ func (h *WebhookNotifier) Configure(config *config.NotifierConfig) (bool, error)
|
|||||||
if _, err := url.ParseRequestURI(httpConfig.Endpoint); err != nil {
|
if _, err := url.ParseRequestURI(httpConfig.Endpoint); err != nil {
|
||||||
return false, fmt.Errorf("could not parse endpoint URL: %s\n", err)
|
return false, fmt.Errorf("could not parse endpoint URL: %s\n", err)
|
||||||
}
|
}
|
||||||
h.endpoint = httpConfig.Endpoint
|
s.endpoint = httpConfig.Endpoint
|
||||||
|
|
||||||
// Setup HTTP client.
|
// Setup HTTP client.
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{}
|
||||||
h.client = &http.Client{
|
s.client = &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
@ -114,7 +113,7 @@ type notificationEnvelope struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebhookNotifier) Send(notification database.VulnerabilityNotification) error {
|
func (s *sender) Send(notification database.VulnerabilityNotification) error {
|
||||||
// Marshal notification.
|
// Marshal notification.
|
||||||
jsonNotification, err := json.Marshal(notificationEnvelope{struct{ Name string }{notification.Name}})
|
jsonNotification, err := json.Marshal(notificationEnvelope{struct{ Name string }{notification.Name}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,7 +121,7 @@ func (h *WebhookNotifier) Send(notification database.VulnerabilityNotification)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send notification via HTTP POST.
|
// Send notification via HTTP POST.
|
||||||
resp, err := h.client.Post(h.endpoint, "application/json", bytes.NewBuffer(jsonNotification))
|
resp, err := s.client.Post(s.endpoint, "application/json", bytes.NewBuffer(jsonNotification))
|
||||||
if err != nil || resp == nil || (resp.StatusCode != 200 && resp.StatusCode != 201) {
|
if err != nil || resp == nil || (resp.StatusCode != 200 && resp.StatusCode != 201) {
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
return fmt.Errorf("got status %d, expected 200/201", resp.StatusCode)
|
return fmt.Errorf("got status %d, expected 200/201", resp.StatusCode)
|
||||||
@ -134,11 +133,11 @@ func (h *WebhookNotifier) Send(notification database.VulnerabilityNotification)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadTLSClientConfig initializes a *tls.Config using the given WebhookNotifierConfiguration.
|
// loadTLSClientConfig initializes a *tls.Config using the given Config.
|
||||||
//
|
//
|
||||||
// If no certificates are given, (nil, nil) is returned.
|
// If no certificates are given, (nil, nil) is returned.
|
||||||
// The CA certificate is optional and falls back to the system default.
|
// The CA certificate is optional and falls back to the system default.
|
||||||
func loadTLSClientConfig(cfg *WebhookNotifierConfiguration) (*tls.Config, error) {
|
func loadTLSClientConfig(cfg *Config) (*tls.Config, error) {
|
||||||
if cfg.CertFile == "" || cfg.KeyFile == "" {
|
if cfg.CertFile == "" || cfg.KeyFile == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
68
ext/vulnmdsrc/driver.go
Normal file
68
ext/vulnmdsrc/driver.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 vulnmdsrc exposes functions to dynamically register vulnerability
|
||||||
|
// metadata sources used to update a Clair database.
|
||||||
|
package vulnmdsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/utils/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Appenders is the list of registered Appenders.
|
||||||
|
var Appenders = make(map[string]Appender)
|
||||||
|
|
||||||
|
// AppendFunc is the type of a callback provided to an Appender.
|
||||||
|
type AppendFunc func(metadataKey string, metadata interface{}, severity types.Priority)
|
||||||
|
|
||||||
|
// Appender represents anything that can fetch vulnerability metadata and
|
||||||
|
// append it to a Vulnerability.
|
||||||
|
type Appender interface {
|
||||||
|
// BuildCache loads metadata into memory such that it can be quickly accessed
|
||||||
|
// for future calls to Append.
|
||||||
|
BuildCache(database.Datastore) error
|
||||||
|
|
||||||
|
// AddMetadata adds metadata to the given database.Vulnerability.
|
||||||
|
// It is expected that the fetcher uses .Lock.Lock() when manipulating the Metadata map.
|
||||||
|
// Append
|
||||||
|
Append(vulnName string, callback AppendFunc) error
|
||||||
|
|
||||||
|
// PurgeCache deallocates metadata from memory after all calls to Append are
|
||||||
|
// finished.
|
||||||
|
PurgeCache()
|
||||||
|
|
||||||
|
// Clean deletes any allocated resources.
|
||||||
|
// It is invoked when Clair stops.
|
||||||
|
Clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterAppender makes an Appender available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func RegisterAppender(name string, a Appender) {
|
||||||
|
if name == "" {
|
||||||
|
panic("updater: could not register an Appender with an empty name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if a == nil {
|
||||||
|
panic("vulnmdsrc: could not register a nil Appender")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, dup := Appenders[name]; dup {
|
||||||
|
panic("vulnmdsrc: RegisterAppender called twice for " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Appenders[name] = a
|
||||||
|
}
|
33
ext/vulnmdsrc/nvd/nested_read_closer.go
Normal file
33
ext/vulnmdsrc/nvd/nested_read_closer.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 nvd
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// NestedReadCloser wraps an io.Reader and implements io.ReadCloser by closing every embed
|
||||||
|
// io.ReadCloser.
|
||||||
|
// It allows chaining io.ReadCloser together and still keep the ability to close them all in a
|
||||||
|
// simple manner.
|
||||||
|
type NestedReadCloser struct {
|
||||||
|
io.Reader
|
||||||
|
NestedReadClosers []io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the gzip.Reader and the underlying io.ReadCloser.
|
||||||
|
func (nrc *NestedReadCloser) Close() {
|
||||||
|
for _, nestedReadCloser := range nrc.NestedReadClosers {
|
||||||
|
nestedReadCloser.Close()
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,19 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 nvd implements a vulnerability metadata appender using the NIST NVD
|
||||||
|
// database.
|
||||||
package nvd
|
package nvd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -15,30 +31,28 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/pkg/capnslog"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnmdsrc"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dataFeedURL string = "http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%s.xml.gz"
|
dataFeedURL string = "http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%s.xml.gz"
|
||||||
dataFeedMetaURL string = "http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%s.meta"
|
dataFeedMetaURL string = "http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-%s.meta"
|
||||||
|
|
||||||
metadataKey string = "NVD"
|
appenderName string = "NVD"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/vulnmdsrc/nvd")
|
||||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/metadata_fetchers")
|
|
||||||
)
|
|
||||||
|
|
||||||
type NVDMetadataFetcher struct {
|
type appender struct {
|
||||||
localPath string
|
localPath string
|
||||||
dataFeedHashes map[string]string
|
dataFeedHashes map[string]string
|
||||||
lock sync.Mutex
|
|
||||||
|
|
||||||
metadata map[string]NVDMetadata
|
metadata map[string]NVDMetadata
|
||||||
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type NVDMetadata struct {
|
type NVDMetadata struct {
|
||||||
@ -51,32 +65,32 @@ type NVDmetadataCVSSv2 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterMetadataFetcher("NVD", &NVDMetadataFetcher{})
|
vulnmdsrc.RegisterAppender(appenderName, &appender{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fetcher *NVDMetadataFetcher) Load(datastore database.Datastore) error {
|
func (a *appender) BuildCache(datastore database.Datastore) error {
|
||||||
fetcher.lock.Lock()
|
a.Lock()
|
||||||
defer fetcher.lock.Unlock()
|
defer a.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
fetcher.metadata = make(map[string]NVDMetadata)
|
a.metadata = make(map[string]NVDMetadata)
|
||||||
|
|
||||||
// Init if necessary.
|
// Init if necessary.
|
||||||
if fetcher.localPath == "" {
|
if a.localPath == "" {
|
||||||
// Create a temporary folder to store the NVD data and create hashes struct.
|
// Create a temporary folder to store the NVD data and create hashes struct.
|
||||||
if fetcher.localPath, err = ioutil.TempDir(os.TempDir(), "nvd-data"); err != nil {
|
if a.localPath, err = ioutil.TempDir(os.TempDir(), "nvd-data"); err != nil {
|
||||||
return cerrors.ErrFilesystem
|
return cerrors.ErrFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher.dataFeedHashes = make(map[string]string)
|
a.dataFeedHashes = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get data feeds.
|
// Get data feeds.
|
||||||
dataFeedReaders, dataFeedHashes, err := getDataFeeds(fetcher.dataFeedHashes, fetcher.localPath)
|
dataFeedReaders, dataFeedHashes, err := getDataFeeds(a.dataFeedHashes, a.localPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fetcher.dataFeedHashes = dataFeedHashes
|
a.dataFeedHashes = dataFeedHashes
|
||||||
|
|
||||||
// Parse data feeds.
|
// Parse data feeds.
|
||||||
for dataFeedName, dataFeedReader := range dataFeedReaders {
|
for dataFeedName, dataFeedReader := range dataFeedReaders {
|
||||||
@ -90,7 +104,7 @@ func (fetcher *NVDMetadataFetcher) Load(datastore database.Datastore) error {
|
|||||||
for _, nvdEntry := range nvd.Entries {
|
for _, nvdEntry := range nvd.Entries {
|
||||||
// Create metadata entry.
|
// Create metadata entry.
|
||||||
if metadata := nvdEntry.Metadata(); metadata != nil {
|
if metadata := nvdEntry.Metadata(); metadata != nil {
|
||||||
fetcher.metadata[nvdEntry.Name] = *metadata
|
a.metadata[nvdEntry.Name] = *metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,42 +114,29 @@ func (fetcher *NVDMetadataFetcher) Load(datastore database.Datastore) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fetcher *NVDMetadataFetcher) AddMetadata(vulnerability *updater.VulnerabilityWithLock) error {
|
func (a *appender) Append(vulnName string, appendFunc vulnmdsrc.AppendFunc) error {
|
||||||
fetcher.lock.Lock()
|
a.Lock()
|
||||||
defer fetcher.lock.Unlock()
|
defer a.Unlock()
|
||||||
|
|
||||||
if nvdMetadata, ok := fetcher.metadata[vulnerability.Name]; ok {
|
if nvdMetadata, ok := a.metadata[vulnName]; ok {
|
||||||
vulnerability.Lock.Lock()
|
appendFunc(appenderName, nvdMetadata, scoreToPriority(nvdMetadata.CVSSv2.Score))
|
||||||
|
|
||||||
// Create Metadata map if necessary and assign the NVD metadata.
|
|
||||||
if vulnerability.Metadata == nil {
|
|
||||||
vulnerability.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
vulnerability.Metadata[metadataKey] = nvdMetadata
|
|
||||||
|
|
||||||
// Set the Severity using the CVSSv2 Score if none is set yet.
|
|
||||||
if vulnerability.Severity == "" || vulnerability.Severity == types.Unknown {
|
|
||||||
vulnerability.Severity = scoreToPriority(nvdMetadata.CVSSv2.Score)
|
|
||||||
}
|
|
||||||
|
|
||||||
vulnerability.Lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fetcher *NVDMetadataFetcher) Unload() {
|
func (a *appender) PurgeCache() {
|
||||||
fetcher.lock.Lock()
|
a.Lock()
|
||||||
defer fetcher.lock.Unlock()
|
defer a.Unlock()
|
||||||
|
|
||||||
fetcher.metadata = nil
|
a.metadata = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fetcher *NVDMetadataFetcher) Clean() {
|
func (a *appender) Clean() {
|
||||||
fetcher.lock.Lock()
|
a.Lock()
|
||||||
defer fetcher.lock.Unlock()
|
defer a.Unlock()
|
||||||
|
|
||||||
os.RemoveAll(fetcher.localPath)
|
os.RemoveAll(a.localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDataFeeds(dataFeedHashes map[string]string, localPath string) (map[string]NestedReadCloser, map[string]string, error) {
|
func getDataFeeds(dataFeedHashes map[string]string, localPath string) (map[string]NestedReadCloser, map[string]string, error) {
|
@ -1,3 +1,17 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 nvd
|
package nvd
|
||||||
|
|
||||||
import (
|
import (
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,12 +12,11 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package alpine implements a vulnerability Fetcher using the alpine-secdb
|
// Package alpine implements a vulnerability source updater using the
|
||||||
// git repository.
|
// alpine-secdb git repository.
|
||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -31,7 +30,7 @@ import (
|
|||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/ext/versionfmt"
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnsrc"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
@ -45,29 +44,23 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrFilesystem is returned when a fetcher fails to interact with the local filesystem.
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/vulnsrc/alpine")
|
||||||
ErrFilesystem = errors.New("updater/fetchers: something went wrong when interacting with the fs")
|
|
||||||
|
|
||||||
// ErrGitFailure is returned when a fetcher fails to interact with git.
|
|
||||||
ErrGitFailure = errors.New("updater/fetchers: something went wrong when interacting with git")
|
|
||||||
|
|
||||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/alpine")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterFetcher("alpine", &fetcher{})
|
vulnsrc.RegisterUpdater("alpine", &updater{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type fetcher struct {
|
type updater struct {
|
||||||
repositoryLocalPath string
|
repositoryLocalPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) FetchUpdate(db database.Datastore) (resp updater.FetcherResponse, err error) {
|
func (u *updater) Update(db database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
log.Info("fetching Alpine vulnerabilities")
|
log.Info("fetching Alpine vulnerabilities")
|
||||||
|
|
||||||
// Pull the master branch.
|
// Pull the master branch.
|
||||||
var commit string
|
var commit string
|
||||||
commit, err = f.pullRepository()
|
commit, err = u.pullRepository()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,12 +83,12 @@ func (f *fetcher) FetchUpdate(db database.Datastore) (resp updater.FetcherRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
var namespaces []string
|
var namespaces []string
|
||||||
namespaces, err = detectNamespaces(f.repositoryLocalPath)
|
namespaces, err = detectNamespaces(u.repositoryLocalPath)
|
||||||
// Append any changed vulnerabilities to the response.
|
// Append any changed vulnerabilities to the response.
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
var vulns []database.Vulnerability
|
var vulns []database.Vulnerability
|
||||||
var note string
|
var note string
|
||||||
vulns, note, err = parseVulnsFromNamespace(f.repositoryLocalPath, namespace)
|
vulns, note, err = parseVulnsFromNamespace(u.repositoryLocalPath, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -108,6 +101,12 @@ func (f *fetcher) FetchUpdate(db database.Datastore) (resp updater.FetcherRespon
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) Clean() {
|
||||||
|
if u.repositoryLocalPath != "" {
|
||||||
|
os.RemoveAll(u.repositoryLocalPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func detectNamespaces(path string) ([]string, error) {
|
func detectNamespaces(path string) ([]string, error) {
|
||||||
// Open the root directory.
|
// Open the root directory.
|
||||||
dir, err := os.Open(path)
|
dir, err := os.Open(path)
|
||||||
@ -163,41 +162,35 @@ func parseVulnsFromNamespace(repositoryPath, namespace string) (vulns []database
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) pullRepository() (commit string, err error) {
|
func (u *updater) pullRepository() (commit string, err error) {
|
||||||
// If the repository doesn't exist, clone it.
|
// If the repository doesn't exist, clone it.
|
||||||
if _, pathExists := os.Stat(f.repositoryLocalPath); f.repositoryLocalPath == "" || os.IsNotExist(pathExists) {
|
if _, pathExists := os.Stat(u.repositoryLocalPath); u.repositoryLocalPath == "" || os.IsNotExist(pathExists) {
|
||||||
if f.repositoryLocalPath, err = ioutil.TempDir(os.TempDir(), "alpine-secdb"); err != nil {
|
if u.repositoryLocalPath, err = ioutil.TempDir(os.TempDir(), "alpine-secdb"); err != nil {
|
||||||
return "", ErrFilesystem
|
return "", vulnsrc.ErrFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
if out, err := utils.Exec(f.repositoryLocalPath, "git", "clone", secdbGitURL, "."); err != nil {
|
if out, err := utils.Exec(u.repositoryLocalPath, "git", "clone", secdbGitURL, "."); err != nil {
|
||||||
f.Clean()
|
u.Clean()
|
||||||
log.Errorf("could not pull alpine-secdb repository: %s. output: %s", err, out)
|
log.Errorf("could not pull alpine-secdb repository: %s. output: %s", err, out)
|
||||||
return "", cerrors.ErrCouldNotDownload
|
return "", cerrors.ErrCouldNotDownload
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The repository exists and it needs to be refreshed via a pull.
|
// The repository exists and it needs to be refreshed via a pull.
|
||||||
_, err := utils.Exec(f.repositoryLocalPath, "git", "pull")
|
_, err := utils.Exec(u.repositoryLocalPath, "git", "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ErrGitFailure
|
return "", vulnsrc.ErrGitFailure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := utils.Exec(f.repositoryLocalPath, "git", "rev-parse", "HEAD")
|
out, err := utils.Exec(u.repositoryLocalPath, "git", "rev-parse", "HEAD")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ErrGitFailure
|
return "", vulnsrc.ErrGitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
commit = strings.TrimSpace(string(out))
|
commit = strings.TrimSpace(string(out))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Clean() {
|
|
||||||
if f.repositoryLocalPath != "" {
|
|
||||||
os.RemoveAll(f.repositoryLocalPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type secdb33File struct {
|
type secdb33File struct {
|
||||||
Distro string `yaml:"distroversion"`
|
Distro string `yaml:"distroversion"`
|
||||||
Packages []struct {
|
Packages []struct {
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package debian implements a vulnerability source updater using the Debian
|
||||||
|
// Security Tracker.
|
||||||
package debian
|
package debian
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -28,7 +30,7 @@ import (
|
|||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/ext/versionfmt"
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnsrc"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
)
|
)
|
||||||
@ -39,7 +41,7 @@ const (
|
|||||||
updaterFlag = "debianUpdater"
|
updaterFlag = "debianUpdater"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/debian")
|
var log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/vulnsrc/debian")
|
||||||
|
|
||||||
type jsonData map[string]map[string]jsonVuln
|
type jsonData map[string]map[string]jsonVuln
|
||||||
|
|
||||||
@ -54,16 +56,13 @@ type jsonRel struct {
|
|||||||
Urgency string `json:"urgency"`
|
Urgency string `json:"urgency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebianFetcher implements updater.Fetcher for the Debian Security Tracker
|
type updater struct{}
|
||||||
// (https://security-tracker.debian.org).
|
|
||||||
type DebianFetcher struct{}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterFetcher("debian", &DebianFetcher{})
|
vulnsrc.RegisterUpdater("debian", &updater{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchUpdate fetches vulnerability updates from the Debian Security Tracker.
|
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
func (fetcher *DebianFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
|
||||||
log.Info("fetching Debian vulnerabilities")
|
log.Info("fetching Debian vulnerabilities")
|
||||||
|
|
||||||
// Download JSON.
|
// Download JSON.
|
||||||
@ -88,7 +87,9 @@ func (fetcher *DebianFetcher) FetchUpdate(datastore database.Datastore) (resp up
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildResponse(jsonReader io.Reader, latestKnownHash string) (resp updater.FetcherResponse, err error) {
|
func (u *updater) Clean() {}
|
||||||
|
|
||||||
|
func buildResponse(jsonReader io.Reader, latestKnownHash string) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
hash := latestKnownHash
|
hash := latestKnownHash
|
||||||
|
|
||||||
// Defer the addition of flag information to the response.
|
// Defer the addition of flag information to the response.
|
||||||
@ -254,6 +255,3 @@ func urgencyToSeverity(urgency string) types.Priority {
|
|||||||
return types.Unknown
|
return types.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
func (fetcher *DebianFetcher) Clean() {}
|
|
73
ext/vulnsrc/driver.go
Normal file
73
ext/vulnsrc/driver.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 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.
|
||||||
|
// 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 vulnsrc exposes functions to dynamically register vulnerability
|
||||||
|
// sources used to update a Clair database.
|
||||||
|
package vulnsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Updaters is the list of registered Updaters.
|
||||||
|
Updaters = make(map[string]Updater)
|
||||||
|
|
||||||
|
// ErrFilesystem is returned when a fetcher fails to interact with the local filesystem.
|
||||||
|
ErrFilesystem = errors.New("vulnsrc: something went wrong when interacting with the fs")
|
||||||
|
|
||||||
|
// ErrGitFailure is returned when a fetcher fails to interact with git.
|
||||||
|
ErrGitFailure = errors.New("vulnsrc: something went wrong when interacting with git")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Updater represents anything that can fetch vulnerabilities and insert them
|
||||||
|
// into a Clair datastore.
|
||||||
|
type Updater interface {
|
||||||
|
// Update gets vulnerability updates.
|
||||||
|
Update(database.Datastore) (UpdateResponse, error)
|
||||||
|
|
||||||
|
// Clean deletes any allocated resources.
|
||||||
|
// It is invoked when Clair stops.
|
||||||
|
Clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResponse represents the sum of results of an update.
|
||||||
|
type UpdateResponse struct {
|
||||||
|
FlagName string
|
||||||
|
FlagValue string
|
||||||
|
Notes []string
|
||||||
|
Vulnerabilities []database.Vulnerability
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUpdater makes an Updater available by the provided name.
|
||||||
|
//
|
||||||
|
// If RegisterUpdater is called twice with the same name, the name is blank, or
|
||||||
|
// if the provided Updater is nil, this function panics.
|
||||||
|
func RegisterUpdater(name string, u Updater) {
|
||||||
|
if name == "" {
|
||||||
|
panic("vulnsrc: could not register an Updater with an empty name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if u == nil {
|
||||||
|
panic("vulnsrc: could not register a nil Updater")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, dup := Updaters[name]; dup {
|
||||||
|
panic("vulnsrc: RegisterUpdater called twice for " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Updaters[name] = u
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package oracle implements a vulnerability source updater using the
|
||||||
|
// Oracle Linux OVAL Database.
|
||||||
package oracle
|
package oracle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -26,7 +28,7 @@ import (
|
|||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/ext/versionfmt"
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/ext/versionfmt/rpm"
|
"github.com/coreos/clair/ext/versionfmt/rpm"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnsrc"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
@ -47,7 +49,7 @@ var (
|
|||||||
|
|
||||||
elsaRegexp = regexp.MustCompile(`com.oracle.elsa-(\d+).xml`)
|
elsaRegexp = regexp.MustCompile(`com.oracle.elsa-(\d+).xml`)
|
||||||
|
|
||||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/oracle")
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/vulnsrc/oracle")
|
||||||
)
|
)
|
||||||
|
|
||||||
type oval struct {
|
type oval struct {
|
||||||
@ -77,16 +79,13 @@ type criterion struct {
|
|||||||
Comment string `xml:"comment,attr"`
|
Comment string `xml:"comment,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OracleFetcher implements updater.Fetcher and gets vulnerability updates from
|
type updater struct{}
|
||||||
// the Oracle Linux OVAL definitions.
|
|
||||||
type OracleFetcher struct{}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterFetcher("Oracle", &OracleFetcher{})
|
vulnsrc.RegisterUpdater("oracle", &updater{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchUpdate gets vulnerability updates from the Oracle Linux OVAL definitions.
|
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
func (f *OracleFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
|
||||||
log.Info("fetching Oracle Linux vulnerabilities")
|
log.Info("fetching Oracle Linux vulnerabilities")
|
||||||
|
|
||||||
// Get the first ELSA we have to manage.
|
// Get the first ELSA we have to manage.
|
||||||
@ -153,6 +152,8 @@ func (f *OracleFetcher) FetchUpdate(datastore database.Datastore) (resp updater.
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) Clean() {}
|
||||||
|
|
||||||
func parseELSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
func parseELSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
||||||
// Decode the XML.
|
// Decode the XML.
|
||||||
var ov oval
|
var ov oval
|
||||||
@ -356,6 +357,3 @@ func priority(def definition) types.Priority {
|
|||||||
return types.Unknown
|
return types.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
func (f *OracleFetcher) Clean() {}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package rhel implements a vulnerability source updater using the
|
||||||
|
// Red Hat Linux OVAL Database.
|
||||||
package rhel
|
package rhel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -28,7 +30,7 @@ import (
|
|||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/ext/versionfmt"
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/ext/versionfmt/rpm"
|
"github.com/coreos/clair/ext/versionfmt/rpm"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnsrc"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
)
|
)
|
||||||
@ -82,17 +84,14 @@ type criterion struct {
|
|||||||
Comment string `xml:"comment,attr"`
|
Comment string `xml:"comment,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RHELFetcher implements updater.Fetcher and gets vulnerability updates from
|
type updater struct{}
|
||||||
// the Red Hat OVAL definitions.
|
|
||||||
type RHELFetcher struct{}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterFetcher("Red Hat", &RHELFetcher{})
|
vulnsrc.RegisterUpdater("rhel", &updater{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchUpdate gets vulnerability updates from the Red Hat OVAL definitions.
|
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
log.Info("fetching RHEL vulnerabilities")
|
||||||
log.Info("fetching Red Hat vulnerabilities")
|
|
||||||
|
|
||||||
// Get the first RHSA we have to manage.
|
// Get the first RHSA we have to manage.
|
||||||
flagValue, err := datastore.GetKeyValue(updaterFlag)
|
flagValue, err := datastore.GetKeyValue(updaterFlag)
|
||||||
@ -156,6 +155,8 @@ func (f *RHELFetcher) FetchUpdate(datastore database.Datastore) (resp updater.Fe
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *updater) Clean() {}
|
||||||
|
|
||||||
func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) {
|
||||||
// Decode the XML.
|
// Decode the XML.
|
||||||
var ov oval
|
var ov oval
|
||||||
@ -362,6 +363,3 @@ func priority(def definition) types.Priority {
|
|||||||
return types.Unknown
|
return types.Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
func (f *RHELFetcher) Clean() {}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,12 +12,13 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package ubuntu implements a vulnerability source updater using the
|
||||||
|
// Ubuntu CVE Tracker.
|
||||||
package ubuntu
|
package ubuntu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -31,7 +32,7 @@ import (
|
|||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/ext/versionfmt"
|
"github.com/coreos/clair/ext/versionfmt"
|
||||||
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
||||||
"github.com/coreos/clair/updater"
|
"github.com/coreos/clair/ext/vulnsrc"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/coreos/clair/utils/types"
|
"github.com/coreos/clair/utils/types"
|
||||||
@ -75,33 +76,27 @@ var (
|
|||||||
affectsCaptureRegexp = regexp.MustCompile(`(?P<release>.*)_(?P<package>.*): (?P<status>[^\s]*)( \(+(?P<note>[^()]*)\)+)?`)
|
affectsCaptureRegexp = regexp.MustCompile(`(?P<release>.*)_(?P<package>.*): (?P<status>[^\s]*)( \(+(?P<note>[^()]*)\)+)?`)
|
||||||
affectsCaptureRegexpNames = affectsCaptureRegexp.SubexpNames()
|
affectsCaptureRegexpNames = affectsCaptureRegexp.SubexpNames()
|
||||||
|
|
||||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/ubuntu")
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/vulnsrc/ubuntu")
|
||||||
|
|
||||||
// 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")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UbuntuFetcher implements updater.Fetcher and gets vulnerability updates from
|
type updater struct {
|
||||||
// the Ubuntu CVE Tracker.
|
|
||||||
type UbuntuFetcher struct {
|
|
||||||
repositoryLocalPath string
|
repositoryLocalPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
updater.RegisterFetcher("Ubuntu", &UbuntuFetcher{})
|
vulnsrc.RegisterUpdater("ubuntu", &updater{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchUpdate gets vulnerability updates from the Ubuntu CVE Tracker.
|
func (u *updater) Update(datastore database.Datastore) (resp vulnsrc.UpdateResponse, err error) {
|
||||||
func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp updater.FetcherResponse, err error) {
|
|
||||||
log.Info("fetching Ubuntu vulnerabilities")
|
log.Info("fetching Ubuntu vulnerabilities")
|
||||||
|
|
||||||
// Pull the bzr repository.
|
// Pull the bzr repository.
|
||||||
if err = fetcher.pullRepository(); err != nil {
|
if err = u.pullRepository(); err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get revision number.
|
// Get revision number.
|
||||||
revisionNumber, err := getRevisionNumber(fetcher.repositoryLocalPath)
|
revisionNumber, err := getRevisionNumber(u.repositoryLocalPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@ -113,7 +108,7 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the list of vulnerabilities that we have to update.
|
// Get the list of vulnerabilities that we have to update.
|
||||||
modifiedCVE, err := collectModifiedVulnerabilities(revisionNumber, dbRevisionNumber, fetcher.repositoryLocalPath)
|
modifiedCVE, err := collectModifiedVulnerabilities(revisionNumber, dbRevisionNumber, u.repositoryLocalPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
@ -121,7 +116,7 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up
|
|||||||
notes := make(map[string]struct{})
|
notes := make(map[string]struct{})
|
||||||
for cvePath := range modifiedCVE {
|
for cvePath := range modifiedCVE {
|
||||||
// Open the CVE file.
|
// Open the CVE file.
|
||||||
file, err := os.Open(fetcher.repositoryLocalPath + "/" + cvePath)
|
file, err := os.Open(u.repositoryLocalPath + "/" + cvePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This can happen when a file is modified and then moved in another
|
// This can happen when a file is modified and then moved in another
|
||||||
// commit.
|
// commit.
|
||||||
@ -166,16 +161,20 @@ func (fetcher *UbuntuFetcher) FetchUpdate(datastore database.Datastore) (resp up
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fetcher *UbuntuFetcher) pullRepository() (err error) {
|
func (u *updater) Clean() {
|
||||||
|
os.RemoveAll(u.repositoryLocalPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *updater) pullRepository() (err error) {
|
||||||
// Determine whether we should branch or pull.
|
// Determine whether we should branch or pull.
|
||||||
if _, pathExists := os.Stat(fetcher.repositoryLocalPath); fetcher.repositoryLocalPath == "" || os.IsNotExist(pathExists) {
|
if _, pathExists := os.Stat(u.repositoryLocalPath); u.repositoryLocalPath == "" || os.IsNotExist(pathExists) {
|
||||||
// Create a temporary folder to store the repository.
|
// Create a temporary folder to store the repository.
|
||||||
if fetcher.repositoryLocalPath, err = ioutil.TempDir(os.TempDir(), "ubuntu-cve-tracker"); err != nil {
|
if u.repositoryLocalPath, err = ioutil.TempDir(os.TempDir(), "ubuntu-cve-tracker"); err != nil {
|
||||||
return ErrFilesystem
|
return vulnsrc.ErrFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Branch repository.
|
// Branch repository.
|
||||||
if out, err := utils.Exec(fetcher.repositoryLocalPath, "bzr", "branch", "--use-existing-dir", trackerRepository, "."); err != nil {
|
if out, err := utils.Exec(u.repositoryLocalPath, "bzr", "branch", "--use-existing-dir", trackerRepository, "."); err != nil {
|
||||||
log.Errorf("could not branch Ubuntu repository: %s. output: %s", err, out)
|
log.Errorf("could not branch Ubuntu repository: %s. output: %s", err, out)
|
||||||
return cerrors.ErrCouldNotDownload
|
return cerrors.ErrCouldNotDownload
|
||||||
}
|
}
|
||||||
@ -184,8 +183,8 @@ func (fetcher *UbuntuFetcher) pullRepository() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pull repository.
|
// Pull repository.
|
||||||
if out, err := utils.Exec(fetcher.repositoryLocalPath, "bzr", "pull", "--overwrite"); err != nil {
|
if out, err := utils.Exec(u.repositoryLocalPath, "bzr", "pull", "--overwrite"); err != nil {
|
||||||
os.RemoveAll(fetcher.repositoryLocalPath)
|
os.RemoveAll(u.repositoryLocalPath)
|
||||||
|
|
||||||
log.Errorf("could not pull Ubuntu repository: %s. output: %s", err, out)
|
log.Errorf("could not pull Ubuntu repository: %s. output: %s", err, out)
|
||||||
return cerrors.ErrCouldNotDownload
|
return cerrors.ErrCouldNotDownload
|
||||||
@ -217,14 +216,14 @@ func collectModifiedVulnerabilities(revision int, dbRevision, repositoryLocalPat
|
|||||||
d, err := os.Open(repositoryLocalPath + "/" + folder)
|
d, err := os.Open(repositoryLocalPath + "/" + folder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not open Ubuntu vulnerabilities repository's folder: %s", err)
|
log.Errorf("could not open Ubuntu vulnerabilities repository's folder: %s", err)
|
||||||
return nil, ErrFilesystem
|
return nil, vulnsrc.ErrFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the FileInfo of all the files in the directory.
|
// Get the FileInfo of all the files in the directory.
|
||||||
names, err := d.Readdirnames(-1)
|
names, err := d.Readdirnames(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not read Ubuntu vulnerabilities repository's folder:: %s.", err)
|
log.Errorf("could not read Ubuntu vulnerabilities repository's folder:: %s.", err)
|
||||||
return nil, ErrFilesystem
|
return nil, vulnsrc.ErrFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the vulnerabilities to the list.
|
// Add the vulnerabilities to the list.
|
||||||
@ -414,8 +413,3 @@ func ubuntuPriorityToSeverity(priority string) types.Priority {
|
|||||||
log.Warning("Could not determine a vulnerability priority from: %s", priority)
|
log.Warning("Could not determine a vulnerability priority from: %s", priority)
|
||||||
return types.Unknown
|
return types.Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
func (fetcher *UbuntuFetcher) Clean() {
|
|
||||||
os.RemoveAll(fetcher.repositoryLocalPath)
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,8 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package notifier fetches notifications from the database and informs the specified remote handler
|
// Package notifier fetches notifications from the database and informs the
|
||||||
// about their existences, inviting the third party to actively query the API about it.
|
// specified remote handler about their existences, inviting the third party to
|
||||||
|
// actively query the API about it.
|
||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -26,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/coreos/clair/config"
|
"github.com/coreos/clair/config"
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
|
"github.com/coreos/clair/ext/notification"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
cerrors "github.com/coreos/clair/utils/errors"
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
)
|
)
|
||||||
@ -40,8 +42,6 @@ const (
|
|||||||
var (
|
var (
|
||||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "notifier")
|
log = capnslog.NewPackageLogger("github.com/coreos/clair", "notifier")
|
||||||
|
|
||||||
notifiers = make(map[string]Notifier)
|
|
||||||
|
|
||||||
promNotifierLatencyMilliseconds = prometheus.NewHistogram(prometheus.HistogramOpts{
|
promNotifierLatencyMilliseconds = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
Name: "clair_notifier_latency_milliseconds",
|
Name: "clair_notifier_latency_milliseconds",
|
||||||
Help: "Time it takes to send a notification after it's been created.",
|
Help: "Time it takes to send a notification after it's been created.",
|
||||||
@ -53,57 +53,29 @@ var (
|
|||||||
}, []string{"backend"})
|
}, []string{"backend"})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Notifier represents anything that can transmit notifications.
|
|
||||||
type Notifier interface {
|
|
||||||
// Configure attempts to initialize the notifier with the provided configuration.
|
|
||||||
// It returns whether the notifier is enabled or not.
|
|
||||||
Configure(*config.NotifierConfig) (bool, error)
|
|
||||||
// Send informs the existence of the specified notification.
|
|
||||||
Send(notification database.VulnerabilityNotification) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(promNotifierLatencyMilliseconds)
|
prometheus.MustRegister(promNotifierLatencyMilliseconds)
|
||||||
prometheus.MustRegister(promNotifierBackendErrorsTotal)
|
prometheus.MustRegister(promNotifierBackendErrorsTotal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNotifier makes a Fetcher available by the provided name.
|
|
||||||
// If Register is called twice with the same name or if driver is nil,
|
|
||||||
// it panics.
|
|
||||||
func RegisterNotifier(name string, n Notifier) {
|
|
||||||
if name == "" {
|
|
||||||
panic("notifier: could not register a Notifier with an empty name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if n == nil {
|
|
||||||
panic("notifier: could not register a nil Notifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, dup := notifiers[name]; dup {
|
|
||||||
panic("notifier: RegisterNotifier called twice for " + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
notifiers[name] = n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts the Notifier service.
|
// Run starts the Notifier service.
|
||||||
func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *utils.Stopper) {
|
func Run(config *config.NotifierConfig, datastore database.Datastore, stopper *utils.Stopper) {
|
||||||
defer stopper.End()
|
defer stopper.End()
|
||||||
|
|
||||||
// Configure registered notifiers.
|
// Configure registered notifiers.
|
||||||
for notifierName, notifier := range notifiers {
|
for senderName, sender := range notification.Senders {
|
||||||
if configured, err := notifier.Configure(config); configured {
|
if configured, err := sender.Configure(config); configured {
|
||||||
log.Infof("notifier '%s' configured\n", notifierName)
|
log.Infof("sender '%s' configured\n", senderName)
|
||||||
} else {
|
} else {
|
||||||
delete(notifiers, notifierName)
|
delete(notification.Senders, senderName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not configure notifier '%s': %s", notifierName, err)
|
log.Errorf("could not configure notifier '%s': %s", senderName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not run the updater if there is no notifier enabled.
|
// Do not run the updater if there is no notifier enabled.
|
||||||
if len(notifiers) == 0 {
|
if len(notification.Senders) == 0 {
|
||||||
log.Infof("notifier service is disabled")
|
log.Infof("notifier service is disabled")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -175,31 +147,31 @@ func findTask(datastore database.Datastore, renotifyInterval time.Duration, whoA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTask(notification database.VulnerabilityNotification, st *utils.Stopper, maxAttempts int) (bool, bool) {
|
func handleTask(n database.VulnerabilityNotification, st *utils.Stopper, maxAttempts int) (bool, bool) {
|
||||||
// Send notification.
|
// Send notification.
|
||||||
for notifierName, notifier := range notifiers {
|
for senderName, sender := range notification.Senders {
|
||||||
var attempts int
|
var attempts int
|
||||||
var backOff time.Duration
|
var backOff time.Duration
|
||||||
for {
|
for {
|
||||||
// Max attempts exceeded.
|
// Max attempts exceeded.
|
||||||
if attempts >= maxAttempts {
|
if attempts >= maxAttempts {
|
||||||
log.Infof("giving up on sending notification '%s' via notifier '%s': max attempts exceeded (%d)\n", notification.Name, notifierName, maxAttempts)
|
log.Infof("giving up on sending notification '%s' via sender '%s': max attempts exceeded (%d)\n", n.Name, senderName, maxAttempts)
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backoff.
|
// Backoff.
|
||||||
if backOff > 0 {
|
if backOff > 0 {
|
||||||
log.Infof("waiting %v before retrying to send notification '%s' via notifier '%s' (Attempt %d / %d)\n", backOff, notification.Name, notifierName, attempts+1, maxAttempts)
|
log.Infof("waiting %v before retrying to send notification '%s' via sender '%s' (Attempt %d / %d)\n", backOff, n.Name, senderName, attempts+1, maxAttempts)
|
||||||
if !st.Sleep(backOff) {
|
if !st.Sleep(backOff) {
|
||||||
return false, true
|
return false, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send using the current notifier.
|
// Send using the current notifier.
|
||||||
if err := notifier.Send(notification); err != nil {
|
if err := sender.Send(n); err != nil {
|
||||||
// Send failed; increase attempts/backoff and retry.
|
// Send failed; increase attempts/backoff and retry.
|
||||||
promNotifierBackendErrorsTotal.WithLabelValues(notifierName).Inc()
|
promNotifierBackendErrorsTotal.WithLabelValues(senderName).Inc()
|
||||||
log.Errorf("could not send notification '%s' via notifier '%s': %v", notification.Name, notifierName, err)
|
log.Errorf("could not send notification '%s' via notifier '%s': %v", n.Name, senderName, err)
|
||||||
backOff = timeutil.ExpBackoff(backOff, maxBackOff)
|
backOff = timeutil.ExpBackoff(backOff, maxBackOff)
|
||||||
attempts++
|
attempts++
|
||||||
continue
|
continue
|
||||||
@ -210,6 +182,6 @@ func handleTask(notification database.VulnerabilityNotification, st *utils.Stopp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("successfully sent notification '%s'\n", notification.Name)
|
log.Infof("successfully sent notification '%s'\n", n.Name)
|
||||||
return true, false
|
return true, false
|
||||||
}
|
}
|
||||||
|
@ -1,56 +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 updater
|
|
||||||
|
|
||||||
import "github.com/coreos/clair/database"
|
|
||||||
|
|
||||||
var fetchers = make(map[string]Fetcher)
|
|
||||||
|
|
||||||
// Fetcher represents anything that can fetch vulnerabilities.
|
|
||||||
type Fetcher interface {
|
|
||||||
// FetchUpdate gets vulnerability updates.
|
|
||||||
FetchUpdate(database.Datastore) (FetcherResponse, error)
|
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
// It is invoked when Clair stops.
|
|
||||||
Clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetcherResponse represents the sum of results of an update.
|
|
||||||
type FetcherResponse struct {
|
|
||||||
FlagName string
|
|
||||||
FlagValue string
|
|
||||||
Notes []string
|
|
||||||
Vulnerabilities []database.Vulnerability
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterFetcher makes a Fetcher available by the provided name.
|
|
||||||
// If Register is called twice with the same name or if driver is nil,
|
|
||||||
// it panics.
|
|
||||||
func RegisterFetcher(name string, f Fetcher) {
|
|
||||||
if name == "" {
|
|
||||||
panic("updater: could not register a Fetcher with an empty name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f == nil {
|
|
||||||
panic("updater: could not register a nil Fetcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, dup := fetchers[name]; dup {
|
|
||||||
panic("updater: RegisterFetcher called twice for " + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchers[name] = f
|
|
||||||
}
|
|
@ -1,64 +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 updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
var metadataFetchers = make(map[string]MetadataFetcher)
|
|
||||||
|
|
||||||
type VulnerabilityWithLock struct {
|
|
||||||
*database.Vulnerability
|
|
||||||
Lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetadataFetcher
|
|
||||||
type MetadataFetcher interface {
|
|
||||||
// Load runs right before the Updater calls AddMetadata for each vulnerabilities.
|
|
||||||
Load(database.Datastore) error
|
|
||||||
|
|
||||||
// AddMetadata adds metadata to the given database.Vulnerability.
|
|
||||||
// It is expected that the fetcher uses .Lock.Lock() when manipulating the Metadata map.
|
|
||||||
AddMetadata(*VulnerabilityWithLock) error
|
|
||||||
|
|
||||||
// Unload runs right after the Updater finished calling AddMetadata for every vulnerabilities.
|
|
||||||
Unload()
|
|
||||||
|
|
||||||
// Clean deletes any allocated resources.
|
|
||||||
// It is invoked when Clair stops.
|
|
||||||
Clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterFetcher makes a Fetcher available by the provided name.
|
|
||||||
// If Register is called twice with the same name or if driver is nil,
|
|
||||||
// it panics.
|
|
||||||
func RegisterMetadataFetcher(name string, f MetadataFetcher) {
|
|
||||||
if name == "" {
|
|
||||||
panic("updater: could not register a MetadataFetcher with an empty name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f == nil {
|
|
||||||
panic("updater: could not register a nil MetadataFetcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, dup := fetchers[name]; dup {
|
|
||||||
panic("updater: RegisterMetadataFetcher called twice for " + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
metadataFetchers[name] = f
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package nvd
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// NestedReadCloser wraps an io.Reader and implements io.ReadCloser by closing every embed
|
|
||||||
// io.ReadCloser.
|
|
||||||
// It allows chaining io.ReadCloser together and still keep the ability to close them all in a
|
|
||||||
// simple manner.
|
|
||||||
type NestedReadCloser struct {
|
|
||||||
io.Reader
|
|
||||||
NestedReadClosers []io.ReadCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the gzip.Reader and the underlying io.ReadCloser.
|
|
||||||
func (nrc *NestedReadCloser) Close() {
|
|
||||||
for _, nestedReadCloser := range nrc.NestedReadClosers {
|
|
||||||
nestedReadCloser.Close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 clair authors
|
// Copyright 2017 clair authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,8 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package updater updates the vulnerability database periodically using
|
// Package updater updates the vulnerability database periodically using the
|
||||||
// the registered vulnerability fetchers.
|
// registered vulnerability source updaters and vulnerability metadata
|
||||||
|
// appenders.
|
||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -22,12 +23,16 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/clair/config"
|
|
||||||
"github.com/coreos/clair/database"
|
|
||||||
"github.com/coreos/clair/utils"
|
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"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 (
|
const (
|
||||||
@ -147,11 +152,11 @@ func Run(config *config.UpdaterConfig, datastore database.Datastore, st *utils.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean resources.
|
// Clean resources.
|
||||||
for _, metadataFetcher := range metadataFetchers {
|
for _, appenders := range vulnmdsrc.Appenders {
|
||||||
metadataFetcher.Clean()
|
appenders.Clean()
|
||||||
}
|
}
|
||||||
for _, fetcher := range fetchers {
|
for _, updaters := range vulnsrc.Updaters {
|
||||||
fetcher.Clean()
|
updaters.Clean()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("updater service stopped")
|
log.Info("updater service stopped")
|
||||||
@ -209,10 +214,10 @@ func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[st
|
|||||||
|
|
||||||
// Fetch updates in parallel.
|
// Fetch updates in parallel.
|
||||||
log.Info("fetching vulnerability updates")
|
log.Info("fetching vulnerability updates")
|
||||||
var responseC = make(chan *FetcherResponse, 0)
|
var responseC = make(chan *vulnsrc.UpdateResponse, 0)
|
||||||
for n, f := range fetchers {
|
for n, u := range vulnsrc.Updaters {
|
||||||
go func(name string, fetcher Fetcher) {
|
go func(name string, u vulnsrc.Updater) {
|
||||||
response, err := fetcher.FetchUpdate(datastore)
|
response, err := u.Update(datastore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
promUpdaterErrorsTotal.Inc()
|
promUpdaterErrorsTotal.Inc()
|
||||||
log.Errorf("an error occured when fetching update '%s': %s.", name, err)
|
log.Errorf("an error occured when fetching update '%s': %s.", name, err)
|
||||||
@ -222,11 +227,11 @@ func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[st
|
|||||||
}
|
}
|
||||||
|
|
||||||
responseC <- &response
|
responseC <- &response
|
||||||
}(n, f)
|
}(n, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect results of updates.
|
// Collect results of updates.
|
||||||
for i := 0; i < len(fetchers); i++ {
|
for i := 0; i < len(vulnsrc.Updaters); i++ {
|
||||||
resp := <-responseC
|
resp := <-responseC
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
vulnerabilities = append(vulnerabilities, doVulnerabilitiesNamespacing(resp.Vulnerabilities)...)
|
vulnerabilities = append(vulnerabilities, doVulnerabilitiesNamespacing(resp.Vulnerabilities)...)
|
||||||
@ -243,42 +248,43 @@ func fetch(datastore database.Datastore) (bool, []database.Vulnerability, map[st
|
|||||||
|
|
||||||
// Add metadata to the specified vulnerabilities using the registered MetadataFetchers, in parallel.
|
// Add metadata to the specified vulnerabilities using the registered MetadataFetchers, in parallel.
|
||||||
func addMetadata(datastore database.Datastore, vulnerabilities []database.Vulnerability) []database.Vulnerability {
|
func addMetadata(datastore database.Datastore, vulnerabilities []database.Vulnerability) []database.Vulnerability {
|
||||||
if len(metadataFetchers) == 0 {
|
if len(vulnmdsrc.Appenders) == 0 {
|
||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("adding metadata to vulnerabilities")
|
log.Info("adding metadata to vulnerabilities")
|
||||||
|
|
||||||
// Wrap vulnerabilities in VulnerabilityWithLock.
|
// Add a mutex to each vulnerability to ensure that only one appender at a
|
||||||
// It ensures that only one metadata fetcher at a time can modify the Metadata map.
|
// time can modify the vulnerability's Metadata map.
|
||||||
vulnerabilitiesWithLocks := make([]*VulnerabilityWithLock, 0, len(vulnerabilities))
|
lockableVulnerabilities := make([]*lockableVulnerability, 0, len(vulnerabilities))
|
||||||
for i := 0; i < len(vulnerabilities); i++ {
|
for i := 0; i < len(vulnerabilities); i++ {
|
||||||
vulnerabilitiesWithLocks = append(vulnerabilitiesWithLocks, &VulnerabilityWithLock{
|
lockableVulnerabilities = append(lockableVulnerabilities, &lockableVulnerability{
|
||||||
Vulnerability: &vulnerabilities[i],
|
Vulnerability: &vulnerabilities[i],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(metadataFetchers))
|
wg.Add(len(vulnmdsrc.Appenders))
|
||||||
|
|
||||||
for n, f := range metadataFetchers {
|
for n, a := range vulnmdsrc.Appenders {
|
||||||
go func(name string, metadataFetcher MetadataFetcher) {
|
go func(name string, appender vulnmdsrc.Appender) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
// Load the metadata fetcher.
|
// Build up a metadata cache.
|
||||||
if err := metadataFetcher.Load(datastore); err != nil {
|
if err := appender.BuildCache(datastore); err != nil {
|
||||||
promUpdaterErrorsTotal.Inc()
|
promUpdaterErrorsTotal.Inc()
|
||||||
log.Errorf("an error occured when loading metadata fetcher '%s': %s.", name, err)
|
log.Errorf("an error occured when loading metadata fetcher '%s': %s.", name, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add metadata to each vulnerability.
|
// Append vulnerability metadata to each vulnerability.
|
||||||
for _, vulnerability := range vulnerabilitiesWithLocks {
|
for _, vulnerability := range lockableVulnerabilities {
|
||||||
metadataFetcher.AddMetadata(vulnerability)
|
appender.Append(vulnerability.Name, vulnerability.appendFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataFetcher.Unload()
|
// Purge the metadata cache.
|
||||||
}(n, f)
|
appender.PurgeCache()
|
||||||
|
}(n, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@ -305,6 +311,29 @@ func getLastUpdate(datastore database.Datastore) (time.Time, bool, error) {
|
|||||||
return time.Unix(lastUpdateTS, 0).UTC(), false, nil
|
return time.Unix(lastUpdateTS, 0).UTC(), false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type lockableVulnerability struct {
|
||||||
|
*database.Vulnerability
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv *lockableVulnerability) appendFunc(metadataKey string, metadata interface{}, severity types.Priority) {
|
||||||
|
lv.Lock()
|
||||||
|
defer lv.Unlock()
|
||||||
|
|
||||||
|
// If necessary, initialize the metadata map for the vulnerability.
|
||||||
|
if lv.Metadata == nil {
|
||||||
|
lv.Metadata = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the metadata.
|
||||||
|
lv.Metadata[metadataKey] = metadata
|
||||||
|
|
||||||
|
// If necessary, provide a severity for the vulnerability.
|
||||||
|
if lv.Severity == "" || lv.Severity == types.Unknown {
|
||||||
|
lv.Severity = severity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// doVulnerabilitiesNamespacing takes Vulnerabilities that don't have a Namespace and split them
|
// doVulnerabilitiesNamespacing takes Vulnerabilities that don't have a Namespace and split them
|
||||||
// into multiple vulnerabilities that have a Namespace and only contains the FixedIn
|
// into multiple vulnerabilities that have a Namespace and only contains the FixedIn
|
||||||
// FeatureVersions corresponding to their Namespace.
|
// FeatureVersions corresponding to their Namespace.
|
||||||
|
Loading…
Reference in New Issue
Block a user