Skip to content

Commit

Permalink
feat(cli): HTML format for vulnerability assessments
Browse files Browse the repository at this point in the history
One feature that our customers has expressed interest in is the ability
to output scan results to HTML so that other teams have clear, actionable
insights to understand vulnerabilities so they can remediate.

This change is introducing a new flag `--html` to the following
commands:
* `lacework vulnerability container scan`
* `lacework vulnerability container scan-status`
* `lacework vulnerability container show-assessment`

The flag will generate a vulnerability assessment in HTML format in
addition to the regular human-readable output.

```
$ lacework vuln ctr show sha256:593efe1a651b4cbc752a99e1771781fa3e5e30dd99579614114feaafb2aae3d3 -p demo --html
                                  CONTAINER IMAGE DETAILS                                 |        VULNERABILITIES
------------------------------------------------------------------------------------------+---------------------------------
    ID          sha256:b86dab42a3b576de2833a9d2b2a06fcbd3608acf10a1152634f9b8aeeb7a2e51   |   SEVERITY   COUNT   FIXABLE
    Digest      sha256:593efe1a651b4cbc752a99e1771781fa3e5e30dd99579614114feaafb2aae3d3   | -----------+-------+----------
    Registry    index.docker.io                                                           |   Critical       7         5
    Repository  mikeoclw/se                                                               |   High          52        33
    Size        390.1 MB                                                                  |   Medium       240       159
    Created At  2020-08-28T22:31:48+0000                                                  |   Low          254       125
    Tags                                                                                  |   Info         361        24
                                                                                          |
The container vulnerability assessment was stored at 'mikeoclw-se-sha256:593efe1a651b4cbc752a99e1771781fa3e5e30dd99579614114feaafb2aae3d3.html'
```
The HTML is a standalone file that can be downloaded that shared without
additional artifacts and it looks exactly like the Lacework Console!

JIRA: ALLY-222

Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune committed Nov 11, 2020
1 parent 821b8e6 commit 00c2f43
Show file tree
Hide file tree
Showing 11 changed files with 2,991 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ build-cli-cross-platform:
-ldflags=$(GO_LDFLAGS) \
github.com/lacework/go-sdk/cli

generate-databox:
go generate internal/databox/box.go

generate-docs:
go generate cli/cmd/docs.go

Expand Down
54 changes: 54 additions & 0 deletions api/vulnerabilities_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,60 @@ type containerVulnerability struct {
Metadata map[string]interface{} `json:"metadata"`
}

// traverseMetadata will try to extract an interface from the nested tree of key
// values contain inside the Metadata field of a container vulnerability struct
//
// Example: extract the version 3 of the CVSS Score
//
// score := v.traverseMetadata("NVD", "CVSSv3", "Score")
//
func (v *containerVulnerability) traverseMetadata(fields ...string) interface{} {

var (
metaInterface interface{}
metaMap = v.Metadata
)
for i, field := range fields {

if i != 0 {
if newMap, ok := metaInterface.(map[string]interface{}); ok {
metaMap = newMap
} else {
return nil
}
}

if found, ok := metaMap[field]; ok {
metaInterface = found
} else {
return nil
}

}

return metaInterface
}

func (v *containerVulnerability) CVSSv3Score() float64 {
score := v.traverseMetadata("NVD", "CVSSv3", "Score")

if f, ok := score.(float64); ok {
return f
}

return 0
}

func (v *containerVulnerability) CVSSv2Score() float64 {
score := v.traverseMetadata("NVD", "CVSSv2", "Score")

if f, ok := score.(float64); ok {
return f
}

return 0
}

type VulnContainerAssessmentsResponse struct {
Assessments []VulnContainerAssessmentSummary `json:"data"`
Ok bool `json:"ok"`
Expand Down
12 changes: 12 additions & 0 deletions api/vulnerabilities_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,18 @@ func TestVulnerabilitiesReportFromID(t *testing.T) {

assert.Equal(t, uno, response.Data.VulnFixableCount("High"))
assert.Equal(t, zero, response.Data.VulnFixableCount("Info"))

// CVSSv2 Score
var expectedScore float64 = 5
assert.Equal(t, expectedScore, response.Data.Image.
ImageLayers[0].Packages[0].
Vulnerabilities[0].CVSSv2Score())

// CVSSv3 Score
expectedScore = 7.5
assert.Equal(t, expectedScore, response.Data.Image.
ImageLayers[0].Packages[0].
Vulnerabilities[0].CVSSv3Score())
}
}

Expand Down
18 changes: 16 additions & 2 deletions cli/cmd/vuln_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ func init() {
vulContainerScanStatusCmd.Flags(),
)

setHtmlFlag(
vulContainerScanCmd.Flags(),
vulContainerScanStatusCmd.Flags(),
vulContainerShowAssessmentCmd.Flags(),
)

setDetailsFlag(
vulContainerScanCmd.Flags(),
vulContainerScanStatusCmd.Flags(),
Expand Down Expand Up @@ -328,11 +334,17 @@ func showContainerAssessmentsWithSha256(sha string) error {
status := assessment.CheckStatus()
switch status {
case "Success":

if cli.JSONOutput() {
return cli.OutputJSON(assessment.Data)
}

cli.OutputHuman(buildVulnerabilityReport(&assessment.Data))

// @afiune is this the best way to make sense of this new flag?
if vulCmdState.Html {
return generateVulnAssessmentHTML(&assessment.Data)
}
case "Unsupported":
return errors.Errorf(
`unable to retrieve assessment for the provided container image. (unsupported distribution)
Expand Down Expand Up @@ -416,9 +428,11 @@ func buildVulnerabilityReport(assessment *api.VulnContainerAssessment) string {
} else {
mainReport.WriteString(buildVulnerabilityReportDetails(assessment))
mainReport.WriteString("\n")
mainReport.WriteString("Try adding '--packages' to show a list of packages with CVE count.\n")
if !vulCmdState.Html {
mainReport.WriteString("Try adding '--packages' to show a list of packages with CVE count.\n")
}
}
} else {
} else if !vulCmdState.Html {
mainReport.WriteString(
"Try adding '--details' to increase details shown about the vulnerability assessment.\n",
)
Expand Down
Loading

0 comments on commit 00c2f43

Please sign in to comment.