Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/state/internal/cmdtree/cmdtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ func New(prime *primer.Values, args ...string) *CmdTree {
newLogoutCommand(prime),
)

cveCmd := newCveCommand(prime)
cveCmd.AddChildren(
newReportCommand(prime),
)

exportCmd := newExportCommand(prime)
exportCmd.AddChildren(
newRecipeCommand(prime),
Expand Down Expand Up @@ -125,6 +130,7 @@ func New(prime *primer.Values, args ...string) *CmdTree {
newActivateCommand(prime),
newInitCommand(prime),
newPushCommand(prime),
cveCmd,
projectsCmd,
authCmd,
exportCmd,
Expand Down
54 changes: 54 additions & 0 deletions cmd/state/internal/cmdtree/cve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmdtree

import (
"github.com/ActiveState/cli/internal/captain"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/primer"
"github.com/ActiveState/cli/internal/runners/cve"
"github.com/ActiveState/cli/pkg/project"
)

func newCveCommand(prime *primer.Values) *captain.Command {
runner := cve.NewCve(prime)

cmd := captain.NewCommand(
"cve",
locale.Tl("cve_title", "Vulnerability Summary"),
locale.Tl("cve_description", "Show a summary of project vulnerabilities"),
prime.Output(),
prime.Config(),
[]*captain.Flag{},
[]*captain.Argument{},
func(_ *captain.Command, _ []string) error {
return runner.Run()
},
)
cmd.SetGroup(PlatformGroup)
return cmd
}

func newReportCommand(prime *primer.Values) *captain.Command {
report := cve.NewReport(prime)
params := cve.ReportParams{
Namespace: &project.Namespaced{},
}

return captain.NewCommand(
"report",
locale.Tl("cve_report_title", "Vulnerability Report"),
locale.Tl("cve_report_cmd_description", "Show a detailed report of project vulnerabilities"),
prime.Output(),
prime.Config(),
[]*captain.Flag{},
[]*captain.Argument{
{
Name: locale.Tl("cve_report_namespace_arg", "Organization/Project"),
Description: locale.Tl("cve_report_namespace_arg_description", "The project for which the report is created"),
Value: params.Namespace,
},
},
func(_ *captain.Command, _ []string) error {
return report.Run(&params)
},
)
}
5 changes: 4 additions & 1 deletion internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ const InventoryAPIPath = "/sv/inventory-api-v1"
// GraphqlAPIPath is the path used for the platform graphql api
const GraphqlAPIPath = "/graphql/v1/graphql"

// RequirementsImportAPIPath is the path used for the requiremments import api
// MediatorAPIPath is the path used for the platform mediator api
const MediatorAPIPath = "/sv/mediator/api"

// RequirementsImportAPIPath is the path used for the requirements import api
const RequirementsImportAPIPath = "/sv/reqsvc/reqs"

// DeprecationInfoURL is the URL we check against to see what versions are deprecated
Expand Down
155 changes: 155 additions & 0 deletions internal/runners/cve/cve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package cve

import (
"fmt"

"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/output"
"github.com/ActiveState/cli/internal/primer"
medmodel "github.com/ActiveState/cli/pkg/platform/api/mediator/model"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/platform/model"
"github.com/ActiveState/cli/pkg/project"
)

type Cve struct {
proj *project.Project
auth *authentication.Auth
out output.Outputer
}

type outputData struct {
Project string `json:"project"`
CommitID string `json:"commitID"`
Histogram []medmodel.SeverityCount `json:"vulnerability_histogram"`
Packages []ByPackageOutput `json:"packages"`
}

type outputDataPrinter struct {
output output.Outputer
data *outputData
}

type ByPackageOutput struct {
Name string `json:"name" locale:"state_cve_package_name,Name"`
Version string `json:"version" locale:"state_cve_package_version,Version"`
CveCount int `json:"cve_count" locale:"state_cve_package_count,Count"`
}

type ProjectInfo struct {
Project string `locale:"project,Project"`
CommitID string `locale:"commit_id,Commit ID"`
}

type primeable interface {
primer.Projecter
primer.Auther
primer.Outputer
}

func NewCve(prime *primer.Values) *Cve {
return &Cve{prime.Project(), prime.Auth(), prime.Output()}
}

func (c *Cve) Run() error {
if c.proj == nil {
return locale.NewError("cve_no_project", "No project found at the current directory.")
}

if !c.auth.Authenticated() {
return errs.AddTips(
locale.NewError("cve_needs_authentication"),
locale.T("auth_tip"),
)
}

resp, err := model.FetchCommitVulnerabilities(c.auth, c.proj.CommitID())
if err != nil {
return locale.WrapError(err, "cve_mediator_resp", "Failed to retrieve vulnerability information")
}

details := model.ExtractPackageVulnerabilities(resp.Sources)
packageVulnerabilities := make([]ByPackageOutput, 0, len(details))
for _, v := range details {
packageVulnerabilities = append(packageVulnerabilities, ByPackageOutput{
v.Name, v.Version, len(v.Details),
})
}

cveOutput := &outputData{
Project: c.proj.Name(),
CommitID: resp.CommitID,
Histogram: resp.VulnerabilityHistogram,
Packages: packageVulnerabilities,
}

odp := &outputDataPrinter{
c.out,
cveOutput,
}

c.out.Print(odp)

return nil
}

type SeverityCountOutput struct {
Count string `locale:"count,Count"`
Severity string `locale:"severity,Severity"`
}

func (od *outputDataPrinter) printFooter() {
od.output.Print("")
od.output.Print([]string{
locale.Tl("cve_hint_report", "To view a detailed report for this runtime, run [ACTIONABLE]state cve report[/RESET]"),
locale.Tl("cve_hint_specific_report", "For a specific runtime, run [ACTIONABLE]state cve report [Organization/Project][/RESET]"),
})
}

func (od *outputDataPrinter) MarshalOutput(format output.Format) interface{} {
if format != output.PlainFormatName {
return od.data
}
pi := &ProjectInfo{
od.data.Project,
od.data.CommitID,
}
od.output.Print(struct {
*ProjectInfo `opts:"verticalTable"`
}{pi})

if len(od.data.Histogram) == 0 {
od.output.Print("")
od.output.Print(fmt.Sprintf("[SUCCESS]✔ %s[/RESET]", locale.Tl("no_cves", "No CVEs detected!")))
od.printFooter()
return output.Suppress
}

hist := make([]*SeverityCountOutput, 0, len(od.data.Histogram))
totalCount := 0
for _, h := range od.data.Histogram {
totalCount += h.Count
var ho *SeverityCountOutput
if h.Severity == "CRITICAL" {
ho = &SeverityCountOutput{
fmt.Sprintf("[ERROR]%d[/RESET]", h.Count),
fmt.Sprintf("[ERROR]%s[/RESET]", h.Severity),
}
} else {
ho = &SeverityCountOutput{
fmt.Sprintf("%d", h.Count),
h.Severity,
}
}
hist = append(hist, ho)
}
od.output.Print(output.Heading(fmt.Sprintf("%d Vulnerabilities", totalCount)))
od.output.Print(hist)

od.output.Print(output.Heading(fmt.Sprintf("%d Affected Packages", len(od.data.Packages))))
od.output.Print(od.data.Packages)

od.printFooter()
return output.Suppress
}
Loading