github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/mariner/mariner.go (about)

     1  package mariner
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	bolt "go.etcd.io/bbolt"
    11  	"golang.org/x/xerrors"
    12  
    13  	"github.com/khulnasoft-lab/tunnel-db/pkg/db"
    14  	"github.com/khulnasoft-lab/tunnel-db/pkg/types"
    15  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/mariner/oval"
    16  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability"
    17  )
    18  
    19  var (
    20  	cblDir         = filepath.Join("mariner")
    21  	platformFormat = "CBL-Mariner %s"
    22  
    23  	source = types.DataSource{
    24  		ID:   vulnerability.CBLMariner,
    25  		Name: "CBL-Mariner Vulnerability Data",
    26  		URL:  "https://github.com/microsoft/CBL-MarinerVulnerabilityData",
    27  	}
    28  
    29  	ErrNotSupported = xerrors.New("format not supported")
    30  )
    31  
    32  type resolvedTest struct {
    33  	Name     string
    34  	Version  string
    35  	Operator operator
    36  }
    37  
    38  type VulnSrc struct {
    39  	dbc db.Operation
    40  }
    41  
    42  func NewVulnSrc() VulnSrc {
    43  	return VulnSrc{
    44  		dbc: db.Config{},
    45  	}
    46  }
    47  
    48  func (vs VulnSrc) Name() types.SourceID {
    49  	return source.ID
    50  }
    51  
    52  func (vs VulnSrc) Update(dir string) error {
    53  	rootDir := filepath.Join(dir, "vuln-list", cblDir)
    54  	versions, err := os.ReadDir(rootDir)
    55  	if err != nil {
    56  		return xerrors.Errorf("unable to list directory entries (%s): %w", rootDir, err)
    57  	}
    58  
    59  	for _, ver := range versions {
    60  		versionDir := filepath.Join(rootDir, ver.Name())
    61  		entries, err := parseOVAL(filepath.Join(versionDir))
    62  		if err != nil {
    63  			return xerrors.Errorf("failed to parse CBL-Mariner OVAL: %w ", err)
    64  		}
    65  
    66  		if err = vs.save(ver.Name(), entries); err != nil {
    67  			return xerrors.Errorf("error in CBL-Mariner save: %w", err)
    68  		}
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  func parseOVAL(dir string) ([]Entry, error) {
    75  	log.Printf("    Parsing %s", dir)
    76  
    77  	// Parse and resolve tests
    78  	tests, err := resolveTests(dir)
    79  	if err != nil {
    80  		return nil, xerrors.Errorf("failed to resolve tests: %w", err)
    81  	}
    82  
    83  	defs, err := oval.ParseDefinitions(dir)
    84  	if err != nil {
    85  		return nil, xerrors.Errorf("failed to parse definitions: %w", err)
    86  	}
    87  
    88  	return resolveDefinitions(defs, tests), nil
    89  }
    90  
    91  func resolveDefinitions(defs []oval.Definition, tests map[string]resolvedTest) []Entry {
    92  	var entries []Entry
    93  
    94  	for _, def := range defs {
    95  		test, ok := tests[def.Criteria.Criterion.TestRef]
    96  		if !ok {
    97  			continue
    98  		}
    99  		entry := Entry{
   100  			PkgName:  test.Name,
   101  			Version:  test.Version,
   102  			Operator: test.Operator,
   103  			Metadata: def.Metadata,
   104  		}
   105  
   106  		entries = append(entries, entry)
   107  	}
   108  	return entries
   109  }
   110  
   111  const (
   112  	lte operator = "less than or equal"
   113  	lt  operator = "less than"
   114  )
   115  
   116  func resolveTests(dir string) (map[string]resolvedTest, error) {
   117  	objects, err := oval.ParseObjects(dir)
   118  	if err != nil {
   119  		return nil, xerrors.Errorf("failed to parse objects: %w", err)
   120  	}
   121  
   122  	states, err := oval.ParseStates(dir)
   123  	if err != nil {
   124  		return nil, xerrors.Errorf("failed to parse states: %w", err)
   125  	}
   126  
   127  	tt, err := oval.ParseTests(dir)
   128  	if err != nil {
   129  		return nil, xerrors.Errorf("failed to parse tests: %w", err)
   130  	}
   131  
   132  	tests := map[string]resolvedTest{}
   133  	for _, test := range tt.RpminfoTests {
   134  		// test directive has should be "at least one"
   135  		if test.Check != "at least one" {
   136  			continue
   137  		}
   138  
   139  		t, err := followTestRefs(test, objects, states)
   140  		if err != nil {
   141  			return nil, xerrors.Errorf("unable to follow test refs: %w", err)
   142  		}
   143  		tests[test.ID] = t
   144  	}
   145  
   146  	return tests, nil
   147  }
   148  
   149  func followTestRefs(test oval.RpmInfoTest, objects map[string]string, states map[string]oval.RpmInfoState) (resolvedTest, error) {
   150  	// Follow object ref
   151  	if test.Object.ObjectRef == "" {
   152  		return resolvedTest{}, xerrors.New("invalid test, no object ref")
   153  	}
   154  
   155  	pkgName, ok := objects[test.Object.ObjectRef]
   156  	if !ok {
   157  		return resolvedTest{}, xerrors.Errorf("invalid test data, can't find object ref: %s, test ref: %s",
   158  			test.Object.ObjectRef, test.ID)
   159  	}
   160  
   161  	// Follow state ref
   162  	if test.State.StateRef == "" {
   163  		return resolvedTest{}, xerrors.New("invalid test, no state ref")
   164  	}
   165  
   166  	state, ok := states[test.State.StateRef]
   167  	if !ok {
   168  		return resolvedTest{}, xerrors.Errorf("invalid tests data, can't find ovalstate ref %s, test ref: %s",
   169  			test.State.StateRef, test.ID)
   170  	}
   171  
   172  	if state.Evr.Datatype != "evr_string" {
   173  		return resolvedTest{}, xerrors.Errorf("state data type (%s): %w", state.Evr.Datatype, ErrNotSupported)
   174  	}
   175  
   176  	if state.Evr.Operation != string(lte) && state.Evr.Operation != string(lt) {
   177  		return resolvedTest{}, xerrors.Errorf("state operation (%s): %w", state.Evr.Operation, ErrNotSupported)
   178  	}
   179  
   180  	return resolvedTest{
   181  		Name:     pkgName,
   182  		Version:  state.Evr.Text,
   183  		Operator: operator(state.Evr.Operation),
   184  	}, nil
   185  }
   186  
   187  func (vs VulnSrc) save(majorVer string, entries []Entry) error {
   188  	err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error {
   189  		platformName := fmt.Sprintf(platformFormat, majorVer)
   190  		if err := vs.dbc.PutDataSource(tx, platformName, source); err != nil {
   191  			return xerrors.Errorf("failed to put data source: %w", err)
   192  		}
   193  
   194  		if err := vs.commit(tx, platformName, entries); err != nil {
   195  			return xerrors.Errorf("CBL-Mariner %s commit error: %w", majorVer, err)
   196  		}
   197  		return nil
   198  	})
   199  	if err != nil {
   200  		return xerrors.Errorf("error in db batch update: %w", err)
   201  	}
   202  	return nil
   203  }
   204  
   205  func (vs VulnSrc) commit(tx *bolt.Tx, platformName string, entries []Entry) error {
   206  	for _, entry := range entries {
   207  		cveID := entry.Metadata.Reference.RefID
   208  		advisory := types.Advisory{}
   209  
   210  		// Definition.Metadata.Patchable has a bool and "Not Applicable" string.
   211  		patchable := strings.ToLower(entry.Metadata.Patchable)
   212  		if patchable == "true" {
   213  			advisory.FixedVersion = entry.Version
   214  		} else if patchable == "not applicable" {
   215  			continue
   216  		}
   217  
   218  		if err := vs.dbc.PutAdvisoryDetail(tx, cveID, entry.PkgName, []string{platformName}, advisory); err != nil {
   219  			return xerrors.Errorf("failed to save CBL-Mariner advisory detail: %w", err)
   220  		}
   221  
   222  		severity, _ := types.NewSeverity(strings.ToUpper(entry.Metadata.Severity))
   223  		vuln := types.VulnerabilityDetail{
   224  			Severity:    severity,
   225  			Title:       entry.Metadata.Title,
   226  			Description: entry.Metadata.Description,
   227  			References:  []string{entry.Metadata.Reference.RefURL},
   228  		}
   229  		if err := vs.dbc.PutVulnerabilityDetail(tx, cveID, source.ID, vuln); err != nil {
   230  			return xerrors.Errorf("failed to save CBL-Mariner vulnerability detail: %w", err)
   231  		}
   232  
   233  		if err := vs.dbc.PutVulnerabilityID(tx, cveID); err != nil {
   234  			return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  func (vs VulnSrc) Get(release, pkgName string) ([]types.Advisory, error) {
   241  	bucket := fmt.Sprintf(platformFormat, release)
   242  	advisories, err := vs.dbc.GetAdvisories(bucket, pkgName)
   243  	if err != nil {
   244  		return nil, xerrors.Errorf("failed to get CBL-Marina advisories: %w", err)
   245  	}
   246  	return advisories, nil
   247  }