176 lines
4.2 KiB
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
|
|
}
|