Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add vulnerability trend #3130

Merged
merged 7 commits into from
Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion contrib/plugins/plugin-clair/clair.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (d ClairPlugin) Run(a plugin.IJob) plugin.Result {
}
}

report := sdk.VulnerabilityReport{
report := sdk.VulnerabilityWorkerReport{
Vulnerabilities: vulnerabilities,
Summary: summary,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (d NpmAuditParserPlugin) Run(j plugin.IJob) plugin.Result {
return plugin.Fail
}

var report sdk.VulnerabilityReport
var report sdk.VulnerabilityWorkerReport
summary := make(map[string]int64)
for _, a := range npmAudit.Advisories {
for _, f := range a.Findings {
Expand Down Expand Up @@ -94,6 +94,7 @@ func (d NpmAuditParserPlugin) Run(j plugin.IJob) plugin.Result {

}
}
report.Summary = summary
if err := plugin.SendVulnerabilityReport(j, report); err != nil {
_ = plugin.SendLog(j, "Unable to send report: %s", err)
return plugin.Fail
Expand Down
4 changes: 4 additions & 0 deletions engine/api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ func (api *API) getApplicationHandler() Handler {
withKeys := FormBool(r, "withKeys")
withUsage := FormBool(r, "withUsage")
withDeploymentStrategies := FormBool(r, "withDeploymentStrategies")
withVulnerabilities := FormBool(r, "withVulnerabilities")
branchName := r.FormValue("branchName")
remote := r.FormValue("remote")
versionString := r.FormValue("version")
Expand All @@ -267,6 +268,9 @@ func (api *API) getApplicationHandler() Handler {
if withDeploymentStrategies {
loadOptions = append(loadOptions, application.LoadOptions.WithDeploymentStrategies)
}
if withVulnerabilities {
loadOptions = append(loadOptions, application.LoadOptions.WithVulnerabilities)
}

app, errApp := application.LoadByName(api.mustDB(), api.Cache, projectKey, applicationName, getUser(ctx), loadOptions...)
if errApp != nil {
Expand Down
29 changes: 18 additions & 11 deletions engine/api/application/application_vunerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,30 @@ import (
"github.com/ovh/cds/sdk"
)

// InsertVulnerability Insert a new vulnerability
func InsertVulnerability(db gorp.SqlExecutor, v sdk.Vulnerability) error {
dbVuln := dbApplicationVulnerability(v)
if err := db.Insert(&dbVuln); err != nil {
return sdk.WrapError(err, "InsertVulnerability> Unable to insert vulnerabilities")
// InsertVulnerabilities Insert vulnerabilities
func InsertVulnerabilities(db gorp.SqlExecutor, vs []sdk.Vulnerability, appID int64) error {
if _, err := db.Exec("DELETE FROM application_vulnerability WHERE application_id = $1", appID); err != nil {
return sdk.WrapError(err, "InsertVulnerability> Unable to remove old vulnerabilities")
}
for _, v := range vs {
v.ApplicationID = appID
dbVuln := dbApplicationVulnerability(v)
if err := db.Insert(&dbVuln); err != nil {
return sdk.WrapError(err, "InsertVulnerability> Unable to insert vulnerabilities")
}
}
return nil
}

// LoadVulnerabilitiesByRun loads vulnerabilities for the given run
func LoadVulnerabilitiesByRun(db gorp.SqlExecutor, nodeRunID int64) ([]sdk.Vulnerability, error) {
// LoadVulnerabilities load vulnerabilities for the given application
func LoadVulnerabilities(db gorp.SqlExecutor, appID int64) ([]sdk.Vulnerability, error) {
results := make([]dbApplicationVulnerability, 0)
query := `SELECT * FROM application_vulnerability
WHERE workflow_node_run_id=$1`
if _, err := db.Select(&results, query, nodeRunID); err != nil {
query := `SELECT *
FROM application_vulnerability
WHERE application_id = $1`
if _, err := db.Select(&results, query, appID); err != nil {
if err != sql.ErrNoRows {
return nil, sdk.WrapError(err, "LoadVulnerabilitiesByRun> unable to load vulnerabilities for run %d", nodeRunID)
return nil, sdk.WrapError(err, "LoadVulnerabilities> unable to load latest vulnerabilities for application %d", appID)
}
return nil, sdk.ErrNotFound
}
Expand Down
2 changes: 2 additions & 0 deletions engine/api/application/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var LoadOptions = struct {
WithClearKeys LoadOptionFunc
WithDeploymentStrategies LoadOptionFunc
WithClearDeploymentStrategies LoadOptionFunc
WithVulnerabilities LoadOptionFunc
}{
Default: &loadDefaultDependencies,
WithVariables: &loadVariables,
Expand All @@ -45,6 +46,7 @@ var LoadOptions = struct {
WithClearKeys: &loadClearKeys,
WithDeploymentStrategies: &loadDeploymentStrategies,
WithClearDeploymentStrategies: &loadDeploymentStrategiesWithClearPassword,
WithVulnerabilities: &loadVulnerabilities,
}

// LoadOldApplicationWorkflowToClean load application to clean
Expand Down
9 changes: 9 additions & 0 deletions engine/api/application/dao_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ var (
return nil
}

loadVulnerabilities = func(db gorp.SqlExecutor, store cache.Store, app *sdk.Application, u *sdk.User) error {
var err error
app.Vulnerabilities, err = LoadVulnerabilities(db, app.ID)
if err != nil && err != sql.ErrNoRows {
return sdk.WrapError(err, "application.loadVulnerabilities> Unable to load vulnerabilities")
}
return nil
}

loadDeploymentStrategiesWithClearPassword = func(db gorp.SqlExecutor, store cache.Store, app *sdk.Application, u *sdk.User) error {
var err error
app.DeploymentStrategies, err = LoadDeploymentStrategies(db, app.ID, true)
Expand Down
9 changes: 4 additions & 5 deletions engine/api/workflow/dao_node_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/go-gorp/gorp"
"github.com/ovh/venom"

"github.com/ovh/cds/engine/api/application"
"github.com/ovh/cds/engine/api/cache"
"github.com/ovh/cds/engine/api/database/gorpmapping"
"github.com/ovh/cds/engine/api/repositoriesmanager"
Expand Down Expand Up @@ -94,11 +93,11 @@ func LoadNodeRun(db gorp.SqlExecutor, projectkey, workflowname string, number, i
r.Coverage = cov
}
if loadOpts.WithVulnerabilities {
vulns, errV := application.LoadVulnerabilitiesByRun(db, r.ID)
if errV != nil && errV != sdk.ErrNotFound {
return nil, sdk.WrapError(errV, "LoadNodeRun>Error loading vulnerabilities for run %d", r.ID)
vuln, errV := loadVulnerabilityReport(db, r.ID)
if errV != nil {
return nil, sdk.WrapError(errV, "LoadNodeRun>Error vulnerability report coverage for run %d", r.ID)
}
r.Vulnerabilities = vulns
r.VulnerabilitiesReport = vuln
}
return r, nil

Expand Down
216 changes: 216 additions & 0 deletions engine/api/workflow/dao_node_run_vulnerability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package workflow

import (
"database/sql"

"github.com/go-gorp/gorp"

"github.com/ovh/cds/engine/api/application"
"github.com/ovh/cds/engine/api/cache"
"github.com/ovh/cds/engine/api/database/gorpmapping"
"github.com/ovh/cds/engine/api/repositoriesmanager"
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/log"
)

// HandleVulnerabilityReport calculate vulnerability trend and save report
func HandleVulnerabilityReport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, nr *sdk.WorkflowNodeRun, workerReport sdk.VulnerabilityWorkerReport) error {
var defaultBranch string
// Get default branch
if nr.VCSServer != "" {
// Get vcs info to known if we are on the default branch or not
projectVCSServer := repositoriesmanager.GetProjectVCSServer(proj, nr.VCSServer)
client, erra := repositoriesmanager.AuthorizedClient(db, cache, projectVCSServer)
if erra != nil {
return sdk.WrapError(sdk.ErrNoReposManagerClientAuth, "HandleVulnerabilityReport> Cannot get repo client %s : %v", nr.VCSServer, erra)
}

branches, errB := client.Branches(nr.VCSRepository)
if errB != nil {
return sdk.WrapError(errB, "HandleVulnerabilityReport> Cannot list branches for %s/%s", nr.VCSServer, nr.VCSRepository)
}
for _, b := range branches {
if b.Default {
defaultBranch = b.DisplayID
break
}
}
}

// Get report on the current node run if exist
currentNodeRunReport, err := loadVulnerabilityReport(db, nr.ID)
if err != nil && err != sdk.ErrNotFound {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to load vulnerability report")
}

if err != nil && err == sdk.ErrNotFound {
if err := createNewVulnerabilityReport(db, cache, proj, nr, workerReport, defaultBranch); err != nil {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to create no vulnerability report")
}
return nil
}

currentNodeRunReport.Report.Vulnerabilities = append(currentNodeRunReport.Report.Vulnerabilities, workerReport.Vulnerabilities...)
if currentNodeRunReport.Report.Summary == nil {
currentNodeRunReport.Report.Summary = workerReport.Summary
} else if workerReport.Summary != nil {
for k, v := range workerReport.Summary {
count, ok := currentNodeRunReport.Report.Summary[k]
if !ok {
currentNodeRunReport.Report.Summary[k] = v
} else {
currentNodeRunReport.Report.Summary[k] = count + v
}
}
}

// Update report
dbReport := dbNodeRunVulenrabilitiesReport(currentNodeRunReport)
if err := dbReport.PostInsert(db); err != nil {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to insert report")
}

// If we are on default branch, save report on application
if defaultBranch != "" && defaultBranch == nr.VCSBranch {
// Save vulnerabilities
if err := application.InsertVulnerabilities(db, currentNodeRunReport.Report.Vulnerabilities, nr.ApplicationID); err != nil {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to insert vulnerability")
}
}

return nil
}

func createNewVulnerabilityReport(db gorp.SqlExecutor, cache cache.Store, proj *sdk.Project, nr *sdk.WorkflowNodeRun, workerReport sdk.VulnerabilityWorkerReport, defaultBranch string) error {
// Build current report
nodeRunReport := sdk.WorkflowNodeRunVulnerabilityReport{
WorkflowID: nr.WorkflowID,
ApplicationID: nr.ApplicationID,
Branch: nr.VCSBranch,
Num: nr.Number,
WorkflowNodeRunID: nr.ID,
WorkflowRunID: nr.WorkflowRunID,
Report: sdk.WorkflowNodeRunVulnerability{
Vulnerabilities: workerReport.Vulnerabilities,
Summary: workerReport.Summary,
},
}

// Get summary from previous run
previousRunReport, err := loadPreviousRunVulnerabilityReport(db, nr)
if err != nil && err != sdk.ErrNotFound {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to get previous vulnerability report")
}
nodeRunReport.Report.PreviousRunSummary = previousRunReport

// Get summary from default branch
if defaultBranch != "" && defaultBranch != nr.VCSBranch {
defaultBranchReport, err := loadLatestRunVulnerabilityReport(db, nr, defaultBranch)
if err != nil && err != sdk.ErrNotFound {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to get default branch vulnerability report")
}
nodeRunReport.Report.DefaultBranchSummary = defaultBranchReport
}

if err := insertVulnerabilityReport(db, nodeRunReport); err != nil {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to save vulnerability report")
}

// If we are on default branch, save report on application
if defaultBranch != "" && defaultBranch == nr.VCSBranch {
if err := application.InsertVulnerabilities(db, nodeRunReport.Report.Vulnerabilities, nr.ApplicationID); err != nil {
return sdk.WrapError(err, "HandleVulnerabilityReport> Unable to update vulnerability")
}
}

return nil
}

func loadPreviousRunVulnerabilityReport(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun) (map[string]int64, error) {
var dbReport dbNodeRunVulenrabilitiesReport
query := `
SELECT * FROM workflow_node_run_vulnerability
WHERE application_id = $1 AND workflow_id = $2 AND branch = $3 AND workflow_number < $4
ORDER BY workflow_number DESC
LIMIT 1
`
if err := db.SelectOne(&dbReport, query, nr.ApplicationID, nr.WorkflowID, nr.VCSBranch, nr.Number); err != nil {
if err == sql.ErrNoRows {
return nil, sdk.ErrNotFound
}
return nil, sdk.WrapError(err, "loadPreviousRunVulnerabilityReport> Unable to load previous report")
}
return dbReport.Report.Summary, nil
}

func loadLatestRunVulnerabilityReport(db gorp.SqlExecutor, nr *sdk.WorkflowNodeRun, branch string) (map[string]int64, error) {
var dbReport dbNodeRunVulenrabilitiesReport
query := `
SELECT * FROM workflow_node_run_vulnerability
WHERE application_id = $1 AND workflow_id = $2 AND branch = $3
ORDER BY workflow_number DESC, workflow_node_run_id DESC
LIMIT 1
`
log.Warning("%d %d %s", nr.ApplicationID, nr.WorkflowID, branch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.Warning??

if err := db.SelectOne(&dbReport, query, nr.ApplicationID, nr.WorkflowID, branch); err != nil {
if err == sql.ErrNoRows {
return nil, sdk.ErrNotFound
}
return nil, sdk.WrapError(err, "loadPreviousRunVulnerabilityReport> Unable to load previous report")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/loadPreviousRunVulnerabilityReport/loadLatestRunVulnerabilityReport

}
log.Warning("%+v", dbReport.Report.Summary)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.Warning??

return dbReport.Report.Summary, nil
}

func insertVulnerabilityReport(db gorp.SqlExecutor, report sdk.WorkflowNodeRunVulnerabilityReport) error {
dbReport := dbNodeRunVulenrabilitiesReport(report)
if err := db.Insert(&dbReport); err != nil {
return sdk.WrapError(err, "InsertVulnerabilityReport> Unable to insert report")
}
return nil
}

// PostGet is a db hook
func (d *dbNodeRunVulenrabilitiesReport) PostGet(db gorp.SqlExecutor) error {
var reportS sql.NullString
query := "SELECT report from workflow_node_run_vulnerability WHERE id = $1"
if err := db.QueryRow(query, d.ID).Scan(&reportS); err != nil {
return sdk.WrapError(err, "dbNodeRunVulenrabilitiesReport.PostGet> Unable to report")
}

var report sdk.WorkflowNodeRunVulnerability
if err := gorpmapping.JSONNullString(reportS, &report); err != nil {
return sdk.WrapError(err, "dbNodeRunVulenrabilitiesReport.PostGet> Unable to unmarshal report")
}

d.Report = report
return nil
}

// PostInsert is a db hook
func (d *dbNodeRunVulenrabilitiesReport) PostInsert(db gorp.SqlExecutor) error {
report, err := gorpmapping.JSONToNullString(d.Report)
if err != nil {
return sdk.WrapError(err, "dbNodeRunVulenrabilitiesReport.PostInsert> Unable to marshal report")
}
query := "UPDATE workflow_node_run_vulnerability set report=$1 WHERE id=$2"
if _, err := db.Exec(query, report, d.ID); err != nil {
return sdk.WrapError(err, "dbNodeRunVulenrabilitiesReport.PostInsert> Unable to insert report")
}
return nil
}

func loadVulnerabilityReport(db gorp.SqlExecutor, nodeRunID int64) (sdk.WorkflowNodeRunVulnerabilityReport, error) {
var dbReport dbNodeRunVulenrabilitiesReport
query := `
SELECT * FROM workflow_node_run_vulnerability
WHERE workflow_node_run_id = $1
`
if err := db.SelectOne(&dbReport, query, nodeRunID); err != nil {
if err == sql.ErrNoRows {
return sdk.WorkflowNodeRunVulnerabilityReport{}, sdk.ErrNotFound
}
return sdk.WorkflowNodeRunVulnerabilityReport{}, sdk.WrapError(err, "loadPreviousRunVulnerabilityReport> Unable to load previous report")
}
return sdk.WorkflowNodeRunVulnerabilityReport(dbReport), nil
}
3 changes: 3 additions & 0 deletions engine/api/workflow/gorp_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type Run sdk.WorkflowRun
// Coverage is a gorp wrapper around sdk.WorkflowNodeRunCoverage
type Coverage sdk.WorkflowNodeRunCoverage

type dbNodeRunVulenrabilitiesReport sdk.WorkflowNodeRunVulnerabilityReport

// NodeRun is a gorp wrapper around sdk.WorkflowNodeRun
type NodeRun struct {
WorkflowID sql.NullInt64 `db:"workflow_id"`
Expand Down Expand Up @@ -201,4 +203,5 @@ func init() {
gorpmapping.Register(gorpmapping.New(Notification{}, "workflow_notification", true, "id"))
gorpmapping.Register(gorpmapping.New(auditWorkflow{}, "workflow_audit", true, "id"))
gorpmapping.Register(gorpmapping.New(Coverage{}, "workflow_node_run_coverage", false, "workflow_id", "workflow_run_id", "workflow_node_run_id", "repository", "branch"))
gorpmapping.Register(gorpmapping.New(dbNodeRunVulenrabilitiesReport{}, "workflow_node_run_vulnerability", true, "id"))
}
Loading