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 }