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  }