commit
eb5be92305
@ -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 context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/coreos/clair/config"
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "api")
|
||||
|
||||
promResponseDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "clair_api_response_duration_milliseconds",
|
||||
Help: "The duration of time it takes to receieve and write a response to an API request",
|
||||
Buckets: prometheus.ExponentialBuckets(9.375, 2, 10),
|
||||
}, []string{"route", "code"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(promResponseDurationMilliseconds)
|
||||
}
|
||||
|
||||
type Handler func(http.ResponseWriter, *http.Request, httprouter.Params, *RouteContext) (route string, status int)
|
||||
|
||||
func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
start := time.Now()
|
||||
route, status := handler(w, r, p, ctx)
|
||||
statusStr := strconv.Itoa(status)
|
||||
if status == 0 {
|
||||
statusStr = "???"
|
||||
}
|
||||
utils.PrometheusObserveTimeMilliseconds(promResponseDurationMilliseconds.WithLabelValues(route, statusStr), start)
|
||||
|
||||
log.Infof("%s \"%s %s\" %s (%s)", r.RemoteAddr, r.Method, r.RequestURI, statusStr, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
type RouteContext struct {
|
||||
Store database.Datastore
|
||||
Config *config.APIConfig
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
// 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 database
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrFailedToParseSeverity is the error returned when a severity could not
|
||||
// be parsed from a string.
|
||||
var 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 (
|
||||
// UnknownSeverity is either a security problem that has not been assigned to
|
||||
// a priority yet or a priority that our system did not recognize.
|
||||
UnknownSeverity Severity = "Unknown"
|
||||
|
||||
// NegligibleSeverity 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.
|
||||
NegligibleSeverity Severity = "Negligible"
|
||||
|
||||
// LowSeverity 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.
|
||||
LowSeverity Severity = "Low"
|
||||
|
||||
// MediumSeverity 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.
|
||||
MediumSeverity Severity = "Medium"
|
||||
|
||||
// HighSeverity 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.
|
||||
HighSeverity Severity = "High"
|
||||
|
||||
// CriticalSeverity 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.
|
||||
CriticalSeverity Severity = "Critical"
|
||||
|
||||
// Defcon1Severity is a Critical problem which has been manually highlighted
|
||||
// by the team. It requires an immediate attention.
|
||||
Defcon1Severity Severity = "Defcon1"
|
||||
)
|
||||
|
||||
// Severities lists all known severities, ordered from lowest to highest.
|
||||
var Severities = []Severity{
|
||||
UnknownSeverity,
|
||||
NegligibleSeverity,
|
||||
LowSeverity,
|
||||
MediumSeverity,
|
||||
HighSeverity,
|
||||
CriticalSeverity,
|
||||
Defcon1Severity,
|
||||
}
|
||||
|
||||
// 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 UnknownSeverity, ErrFailedToParseSeverity
|
||||
}
|
||||
|
||||
// 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(Severities); i1 = i1 + 1 {
|
||||
if s == Severities[i1] {
|
||||
break
|
||||
}
|
||||
}
|
||||
for i2 = 0; i2 < len(Severities); i2 = i2 + 1 {
|
||||
if s2 == Severities[i2] {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return i1 - i2
|
||||
}
|
||||
|
||||
// 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 Severity from a non-string input")
|
||||
}
|
||||
|
||||
var err error
|
||||
*s, err = NewSeverity(string(val))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface.
|
||||
func (s Severity) Value() (driver.Value, error) {
|
||||
return string(s), nil
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// 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 database
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCompareSeverity(t *testing.T) {
|
||||
assert.Equal(t, MediumSeverity.Compare(MediumSeverity), 0, "Severity comparison failed")
|
||||
assert.True(t, MediumSeverity.Compare(HighSeverity) < 0, "Severity comparison failed")
|
||||
assert.True(t, CriticalSeverity.Compare(LowSeverity) > 0, "Severity comparison failed")
|
||||
}
|
||||
|
||||
func TestParseSeverity(t *testing.T) {
|
||||
_, err := NewSeverity("Test")
|
||||
assert.Equal(t, ErrFailedToParseSeverity, err)
|
||||
|
||||
_, err = NewSeverity("Unknown")
|
||||
assert.Nil(t, err)
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
// 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 featurefmt exposes functions to dynamically register methods for
|
||||
// determining the features present in an image layer.
|
||||
package featurefmt
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
listersM sync.RWMutex
|
||||
listers = make(map[string]Lister)
|
||||
)
|
||||
|
||||
// Lister represents an ability to list the features present in an image layer.
|
||||
type Lister interface {
|
||||
// ListFeatures produces a list of FeatureVersions present in an image layer.
|
||||
ListFeatures(tarutil.FilesMap) ([]database.FeatureVersion, error)
|
||||
|
||||
// RequiredFilenames returns the list of files required to be in the FilesMap
|
||||
// provided to the ListFeatures method.
|
||||
//
|
||||
// Filenames must not begin with "/".
|
||||
RequiredFilenames() []string
|
||||
}
|
||||
|
||||
// RegisterLister makes a Lister available by the provided name.
|
||||
//
|
||||
// If called twice with the same name, the name is blank, or if the provided
|
||||
// Lister is nil, this function panics.
|
||||
func RegisterLister(name string, l Lister) {
|
||||
if name == "" {
|
||||
panic("featurefmt: could not register a Lister with an empty name")
|
||||
}
|
||||
if l == nil {
|
||||
panic("featurefmt: could not register a nil Lister")
|
||||
}
|
||||
|
||||
listersM.Lock()
|
||||
defer listersM.Unlock()
|
||||
|
||||
if _, dup := listers[name]; dup {
|
||||
panic("featurefmt: RegisterLister called twice for " + name)
|
||||
}
|
||||
|
||||
listers[name] = l
|
||||
}
|
||||
|
||||
// ListFeatures produces the list of FeatureVersions in an image layer using
|
||||
// every registered Lister.
|
||||
func ListFeatures(files tarutil.FilesMap) ([]database.FeatureVersion, error) {
|
||||
listersM.RLock()
|
||||
defer listersM.RUnlock()
|
||||
|
||||
var totalFeatures []database.FeatureVersion
|
||||
for _, lister := range listers {
|
||||
features, err := lister.ListFeatures(files)
|
||||
if err != nil {
|
||||
return []database.FeatureVersion{}, err
|
||||
}
|
||||
totalFeatures = append(totalFeatures, features...)
|
||||
}
|
||||
|
||||
return totalFeatures, nil
|
||||
}
|
||||
|
||||
// RequiredFilenames returns the total list of files required for all
|
||||
// registered Listers.
|
||||
func RequiredFilenames() (files []string) {
|
||||
listersM.RLock()
|
||||
defer listersM.RUnlock()
|
||||
|
||||
for _, lister := range listers {
|
||||
files = append(files, lister.RequiredFilenames()...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TestData represents the data used to test an implementation of Lister.
|
||||
type TestData struct {
|
||||
Files tarutil.FilesMap
|
||||
FeatureVersions []database.FeatureVersion
|
||||
}
|
||||
|
||||
// LoadFileForTest can be used in order to obtain the []byte contents of a file
|
||||
// that is meant to be used for test data.
|
||||
func LoadFileForTest(name string) []byte {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
d, _ := ioutil.ReadFile(filepath.Join(filepath.Dir(filename)) + "/" + name)
|
||||
return d
|
||||
}
|
||||
|
||||
// TestLister runs a Lister on each provided instance of TestData and asserts
|
||||
// the ouput to be equal to the expected output.
|
||||
func TestLister(t *testing.T, l Lister, testData []TestData) {
|
||||
for _, td := range testData {
|
||||
featureVersions, err := l.ListFeatures(td.Files)
|
||||
if assert.Nil(t, err) && assert.Len(t, featureVersions, len(td.FeatureVersions)) {
|
||||
for _, expectedFeatureVersion := range td.FeatureVersions {
|
||||
assert.Contains(t, featureVersions, expectedFeatureVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
// 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 featurens exposes functions to dynamically register methods for
|
||||
// determining a namespace for features present in an image layer.
|
||||
package featurens
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/featurens")
|
||||
|
||||
detectorsM sync.RWMutex
|
||||
detectors = make(map[string]Detector)
|
||||
)
|
||||
|
||||
// Detector represents an ability to detect a namespace used for organizing
|
||||
// features present in an image layer.
|
||||
type Detector interface {
|
||||
// Detect attempts to determine a Namespace from a FilesMap of an image
|
||||
// layer.
|
||||
Detect(tarutil.FilesMap) (*database.Namespace, error)
|
||||
|
||||
// RequiredFilenames returns the list of files required to be in the FilesMap
|
||||
// provided to the Detect method.
|
||||
//
|
||||
// Filenames must not begin with "/".
|
||||
RequiredFilenames() []string
|
||||
}
|
||||
|
||||
// RegisterDetector makes a detector available by the provided name.
|
||||
//
|
||||
// If called twice with the same name, the name is blank, or if the provided
|
||||
// Detector is nil, this function panics.
|
||||
func RegisterDetector(name string, d Detector) {
|
||||
if name == "" {
|
||||
panic("namespace: could not register a Detector with an empty name")
|
||||
}
|
||||
if d == nil {
|
||||
panic("namespace: could not register a nil Detector")
|
||||
}
|
||||
|
||||
detectorsM.Lock()
|
||||
defer detectorsM.Unlock()
|
||||
|
||||
if _, dup := detectors[name]; dup {
|
||||
panic("namespace: RegisterDetector called twice for " + name)
|
||||
}
|
||||
|
||||
detectors[name] = d
|
||||
}
|
||||
|
||||
// Detect iterators through all registered Detectors and returns the first
|
||||
// non-nil detected namespace.
|
||||
func Detect(files tarutil.FilesMap) (*database.Namespace, error) {
|
||||
detectorsM.RLock()
|
||||
defer detectorsM.RUnlock()
|
||||
|
||||
for name, detector := range detectors {
|
||||
namespace, err := detector.Detect(files)
|
||||
if err != nil {
|
||||
log.Warningf("failed while attempting to detect namespace %s: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if namespace != nil {
|
||||
log.Debugf("detected namespace %s: %#v", name, namespace)
|
||||
return namespace, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RequiredFilenames returns the total list of files required for all
|
||||
// registered Detectors.
|
||||
func RequiredFilenames() (files []string) {
|
||||
detectorsM.RLock()
|
||||
defer detectorsM.RUnlock()
|
||||
|
||||
for _, detector := range detectors {
|
||||
files = append(files, detector.RequiredFilenames()...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TestData represents the data used to test an implementation of Detector.
|
||||
type TestData struct {
|
||||
Files tarutil.FilesMap
|
||||
ExpectedNamespace *database.Namespace
|
||||
}
|
||||
|
||||
// TestDetector runs a Detector on each provided instance of TestData and
|
||||
// asserts the output to be equal to the expected output.
|
||||
func TestDetector(t *testing.T, d Detector, testData []TestData) {
|
||||
for _, td := range testData {
|
||||
namespace, err := d.Detect(td.Files)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if namespace == nil {
|
||||
assert.Equal(t, td.ExpectedNamespace, namespace)
|
||||
} else {
|
||||
assert.Equal(t, td.ExpectedNamespace.Name, namespace.Name)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// 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 aci implements an imagefmt.Extractor for appc formatted container
|
||||
// image layers.
|
||||
package aci
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/coreos/clair/ext/imagefmt"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
)
|
||||
|
||||
type format struct{}
|
||||
|
||||
func init() {
|
||||
imagefmt.RegisterExtractor("aci", &format{})
|
||||
}
|
||||
|
||||
func (f format) ExtractFiles(layerReader io.ReadCloser, toExtract []string) (tarutil.FilesMap, error) {
|
||||
// All contents are inside a "rootfs" directory, so this needs to be
|
||||
// prepended to each filename.
|
||||
var filenames []string
|
||||
for _, filename := range toExtract {
|
||||
filenames = append(filenames, filepath.Join("rootfs/", filename))
|
||||
}
|
||||
|
||||
return tarutil.ExtractFiles(layerReader, filenames)
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
// 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 imagefmt exposes functions to dynamically register methods to
|
||||
// detect different types of container image formats.
|
||||
package imagefmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/clair/pkg/commonerr"
|
||||
"github.com/coreos/clair/pkg/tarutil"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCouldNotFindLayer is returned when we could not download or open the layer file.
|
||||
ErrCouldNotFindLayer = commonerr.NewBadRequestError("could not find layer")
|
||||
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/imagefmt")
|
||||
|
||||
extractorsM sync.RWMutex
|
||||
extractors = make(map[string]Extractor)
|
||||
)
|
||||
|
||||
// Extractor represents an ability to extract files from a particular container
|
||||
// image format.
|
||||
type Extractor interface {
|
||||
// ExtractFiles produces a tarutil.FilesMap from a image layer.
|
||||
ExtractFiles(layer io.ReadCloser, filenames []string) (tarutil.FilesMap, error)
|
||||
}
|
||||
|
||||
// RegisterExtractor makes an extractor available by the provided name.
|
||||
//
|
||||
// If called twice with the same name, the name is blank, or if the provided
|
||||
// Extractor is nil, this function panics.
|
||||
func RegisterExtractor(name string, d Extractor) {
|
||||
extractorsM.Lock()
|
||||
defer extractorsM.Unlock()
|
||||
|
||||
if name == "" {
|
||||
panic("imagefmt: could not register an Extractor with an empty name")
|
||||
}
|
||||
|
||||
if d == nil {
|
||||
panic("imagefmt: could not register a nil Extractor")
|
||||
}
|
||||
|
||||
// Enforce lowercase names, so that they can be reliably be found in a map.
|
||||
name = strings.ToLower(name)
|
||||
|
||||
if _, dup := extractors[name]; dup {
|
||||
panic("imagefmt: RegisterExtractor called twice for " + name)
|
||||
}
|
||||
|
||||
extractors[name] = d
|
||||
}
|
||||
|
||||
// Extractors returns the list of the registered extractors.
|
||||
func Extractors() map[string]Extractor {
|
||||
extractorsM.RLock()
|
||||
defer extractorsM.RUnlock()
|
||||
|
||||
ret := make(map[string]Extractor)
|
||||
for k, v := range extractors {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnregisterExtractor removes a Extractor with a particular name from the list.
|
||||
func UnregisterExtractor(name string) {
|
||||
extractorsM.Lock()
|
||||
defer extractorsM.Unlock()
|
||||
delete(extractors, name)
|
||||
}
|
||||
|
||||
// Extract streams an image layer from disk or over HTTP, determines the
|
||||
// image format, then extracts the files specified.
|
||||
func Extract(format, path string, headers map[string]string, toExtract []string) (tarutil.FilesMap, error) {
|
||||
var layerReader io.ReadCloser
|
||||
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
|
||||
// Create a new HTTP request object.
|
||||
request, err := http.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, ErrCouldNotFindLayer
|
||||
}
|
||||
|
||||
// Set any provided HTTP Headers.
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Send the request and handle the response.
|
||||
r, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
log.Warningf("could not download layer: %s", err)
|
||||
return nil, ErrCouldNotFindLayer
|
||||
}
|
||||
|
||||
// Fail if we don't receive a 2xx HTTP status code.
|
||||
if math.Floor(float64(r.StatusCode/100)) != 2 {
|
||||
log.Warningf("could not download layer: got status code %d, expected 2XX", r.StatusCode)
|
||||
return nil, ErrCouldNotFindLayer
|
||||
}
|
||||
|
||||
layerReader = r.Body
|
||||
} else {
|
||||
var err error
|
||||
layerReader, err = os.Open(path)
|
||||
if err != nil {
|
||||
return nil, ErrCouldNotFindLayer
|
||||
}
|
||||
}
|
||||
defer layerReader.Close()
|
||||
|
||||
if extractor, exists := Extractors()[strings.ToLower(format)]; exists {
|
||||
files, err := extractor.ExtractFiles(layerReader, toExtract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
return nil, commonerr.NewBadRequestError(fmt.Sprintf("unsupported image format '%s'", format))
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
var (
|
||||
log = capnslog.NewPackageLogger("github.com/coreos/clair", "ext/notification")
|
||||
|
||||
sendersM sync.RWMutex
|
||||
senders = make(map[string]Sender)
|
||||
)
|
||||
|
||||
// Config is the configuration for the Notifier service and its registered
|
||||
// notifiers.
|
||||
type Config struct {
|
||||
Attempts int
|
||||
RenotifyInterval time.Duration
|
||||
Params map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// 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) (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 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")
|
||||
}
|
||||
|
||||
sendersM.Lock()
|
||||
defer sendersM.Unlock()
|
||||
|
||||
if _, dup := senders[name]; dup {
|
||||
panic("notification: RegisterSender called twice for " + name)
|
||||
}
|
||||
|
||||
senders[name] = s
|
||||
}
|
||||
|
||||
// Senders returns the list of the registered Senders.
|
||||
func Senders() map[string]Sender {
|
||||
sendersM.RLock()
|
||||
defer sendersM.RUnlock()
|
||||
|
||||
ret := make(map[string]Sender)
|
||||
for k, v := range senders {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnregisterSender removes a Sender with a particular name from the list.
|
||||
func UnregisterSender(name string) {
|
||||
sendersM.Lock()
|
||||
defer sendersM.Unlock()
|
||||
|
||||
delete(senders, name)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
var (
|
||||
appendersM sync.RWMutex
|
||||
appenders = make(map[string]Appender)
|
||||
)
|
||||
|
||||
// AppendFunc is the type of a callback provided to an Appender.
|
||||
type AppendFunc func(metadataKey string, metadata interface{}, severity database.Severity)
|
||||
|
||||
// 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 called twice with the same name, the name is blank, or if the provided
|
||||
// Appender is nil, this function panics.
|
||||
func RegisterAppender(name string, a Appender) {
|
||||
if name == "" {
|
||||
panic("vulnmdsrc: could not register an Appender with an empty name")
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
panic("vulnmdsrc: could not register a nil Appender")
|
||||
}
|
||||
|
||||
appendersM.Lock()
|
||||
defer appendersM.Unlock()
|
||||
|
||||
if _, dup := appenders[name]; dup {
|
||||
panic("vulnmdsrc: RegisterAppender called twice for " + name)
|
||||
}
|
||||
|
||||
appenders[name] = a
|
||||
}
|
||||
|
||||
// Appenders returns the list of the registered Appenders.
|
||||
func Appenders() map[string]Appender {
|
||||
appendersM.RLock()
|
||||
defer appendersM.RUnlock()
|
||||
|
||||
ret := make(map[string]Appender)
|
||||
for k, v := range appenders {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
@ -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
|
||||
|
||||
import (
|
@ -0,0 +1,90 @@
|
||||
// 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"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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")
|
||||
|
||||
updatersM sync.RWMutex
|
||||
updaters = make(map[string]Updater)
|
||||
)
|
||||
|
||||
// UpdateResponse represents the sum of results of an update.
|
||||
type UpdateResponse struct {
|
||||
FlagName string
|
||||
FlagValue string
|
||||
Notes []string
|
||||
Vulnerabilities []database.Vulnerability
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// RegisterUpdater makes an Updater available by the provided name.
|
||||
//
|
||||
// If 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")
|
||||
}
|
||||
|
||||
updatersM.Lock()
|
||||
defer updatersM.Unlock()
|
||||
|
||||
if _, dup := updaters[name]; dup {
|
||||
panic("vulnsrc: RegisterUpdater called twice for " + name)
|
||||
}
|
||||
|
||||
updaters[name] = u
|
||||
}
|
||||
|
||||
// Updaters returns the list of the registered Updaters.
|
||||
func Updaters() map[string]Updater {
|
||||
updatersM.RLock()
|
||||
defer updatersM.RUnlock()
|
||||
|
||||
ret := make(map[string]Updater)
|
||||
for k, v := range updaters {
|
||||
ret[k] = v
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
// 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 tarutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testTarballs = []string{
|
||||
"utils_test.tar",
|
||||
"utils_test.tar.gz",
|
||||
"utils_test.tar.bz2",
|
||||
"utils_test.tar.xz",
|
||||
}
|
||||
|
||||
func testfilepath(filename string) string {
|
||||
_, path, _, _ := runtime.Caller(0)
|
||||
testDataDir := "/testdata"
|
||||
return filepath.Join(filepath.Dir(path), testDataDir, filename)
|
||||
}
|
||||
|
||||
func TestExtract(t *testing.T) {
|
||||
for _, filename := range testTarballs {
|
||||
f, err := os.Open(testfilepath(filename))
|
||||
assert.Nil(t, err)
|
||||
defer f.Close()
|
||||
|
||||
data, err := ExtractFiles(f, []string{"test/"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
if c, n := data["test/test.txt"]; !n {
|
||||
assert.Fail(t, "test/test.txt should have been extracted")
|
||||
} else {
|
||||
assert.NotEqual(t, 0, len(c) > 0, "test/test.txt file is empty")
|
||||
}
|
||||
if _, n := data["test.txt"]; n {
|
||||
assert.Fail(t, "test.txt should not be extracted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUncompressedData(t *testing.T) {
|
||||
for _, filename := range testTarballs {
|
||||
f, err := os.Open(testfilepath(filename))
|
||||
assert.Nil(t, err)
|
||||
defer f.Close()
|
||||
|
||||
_, err = ExtractFiles(bytes.NewReader([]byte("that string does not represent a tar or tar-gzip file")), []string{})
|
||||
assert.Error(t, err, "Extracting uncompressed data should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxExtractableFileSize(t *testing.T) {
|
||||
for _, filename := range testTarballs {
|
||||
f, err := os.Open(testfilepath(filename))
|
||||
assert.Nil(t, err)
|
||||
defer f.Close()
|
||||
MaxExtractableFileSize = 50
|
||||
_, err = ExtractFiles(f, []string{"test"})
|
||||
assert.Equal(t, ErrExtractedFileTooBig, err)
|
||||
}
|
||||
}
|
@ -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,77 +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 http provides utility functions for HTTP servers and clients.
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/utils"
|
||||
cerrors "github.com/coreos/clair/utils/errors"
|
||||
"github.com/coreos/clair/worker"
|
||||
)
|
||||
|
||||
// MaxBodySize is the maximum number of bytes that ParseHTTPBody reads from an http.Request.Body.
|
||||
const MaxBodySize int64 = 1048576
|
||||
|
||||
// WriteHTTP writes a JSON-encoded object to a http.ResponseWriter, as well as
|
||||
// a HTTP status code.
|
||||
func WriteHTTP(w http.ResponseWriter, httpStatus int, v interface{}) {
|
||||
w.WriteHeader(httpStatus)
|
||||
if v != nil {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
result, _ := json.Marshal(v)
|
||||
w.Write(result)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHTTPError writes an error, wrapped in the Message field of a JSON-encoded
|
||||
// object to a http.ResponseWriter, as well as a HTTP status code.
|
||||
// If the status code is 0, handleError tries to guess the proper HTTP status
|
||||
// code from the error type.
|
||||
func WriteHTTPError(w http.ResponseWriter, httpStatus int, err error) {
|
||||
if httpStatus == 0 {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
// Try to guess the http status code from the error type
|
||||
if _, isBadRequestError := err.(*cerrors.ErrBadRequest); isBadRequestError {
|
||||
httpStatus = http.StatusBadRequest
|
||||
} else {
|
||||
switch err {
|
||||
case cerrors.ErrNotFound:
|
||||
httpStatus = http.StatusNotFound
|
||||
case database.ErrBackendException:
|
||||
httpStatus = http.StatusServiceUnavailable
|
||||
case worker.ErrParentUnknown, worker.ErrUnsupported, utils.ErrCouldNotExtract, utils.ErrExtractedFileTooBig:
|
||||
httpStatus = http.StatusBadRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteHTTP(w, httpStatus, struct{ Message string }{Message: err.Error()})
|
||||
}
|
||||
|
||||
// ParseHTTPBody reads a JSON-encoded body from a http.Request and unmarshals it
|
||||
// into the provided object.
|
||||
func ParseHTTPBody(r *http.Request, v interface{}) (int, error) {
|
||||
defer r.Body.Close()
|
||||
err := json.NewDecoder(io.LimitReader(r.Body, MaxBodySize)).Decode(v)
|
||||
if err != nil {
|
||||
return http.StatusUnsupportedMediaType, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// PrometheusObserveTimeMilliseconds observes the elapsed time since start, in milliseconds,
|
||||
// on the specified Prometheus Histogram.
|
||||
func PrometheusObserveTimeMilliseconds(h prometheus.Histogram, start time.Time) {
|
||||
h.Observe(float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond))
|
||||
}
|
@ -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 utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var urlParametersRegexp = regexp.MustCompile(`(\?|\&)([^=]+)\=([^ &]+)`)
|
||||
|
||||
// CleanURL removes all parameters from an URL.
|
||||
func CleanURL(str string) string {
|
||||
return urlParametersRegexp.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// Contains looks for a string into an array of strings and returns whether
|
||||
// the string exists.
|
||||
func Contains(needle string, haystack []string) bool {
|
||||
for _, h := range haystack {
|
||||
if h == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CompareStringLists returns the strings that are present in X but not in Y.
|
||||
func CompareStringLists(X, Y []string) []string {
|
||||
m := make(map[string]bool)
|
||||
|
||||
for _, y := range Y {
|
||||
m[y] = true
|
||||
}
|
||||
|
||||
diff := []string{}
|
||||
for _, x := range X {
|
||||
if m[x] {
|
||||
continue
|
||||
}
|
||||
|
||||
diff = append(diff, x)
|
||||
m[x] = true
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// CompareStringListsInBoth returns the strings that are present in both X and Y.
|
||||
func CompareStringListsInBoth(X, Y []string) []string {
|
||||
m := make(map[string]struct{})
|
||||
|
||||
for _, y := range Y {
|
||||
m[y] = struct{}{}
|
||||
}
|
||||
|
||||
diff := []string{}
|
||||
for _, x := range X {
|
||||
if _, e := m[x]; e {
|
||||
diff = append(diff, x)
|
||||
delete(m, x)
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue