providers/lib/runner/main.go (121 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// 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 runner
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"sync"
"time"
"github.com/facebookincubator/flog"
nvd "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema"
"github.com/facebookincubator/nvdtools/providers/lib/client"
"github.com/facebookincubator/nvdtools/stats"
)
// Convertible is any struct which knows how to convert itself to NVD CVE Item
type Convertible interface {
// ID should return vulnerabilities ID
ID() string
// Convert should return a new CVE Item, or an error if it's not possible
Convert() (*nvd.NVDCVEFeedJSON10DefCVEItem, error)
}
// Read should read the vulnerabilities from the given reader and push them into the channel
// The contents of the reader should be a slice of structs which are convertibles
// channel will be created and mustn't be closed
type Read func(io.Reader, chan Convertible) error
// FetchSince knows how to fetch vulnerabilities from an API
// it should create a new channel, fetch everything concurrently and close the channel
type FetchSince func(ctx context.Context, c client.Client, baseURL string, since int64) (<-chan Convertible, error)
// Runner knows how to run everything together, based on the config values
// if config.Download is set, it will use the fetcher, otherwise it will use Reader to read stdin or files
type Runner struct {
Config
FetchSince
Read
}
// Run should be called in main function of the converter
// It will run the fetchers/runners (and convert vulnerabilities)
// Finally, it will output it as json to stdout
func (r *Runner) Run() error {
r.Config.addFlags()
stats.AddFlags()
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [flags]\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}
flag.Parse()
defer func(startTime time.Time) {
stats.TrackTime("run.time", startTime, time.Second)
stats.WriteAndLogError()
}(time.Now())
if err := r.Config.validate(); err != nil {
return fmt.Errorf("config is invalid: %v", err)
}
var vulns <-chan Convertible
var err error
if r.Config.download {
vulns, err = r.downloadVulnerabilities(context.Background())
} else {
vulns, err = r.readVulnerabilities()
}
if err != nil {
return fmt.Errorf("couldn't get vulnerabilities: %v", err)
}
if r.Config.convert {
if err := convert(vulns); err != nil {
return fmt.Errorf("failed to convert vulns: %v", err)
}
return nil
}
m := make(map[string]Convertible)
for v := range vulns {
m[v.ID()] = v
}
if err := json.NewEncoder(os.Stdout).Encode(m); err != nil {
return fmt.Errorf("couldn't write vulnerabilities: %v", err)
}
return nil
}
func (r *Runner) downloadVulnerabilities(ctx context.Context) (<-chan Convertible, error) {
c := client.Default()
c = r.Config.ClientConfig.Configure(c)
return r.FetchSince(ctx, c, r.Config.BaseURL, int64(r.Config.downloadSince))
}
func (r *Runner) readVulnerabilities() (<-chan Convertible, error) {
vulns := make(chan Convertible)
if flag.NArg() == 0 {
// read from stdin
go func() {
defer close(vulns)
if err := r.Read(os.Stdin, vulns); err != nil {
flog.Errorf("error while reading from stdin: %v", err)
}
}()
return vulns, nil
}
// read from files in args
wg := sync.WaitGroup{}
for _, filename := range flag.Args() {
wg.Add(1)
go func(filename string) {
defer wg.Done()
file, err := os.Open(filename)
if err != nil {
flog.Errorf("couldn't open file %q: %v", filename, err)
return
}
defer file.Close()
if err := r.Read(file, vulns); err != nil {
flog.Errorf("error while reading from file %q: %v", filename, err)
}
}(filename)
}
go func() {
defer close(vulns)
wg.Wait()
}()
return vulns, nil
}
// getNVDFeed will convert the vulns in channel to NVD Feed
func convert(vulns <-chan Convertible) error {
defer stats.TrackTime("convert.time", time.Now(), time.Second)
var feed nvd.NVDCVEFeedJSON10
for vuln := range vulns {
converted, err := vuln.Convert()
if err != nil {
flog.Errorf("error while converting vuln: %v", err)
continue
}
feed.CVEItems = append(feed.CVEItems, converted)
}
if err := json.NewEncoder(os.Stdout).Encode(feed); err != nil {
return fmt.Errorf("couldn't write NVD feed: %v", err)
}
return nil
}