go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/platform_advisories.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package resources 5 6 import ( 7 "context" 8 "net/http" 9 "time" 10 11 "github.com/mitchellh/mapstructure" 12 "github.com/rs/zerolog/log" 13 "go.mondoo.com/cnquery/llx" 14 "go.mondoo.com/cnquery/logger" 15 "go.mondoo.com/cnquery/providers-sdk/v1/plugin" 16 "go.mondoo.com/cnquery/providers-sdk/v1/resources" 17 "go.mondoo.com/cnquery/providers-sdk/v1/upstream/mvd" 18 "go.mondoo.com/cnquery/providers-sdk/v1/upstream/mvd/cvss" 19 "go.mondoo.com/cnquery/providers-sdk/v1/util/convert" 20 "go.mondoo.com/cnquery/providers/os/connection/shared" 21 "go.mondoo.com/ranger-rpc" 22 ) 23 24 // TODO: generalize this kind of function 25 func getKernelVersion(kernel *mqlKernel) string { 26 raw := kernel.GetInfo() 27 if raw.Error != nil { 28 return "" 29 } 30 31 info, ok := raw.Data.(map[string]interface{}) 32 if !ok { 33 return "" 34 } 35 36 val, ok := info["version"] 37 if !ok { 38 return "" 39 } 40 41 return val.(string) 42 } 43 44 func newAdvisoryScannerHttpClient(mondooapi string, plugins []ranger.ClientPlugin, httpClient *http.Client) (*mvd.AdvisoryScannerClient, error) { 45 sa, err := mvd.NewAdvisoryScannerClient(mondooapi, httpClient) 46 if err != nil { 47 return nil, err 48 } 49 50 for i := range plugins { 51 sa.AddPlugin(plugins[i]) 52 } 53 return sa, nil 54 } 55 56 func fetchVulnReport(runtime *plugin.Runtime) (interface{}, error) { 57 mcc := runtime.Upstream 58 if mcc == nil || mcc.ApiEndpoint == "" { 59 return nil, resources.MissingUpstreamError{} 60 } 61 62 // get new advisory report 63 // start scanner client 64 scannerClient, err := newAdvisoryScannerHttpClient(mcc.ApiEndpoint, mcc.Plugins, mcc.HttpClient) 65 if err != nil { 66 return nil, err 67 } 68 69 conn := runtime.Connection.(shared.Connection) 70 apiPackages := []*mvd.Package{} 71 kernelVersion := "" 72 73 // collect pacakges if the platform supports gathering files 74 if conn.Capabilities().Has(shared.Capability_File) { 75 obj, err := CreateResource(runtime, "packages", map[string]*llx.RawData{}) 76 if err != nil { 77 return nil, err 78 } 79 packages := obj.(*mqlPackages) 80 81 r := packages.GetList() 82 if r.Error != nil { 83 return nil, r.Error 84 } 85 86 for i := range r.Data { 87 mqlPkg := r.Data[i] 88 pkg := mqlPkg.(*mqlPackage) 89 90 apiPackages = append(apiPackages, &mvd.Package{ 91 Name: pkg.Name.Data, 92 Version: pkg.Version.Data, 93 Arch: pkg.Arch.Data, 94 Format: pkg.Format.Data, 95 Origin: pkg.Origin.Data, 96 }) 97 } 98 99 // determine the kernel version if possible (just needed for linux at this point) 100 // therefore we ignore the error because its not important, worst case the user sees to many advisories 101 objKernel, err := CreateResource(runtime, "kernel", map[string]*llx.RawData{}) 102 if err == nil { 103 kernelVersion = getKernelVersion(objKernel.(*mqlKernel)) 104 } 105 } 106 107 scanjob := &mvd.AnalyseAssetRequest{ 108 Platform: convertPlatform2VulnPlatform(conn.Asset().Platform), 109 Packages: apiPackages, 110 KernelVersion: kernelVersion, 111 } 112 logger.DebugDumpYAML("vuln-scan-job", scanjob) 113 114 log.Debug().Bool("incognito", mcc.Incognito).Msg("run advisory scan") 115 report, err := scannerClient.AnalyseAsset(context.Background(), scanjob) 116 if err != nil { 117 return nil, err 118 } 119 120 return convert.JsonToDict(report) 121 } 122 123 func (p *mqlPlatform) vulnerabilityReport() (interface{}, error) { 124 return fetchVulnReport(p.MqlRuntime) 125 } 126 127 // fetches the vulnerability report and returns the full report 128 func (p *mqlAsset) vulnerabilityReport() (interface{}, error) { 129 return fetchVulnReport(p.MqlRuntime) 130 } 131 132 func getAdvisoryReport(runtime *plugin.Runtime) (*mvd.VulnReport, error) { 133 obj, err := CreateResource(runtime, "asset", map[string]*llx.RawData{}) 134 if err != nil { 135 return nil, err 136 } 137 asset := obj.(*mqlAsset) 138 139 r := asset.GetVulnerabilityReport() 140 if r.Error != nil { 141 return nil, r.Error 142 } 143 rawReport := r.Data 144 145 var vulnReport mvd.VulnReport 146 cfg := &mapstructure.DecoderConfig{ 147 Metadata: nil, 148 Result: &vulnReport, 149 TagName: "json", 150 } 151 decoder, _ := mapstructure.NewDecoder(cfg) 152 err = decoder.Decode(rawReport) 153 if err != nil { 154 return nil, err 155 } 156 157 return &vulnReport, nil 158 } 159 160 func (a *mqlPlatformAdvisories) id() (string, error) { 161 return "platform.advisories", nil 162 } 163 164 func (a *mqlPlatformAdvisories) cvss() (*mqlAuditCvss, error) { 165 report, err := getAdvisoryReport(a.MqlRuntime) 166 if err != nil { 167 return nil, err 168 } 169 170 obj, err := CreateResource(a.MqlRuntime, "audit.cvss", map[string]*llx.RawData{ 171 "score": llx.FloatData(float64(report.Stats.Score) / 10), 172 "vector": llx.StringData(""), // TODO: we need to extend the report to include the vector in the report 173 }) 174 if err != nil { 175 return nil, err 176 } 177 178 return obj.(*mqlAuditCvss), nil 179 } 180 181 func (a *mqlPlatformAdvisories) list() ([]interface{}, error) { 182 report, err := getAdvisoryReport(a.MqlRuntime) 183 if err != nil { 184 return nil, err 185 } 186 187 mqlAdvisories := make([]interface{}, len(report.Advisories)) 188 for i := range report.Advisories { 189 advisory := report.Advisories[i] 190 191 var worstScore *cvss.Cvss 192 if advisory.WorstScore != nil { 193 worstScore = advisory.WorstScore 194 } else { 195 worstScore = &cvss.Cvss{Score: 0.0, Vector: ""} 196 } 197 198 cvssScore, err := CreateResource(a.MqlRuntime, "audit.cvss", map[string]*llx.RawData{ 199 "score": llx.FloatData(float64(worstScore.Score)), 200 "vector": llx.StringData(worstScore.Vector), 201 }) 202 if err != nil { 203 return nil, err 204 } 205 206 var published *time.Time 207 parsedTime, err := time.Parse(time.RFC3339, advisory.Published) 208 if err == nil { 209 published = &parsedTime 210 } 211 212 var modified *time.Time 213 parsedTime, err = time.Parse(time.RFC3339, advisory.Modified) 214 if err == nil { 215 modified = &parsedTime 216 } 217 218 mqlAdvisory, err := CreateResource(a.MqlRuntime, "audit.advisory", map[string]*llx.RawData{ 219 "id": llx.StringData(advisory.ID), 220 "mrn": llx.StringData(advisory.Mrn), 221 "title": llx.StringData(advisory.Title), 222 "description": llx.StringData(advisory.Description), 223 "published": llx.TimeData(*published), 224 "modified": llx.TimeData(*modified), 225 "worstScore": llx.ResourceData(cvssScore, "audit.cvss"), 226 }) 227 if err != nil { 228 return nil, err 229 } 230 231 mqlAdvisories[i] = mqlAdvisory 232 } 233 234 return mqlAdvisories, nil 235 } 236 237 func (a *mqlPlatformAdvisories) stats() (interface{}, error) { 238 report, err := getAdvisoryReport(a.MqlRuntime) 239 if err != nil { 240 return nil, err 241 } 242 243 dict, err := convert.JsonToDict(report.Stats.Advisories) 244 if err != nil { 245 return nil, err 246 } 247 248 return dict, nil 249 } 250 251 func (a *mqlPlatformCves) id() (string, error) { 252 return "platform.cves", nil 253 } 254 255 func (a *mqlPlatformCves) list() ([]interface{}, error) { 256 report, err := getAdvisoryReport(a.MqlRuntime) 257 if err != nil { 258 return nil, err 259 } 260 261 cveList := report.Cves() 262 263 mqlCves := make([]interface{}, len(cveList)) 264 for i := range cveList { 265 cve := cveList[i] 266 267 var worstScore *cvss.Cvss 268 if cve.WorstScore != nil { 269 worstScore = cve.WorstScore 270 } else { 271 worstScore = &cvss.Cvss{Score: 0.0, Vector: ""} 272 } 273 274 cvssScore, err := CreateResource(a.MqlRuntime, "audit.cvss", map[string]*llx.RawData{ 275 "score": llx.FloatData(float64(worstScore.Score)), 276 "vector": llx.StringData(worstScore.Vector), 277 }) 278 if err != nil { 279 return nil, err 280 } 281 282 var published *time.Time 283 parsedTime, err := time.Parse(time.RFC3339, cve.Published) 284 if err == nil { 285 published = &parsedTime 286 } 287 288 var modified *time.Time 289 parsedTime, err = time.Parse(time.RFC3339, cve.Modified) 290 if err == nil { 291 modified = &parsedTime 292 } 293 294 mqlCve, err := CreateResource(a.MqlRuntime, "audit.cve", map[string]*llx.RawData{ 295 "id": llx.StringData(cve.ID), 296 "mrn": llx.StringData(cve.Mrn), 297 "state": llx.StringData(cve.State.String()), 298 "summary": llx.StringData(cve.Summary), 299 "unscored": llx.BoolData(cve.Unscored), 300 "published": llx.TimeDataPtr(published), 301 "modified": llx.TimeDataPtr(modified), 302 "worstScore": llx.ResourceData(cvssScore, "audit.cvss"), 303 }) 304 if err != nil { 305 return nil, err 306 } 307 308 mqlCves[i] = mqlCve 309 } 310 311 return mqlCves, nil 312 } 313 314 func (a *mqlPlatformCves) cvss() (*mqlAuditCvss, error) { 315 report, err := getAdvisoryReport(a.MqlRuntime) 316 if err != nil { 317 return nil, err 318 } 319 320 score := float32(0.0) 321 for i := range report.Advisories { 322 advisory := report.Advisories[i] 323 for j := range advisory.Cves { 324 cve := advisory.Cves[j] 325 if cve.WorstScore != nil && cve.WorstScore.Score > score { 326 score = cve.WorstScore.Score 327 } 328 } 329 } 330 331 obj, err := CreateResource(a.MqlRuntime, "audit.cvss", map[string]*llx.RawData{ 332 "score": llx.FloatData(float64(int(score*10)) / 10), 333 "vector": llx.StringData(""), 334 }) 335 if err != nil { 336 return nil, err 337 } 338 339 return obj.(*mqlAuditCvss), nil 340 } 341 342 func (a *mqlPlatformCves) stats() (interface{}, error) { 343 report, err := getAdvisoryReport(a.MqlRuntime) 344 if err != nil { 345 return nil, err 346 } 347 348 dict, err := convert.JsonToDict(report.Stats.Cves) 349 if err != nil { 350 return nil, err 351 } 352 353 return dict, nil 354 }