Skip to content

Commit

Permalink
fix: false positive results in pkg manifest scan (#255)
Browse files Browse the repository at this point in the history
The API response coming from the Lacework server contains too much
information that could confuse our users, this change is cleaning the
response by removing all vulnerabilities that don't match, that is,
those vulnerabilities that have the evaluation status equal to
`VULNERABLE`.

This fix is modifying both, the human-readable and JSON outputs.

Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune authored Dec 7, 2020
1 parent 3d71ed1 commit a6d6cda
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 53 deletions.
80 changes: 69 additions & 11 deletions api/vulnerabilities_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,23 @@ type HostVulnerabilityService struct {
// to determine if the packages contain any common vulnerabilities and exposures
//
// NOTE: Only packages managed by a package manager for supported OS's are reported
func (svc *HostVulnerabilityService) Scan(manifest string) (
func (svc *HostVulnerabilityService) Scan(manifest *PackageManifest) (
response HostVulnScanPkgManifestResponse,
err error,
) {
err = svc.client.RequestDecoder("POST",
err = svc.client.RequestEncoderDecoder("POST",
apiVulnerabilitiesScanPkgManifest,
strings.NewReader(manifest),
manifest,
&response,
)

if err == nil {
// the API response coming from the Lacework server contains too much
// information that could confuse our users, this function will parse
// all the vulnerabilities and remove the non-matching ones
response.CleanResponse()
}

return
}

Expand Down Expand Up @@ -268,38 +276,50 @@ type HostVulnScanPkgManifestResponse struct {
Message string `json:"message"`
}

func (scanPkgManifest *HostVulnScanPkgManifestResponse) VulnerabilityCounts() HostVulnCounts {
var hostCounts = HostVulnCounts{}
// CleanResponse will go over all the vulnerabilities from a package-manifest
// scan and remove the non-matching ones, leaving only the vulnerabilities
// that matter
func (scanPkgManifest *HostVulnScanPkgManifestResponse) CleanResponse() {
filteredVulns := make([]HostScanPackageVulnDetails, 0)

for _, vuln := range scanPkgManifest.Vulns {
if vuln.Summary.EvalStatus != "MATCH_VULN" {
if !vuln.Match() {
continue
}
filteredVulns = append(filteredVulns, vuln)
}

scanPkgManifest.Vulns = filteredVulns
}

func (scanPkgManifest *HostVulnScanPkgManifestResponse) VulnerabilityCounts() HostVulnCounts {
var hostCounts = HostVulnCounts{}

for _, vuln := range scanPkgManifest.Vulns {
switch vuln.Severity {
case "Critical":
hostCounts.Critical++
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
hostCounts.CritFixable++
}
case "High":
hostCounts.High++
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
hostCounts.HighFixable++
}
case "Medium":
hostCounts.Medium++
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
hostCounts.MedFixable++
}
case "Low":
hostCounts.Low++
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
hostCounts.LowFixable++
}
default:
hostCounts.Negligible++
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
hostCounts.NegFixable++
}
}
Expand Down Expand Up @@ -394,3 +414,41 @@ type HostScanPackageVulnDetails struct {
} `json:"SUMMARY"`
VulnID string `json:"VULN_ID"`
}

func (v *HostScanPackageVulnDetails) Match() bool {
if v.Summary.EvalStatus != "MATCH_VULN" {
return false
}
if v.FixInfo.EvalStatus != "VULNERABLE" {
return false
}
return true
}

func (v *HostScanPackageVulnDetails) HasFix() bool {
return v.FixInfo.FixAvailable == 1
}

// PackageManifest is the representation of a package manifest
// that the Lacework API server expects when executing a scan
//
// {
// "os_pkg_info_list": [
// {
// "os":"Ubuntu",
// "os_ver":"18.04",
// "pkg": "openssl",
// "pkg_ver": "1.1.1-1ubuntu2.1~18.04.6"
// }
// ]
// }
type PackageManifest struct {
OsPkgInfoList []OsPkgInfo `json:"os_pkg_info_list"`
}

type OsPkgInfo struct {
Os string `json:"os"`
OsVer string `json:"os_ver"`
Pkg string `json:"pkg"`
PkgVer string `json:"pkg_ver"`
}
19 changes: 5 additions & 14 deletions cli/cmd/package_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,12 @@ import (
"syscall"

"github.com/pkg/errors"

"github.com/lacework/go-sdk/api"
)

var SupportedPackageManagers = []string{"dpkg-query", "rpm"} // @afiune can we support ym and apk?

type PackageManifest struct {
OsPkgInfoList []OsPkgInfo `json:"os_pkg_info_list"`
}

type OsPkgInfo struct {
Os string `json:"os"`
OsVer string `json:"os_ver"`
Pkg string `json:"pkg"`
PkgVer string `json:"pkg_ver"`
}

type OS struct {
Name string
Version string
Expand All @@ -55,8 +46,8 @@ var (
rexVersionID = regexp.MustCompile(`^VERSION_ID=(.*)$`)
)

func (c *cliState) GeneratePackageManifest() (*PackageManifest, error) {
manifest := new(PackageManifest)
func (c *cliState) GeneratePackageManifest() (*api.PackageManifest, error) {
manifest := new(api.PackageManifest)
osInfo, err := cli.GetOSInfo()
if err != nil {
return manifest, err
Expand Down Expand Up @@ -144,7 +135,7 @@ func (c *cliState) GeneratePackageManifest() (*PackageManifest, error) {
}

manifest.OsPkgInfoList = append(manifest.OsPkgInfoList,
OsPkgInfo{
api.OsPkgInfo{
Os: osInfo.Name,
OsVer: osInfo.Version,
Pkg: pkgDetail[0],
Expand Down
60 changes: 33 additions & 27 deletions cli/cmd/vuln_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,38 +92,52 @@ To generate a package-manifest from the local host and scan it automatically:
- This operation is limited to 1k of packages per payload. If you require a payload
larger than 1k, you must make multiple requests.`,
RunE: func(_ *cobra.Command, args []string) error {
var pkgManifest = ""
var (
pkgManifest = new(api.PackageManifest)
pkgManifestBytes []byte
err error
)

if len(args) != 0 && args[0] != "" {
pkgManifest = args[0]
pkgManifestBytes = []byte(args[0])
cli.Log.Infow("package manifest loaded from arguments", "raw", args[0])
} else if pkgManifestFile != "" {
pkgManifestBytes, err := ioutil.ReadFile(pkgManifestFile)
pkgManifestBytes, err = ioutil.ReadFile(pkgManifestFile)
if err != nil {
return errors.Wrap(err, "unable to read file")
}
pkgManifest = string(pkgManifestBytes)
cli.Log.Infow("package manifest loaded from file", "raw", string(pkgManifestBytes))
} else if pkgManifestLocal {
manifest, err := cli.GeneratePackageManifest()
pkgManifest, err = cli.GeneratePackageManifest()
if err != nil {
return errors.Wrap(err, "unable to generate package manifest")
}
manifestString, err := json.Marshal(&manifest)
if err != nil {
panic(err)
}

pkgManifest = string(manifestString)
cli.Log.Infow("package manifest generated from localhost", "raw", pkgManifest)
} else {
// avoid asking for a confirmation before launching the editor
var content string
prompt := &survey.Editor{
Message: "Provide a package manifest to scan",
FileName: "pkg-manifest*.json",
FileName: "package-manifest*.json",
}
err := survey.AskOne(prompt, &pkgManifest)
err = survey.AskOne(prompt, &content)
if err != nil {
return errors.Wrap(err, "unable to load package manifest from editor")
}
pkgManifestBytes = []byte(content)
cli.Log.Infow("package manifest loaded via editor", "raw", content)
}

if len(pkgManifestBytes) != 0 {
err = json.Unmarshal(pkgManifestBytes, pkgManifest)
if err != nil {
return err
return errors.Wrap(err, "invalid package manifest json file")
}
}

// TODO @afiune check if the package manifest has more than
// 1k packages, if so, make multiple API requests

response, err := cli.LwApi.Vulnerabilities.Host.Scan(pkgManifest)
if err != nil {
return errors.Wrap(err, "unable to request an on-demand host vulnerability scan")
Expand Down Expand Up @@ -844,16 +858,12 @@ func hostScanPackagesVulnToTable(scan *api.HostVulnScanPkgManifestResponse) stri
func hostScanPackagesVulnDetailsTable(vulns []api.HostScanPackageVulnDetails) [][]string {
out := [][]string{}
for _, vuln := range vulns {
if vuln.Summary.EvalStatus != "MATCH_VULN" {
continue
}

if vulCmdState.Fixable && vuln.FixInfo.EvalStatus != "GOOD" {
if vulCmdState.Fixable && vuln.HasFix() {
continue
}

fixedVersion := ""
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
fixedVersion = vuln.FixInfo.FixedVersion
}

Expand All @@ -878,11 +888,7 @@ func hostScanPackagesVulnDetailsTable(vulns []api.HostScanPackageVulnDetails) []
func hostScanPackagesVulnPackagesView(vulns []api.HostScanPackageVulnDetails) [][]string {
out := [][]string{}
for _, vuln := range vulns {
if vuln.Summary.EvalStatus != "MATCH_VULN" {
continue
}

if vulCmdState.Fixable && vuln.FixInfo.EvalStatus != "GOOD" {
if vulCmdState.Fixable && vuln.HasFix() {
continue
}

Expand All @@ -897,7 +903,7 @@ func hostScanPackagesVulnPackagesView(vulns []api.HostScanPackageVulnDetails) []
added = true
}

if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
if fixes, err := strconv.Atoi(out[i][4]); err == nil {
out[i][4] = fmt.Sprintf("%d", (fixes + 1))
}
Expand All @@ -910,7 +916,7 @@ func hostScanPackagesVulnPackagesView(vulns []api.HostScanPackageVulnDetails) []
}

fixes := "0"
if vuln.FixInfo.EvalStatus == "GOOD" {
if vuln.HasFix() {
fixes = "1"
}

Expand Down
3 changes: 2 additions & 1 deletion cli/cmd/vuln_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import (
"sort"
"strings"

"github.com/lacework/go-sdk/api"
"github.com/lacework/go-sdk/internal/databox"
"github.com/pkg/errors"

"github.com/lacework/go-sdk/api"
)

const (
Expand Down

0 comments on commit a6d6cda

Please sign in to comment.