vuln-list-update/mariner/mariner.go

176 lines
4.2 KiB
Go

package mariner
import (
"context"
"encoding/xml"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/cheggaaa/pb"
"golang.org/x/xerrors"
"github.com/aquasecurity/vuln-list-update/utils"
)
const (
repoURL = "https://github.com/microsoft/CBL-MarinerVulnerabilityData/archive/refs/heads/main.tar.gz//CBL-MarinerVulnerabilityData-main"
cblDir = "mariner" // CBL-Mariner Vulnerability Data
retry = 3
testsDir = "tests"
objectsDir = "objects"
statesDir = "states"
definitionsDir = "definitions"
)
var (
ErrInvalidCVEFormat = errors.New("invalid CVE-ID format")
ErrNonCVEID = errors.New("discovered non-CVE-ID")
)
type Config struct {
*options
}
type option func(*options)
type options struct {
url string
dir string
retry int
}
func WithURL(url string) option {
return func(opts *options) { opts.url = url }
}
func WithDir(dir string) option {
return func(opts *options) { opts.dir = dir }
}
func WithRetry(retry int) option {
return func(opts *options) { opts.retry = retry }
}
func NewConfig(opts ...option) Config {
o := &options{
url: repoURL,
dir: filepath.Join(utils.VulnListDir(), cblDir),
retry: retry,
}
for _, opt := range opts {
opt(o)
}
return Config{
options: o,
}
}
func (c Config) Update() error {
ctx := context.Background()
log.Printf("Remove CBL-Mariner Vulnerability Data directory %sn", c.dir)
if err := os.RemoveAll(c.dir); err != nil {
return xerrors.Errorf("failed to remove CBL-Mariner Vulnerability Data directory: %w", err)
}
log.Print("Fetching CBL-Mariner Vulnerability Data")
tmpDir, err := utils.DownloadToTempDir(ctx, c.url)
if err != nil {
return xerrors.Errorf("failed to retrieve CBL-Mariner Vulnerability Data: %w", err)
}
defer os.RemoveAll(tmpDir)
entries, err := os.ReadDir(tmpDir)
if err != nil {
return xerrors.Errorf("failed to read directory: %w", err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if !strings.HasPrefix(entry.Name(), "cbl-mariner-") {
continue
}
if filepath.Ext(entry.Name()) != ".xml" {
continue
}
osVersoin := strings.TrimSuffix(strings.TrimSuffix(strings.TrimPrefix(entry.Name(), "cbl-mariner-"), "-oval.xml"), "-preview")
if err := c.update(osVersoin, filepath.Join(tmpDir, entry.Name())); err != nil {
return xerrors.Errorf("failed to update oval data: %w", err)
}
}
return nil
}
func (c Config) update(version, path string) error {
f, err := os.Open(path)
if err != nil {
return xerrors.Errorf("failed to open file: %w", err)
}
var oval OvalDefinitions
if err := xml.NewDecoder(f).Decode(&oval); err != nil {
return xerrors.Errorf("failed to decode xml: %w", err)
}
dirPath := filepath.Join(c.dir, version)
// write tests/tests.json file
if err := utils.Write(filepath.Join(dirPath, testsDir, "tests.json"), oval.Tests); err != nil {
return xerrors.Errorf("failed to write tests: %w", err)
}
// write objects/objects.json file
if err := utils.Write(filepath.Join(dirPath, objectsDir, "objects.json"), oval.Objects); err != nil {
return xerrors.Errorf("failed to write objects: %w", err)
}
// write states/states.json file
if err := utils.Write(filepath.Join(dirPath, statesDir, "states.json"), oval.States); err != nil {
return xerrors.Errorf("failed to write states: %w", err)
}
// write definitions
bar := pb.StartNew(len(oval.Definitions.Definition))
for _, def := range oval.Definitions.Definition {
vulnID := def.Metadata.Reference.RefID
if err := c.saveAdvisoryPerYear(filepath.Join(dirPath, definitionsDir), vulnID, def); err != nil {
return xerrors.Errorf("failed to save advisory per year: %w", err)
}
bar.Increment()
}
bar.Finish()
return nil
}
func (c Config) saveAdvisoryPerYear(dirName string, vulnID string, def Definition) error {
if !strings.HasPrefix(vulnID, "CVE") {
log.Printf("discovered non-CVE-ID: %s", vulnID)
return ErrNonCVEID
}
s := strings.Split(vulnID, "-")
if len(s) != 3 {
log.Printf("invalid CVE-ID format: %s", vulnID)
return ErrInvalidCVEFormat
}
yearDir := filepath.Join(dirName, s[1])
if err := utils.Write(filepath.Join(yearDir, fmt.Sprintf("%s.json", vulnID)), def); err != nil {
return xerrors.Errorf("unable to write a JSON file: %w", err)
}
return nil
}