From a43f46465ad228b914606842d81559b93da19648 Mon Sep 17 00:00:00 2001 From: Sven Merk <33895725+nevskrem@users.noreply.github.com> Date: Tue, 15 Jun 2021 14:53:42 +0200 Subject: [PATCH] feat(fortifyExecuteScan): HTML report for Fortify (#2879) * Tune test * Fix report implementation * Fix tests * Fix values * Fix code and test * Report writing fix * Commit generated sources * Update cmd/fortifyExecuteScan.go Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * Externalize report generation * Fix fmt * Fix fmt 2 Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> --- cmd/fortifyExecuteScan.go | 67 ++++++++++++++----- cmd/fortifyExecuteScan_test.go | 18 +++-- pkg/fortify/fortify.go | 3 + pkg/fortify/fortify_test.go | 58 ++++++++-------- pkg/fortify/reporting.go | 117 +++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 pkg/fortify/reporting.go diff --git a/cmd/fortifyExecuteScan.go b/cmd/fortifyExecuteScan.go index 750e285a9..e207e6061 100644 --- a/cmd/fortifyExecuteScan.go +++ b/cmd/fortifyExecuteScan.go @@ -174,7 +174,9 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils if config.VerifyOnly { log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) - return reports, verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus) + err, paths := verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus) + reports = append(reports, paths...) + return reports, err } log.Entry().Infof("Scanning and uploading to project %v with version %v and projectVersionId %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID) @@ -196,10 +198,12 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils } } - triggerFortifyScan(config, utils, buildID, buildLabel, fortifyProjectName) - + err = triggerFortifyScan(config, utils, buildID, buildLabel, fortifyProjectName) reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/fortify-scan.*", config.ModulePath)}) reports = append(reports, piperutils.Path{Target: fmt.Sprintf("%vtarget/*.fpr", config.ModulePath)}) + if err != nil { + return reports, errors.Wrapf(err, "failed to scan project") + } var message string if config.UploadResults { @@ -228,7 +232,9 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils return reports, err } - return reports, verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus) + err, paths := verifyFFProjectCompliance(config, sys, project, projectVersion, filterSet, influx, auditStatus) + reports = append(reports, paths...) + return reports, err } func classifyErrorOnLookup(err error) { @@ -237,7 +243,8 @@ func classifyErrorOnLookup(err error) { } } -func verifyFFProjectCompliance(config fortifyExecuteScanOptions, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) error { +func verifyFFProjectCompliance(config fortifyExecuteScanOptions, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (error, []piperutils.Path) { + reports := []piperutils.Path{} // Generate report if config.Reporting { resultURL := []byte(fmt.Sprintf("https://fortify.tools.sap/ssc/html/ssc/version/%v/fix/null/", projectVersion.ID)) @@ -245,7 +252,7 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, sys fortify.Sys data, err := generateAndDownloadQGateReport(config, sys, project, projectVersion) if err != nil { - return err + return err, reports } ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0700) } @@ -253,43 +260,67 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, sys fortify.Sys // Perform audit compliance checks issueFilterSelectorSet, err := sys.GetIssueFilterSelectorOfProjectVersionByName(projectVersion.ID, []string{"Analysis", "Folder", "Category"}, nil) if err != nil { - return errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID) + return errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID), reports } log.Entry().Debugf("initial filter selector set: %v", issueFilterSelectorSet) - numberOfViolations, err := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) + numberOfViolations, issueGroups, err := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) if err != nil { - return errors.Wrap(err, "failed to analyze unaudited issues") + return errors.Wrap(err, "failed to analyze unaudited issues"), reports } - numberOfViolations += analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) + numberOfSuspiciousExplotable, issueGroupsSuspiciousExploitable := analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus) + numberOfViolations += numberOfSuspiciousExplotable + issueGroups = append(issueGroups, issueGroupsSuspiciousExploitable...) log.Entry().Infof("Counted %v violations, details: %v", numberOfViolations, auditStatus) influx.fortify_data.fields.projectName = *project.Name influx.fortify_data.fields.projectVersion = *projectVersion.Name influx.fortify_data.fields.violations = numberOfViolations + + scanReport := fortify.CreateCustomReport(prepareReportData(influx), issueGroups) + paths, err := fortify.WriteCustomReports(scanReport, influx.fortify_data.fields.projectName, influx.fortify_data.fields.projectVersion) + reports = append(reports, paths...) if numberOfViolations > 0 { log.SetErrorCategory(log.ErrorCompliance) - return errors.New("fortify scan failed, the project is not compliant. For details check the archived report") + return errors.New("fortify scan failed, the project is not compliant. For details check the archived report"), reports } - return nil + return nil, reports } -func analyseUnauditedIssues(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (int, error) { +func prepareReportData(influx *fortifyExecuteScanInflux) fortify.FortifyReportData { + input := influx.fortify_data.fields + output := fortify.FortifyReportData{} + output.ProjectName = input.projectName + output.ProjectVersion = input.projectVersion + output.AuditAllAudited = input.auditAllAudited + output.AuditAllTotal = input.auditAllTotal + output.CorporateAudited = input.corporateAudited + output.CorporateTotal = input.corporateTotal + output.SpotChecksAudited = input.spotChecksAudited + output.SpotChecksGap = input.spotChecksGap + output.SpotChecksTotal = input.spotChecksTotal + output.Exploitable = input.exploitable + output.Suppressed = input.suppressed + output.Suspicious = input.suspicious + return output +} + +func analyseUnauditedIssues(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (int, []*models.ProjectVersionIssueGroup, error) { log.Entry().Info("Analyzing unaudited issues") reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Folder"}, nil) fetchedIssueGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) if err != nil { - return 0, errors.Wrapf(err, "failed to fetch project version issue groups with filter set %v and selector %v for project version ID %v", filterSet, issueFilterSelectorSet, projectVersion.ID) + return 0, fetchedIssueGroups, errors.Wrapf(err, "failed to fetch project version issue groups with filter set %v and selector %v for project version ID %v", filterSet, issueFilterSelectorSet, projectVersion.ID) } overallViolations := 0 for _, issueGroup := range fetchedIssueGroups { issueDelta, err := getIssueDeltaFor(config, sys, issueGroup, projectVersion.ID, filterSet, issueFilterSelectorSet, influx, auditStatus) if err != nil { - return overallViolations, errors.Wrap(err, "failed to get issue delata") + return overallViolations, fetchedIssueGroups, errors.Wrap(err, "failed to get issue delta") } overallViolations += issueDelta } - return overallViolations, nil + return overallViolations, fetchedIssueGroups, nil } func getIssueDeltaFor(config fortifyExecuteScanOptions, sys fortify.System, issueGroup *models.ProjectVersionIssueGroup, projectVersionID int64, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (int, error) { @@ -383,7 +414,7 @@ func getSpotIssueCount(config fortifyExecuteScanOptions, sys fortify.System, spo return overallDelta } -func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) int { +func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify.System, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, issueFilterSelectorSet *models.IssueFilterSelectorSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (int, []*models.ProjectVersionIssueGroup) { log.Entry().Info("Analyzing suspicious and exploitable issues") reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Analysis"}, []string{}) fetchedGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet) @@ -418,7 +449,7 @@ func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify. influx.fortify_data.fields.exploitable = exploitableCount influx.fortify_data.fields.suppressed = int(suppressedCount) - return result + return result, fetchedGroups } func logIssueURL(config fortifyExecuteScanOptions, projectVersionID int64, folderSelector, analysisSelector *models.IssueFilterSelector) { diff --git a/cmd/fortifyExecuteScan_test.go b/cmd/fortifyExecuteScan_test.go index 7e4472bc1..4a38dea4b 100644 --- a/cmd/fortifyExecuteScan_test.go +++ b/cmd/fortifyExecuteScan_test.go @@ -217,6 +217,8 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64 }, nil } if issueFilterSelectorSet != nil && issueFilterSelectorSet.FilterBySet != nil && len(issueFilterSelectorSet.FilterBySet) > 0 && issueFilterSelectorSet.FilterBySet[0].GUID == "3" { + groupName := "Suspicious" + groupName2 := "Exploitable" group := "3" total := int32(4) audited := int32(0) @@ -224,8 +226,8 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64 total2 := int32(5) audited2 := int32(0) return []*models.ProjectVersionIssueGroup{ - {ID: &group, TotalCount: &total, AuditedCount: &audited}, - {ID: &group2, TotalCount: &total2, AuditedCount: &audited2}, + {ID: &group, CleanName: &groupName, TotalCount: &total, AuditedCount: &audited}, + {ID: &group2, CleanName: &groupName2, TotalCount: &total2, AuditedCount: &audited2}, }, nil } group := "Audit All" @@ -238,9 +240,9 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64 total3 := int32(5) audited3 := int32(4) return []*models.ProjectVersionIssueGroup{ - {ID: &group, TotalCount: &total, AuditedCount: &audited}, - {ID: &group2, TotalCount: &total2, AuditedCount: &audited2}, - {ID: &group3, TotalCount: &total3, AuditedCount: &audited3}, + {ID: &group, CleanName: &group, TotalCount: &total, AuditedCount: &audited}, + {ID: &group2, CleanName: &group2, TotalCount: &total2, AuditedCount: &audited2}, + {ID: &group3, CleanName: &group3, TotalCount: &total3, AuditedCount: &audited3}, }, nil } func (f *fortifyMock) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet { @@ -432,8 +434,9 @@ func TestAnalyseSuspiciousExploitable(t *testing.T) { }, }, } - issues := analyseSuspiciousExploitable(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) + issues, groups := analyseSuspiciousExploitable(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) assert.Equal(t, 9, issues) + assert.Equal(t, 2, len(groups)) assert.Equal(t, 4, influx.fortify_data.fields.suspicious) assert.Equal(t, 5, influx.fortify_data.fields.exploitable) @@ -481,9 +484,10 @@ func TestAnalyseUnauditedIssues(t *testing.T) { }, }, } - issues, err := analyseUnauditedIssues(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) + issues, groups, err := analyseUnauditedIssues(config, &ff, &projectVersion, &models.FilterSet{}, &selectorSet, &influx, auditStatus) assert.NoError(t, err) assert.Equal(t, 13, issues) + assert.Equal(t, 3, len(groups)) assert.Equal(t, 15, influx.fortify_data.fields.auditAllTotal) assert.Equal(t, 12, influx.fortify_data.fields.auditAllAudited) diff --git a/pkg/fortify/fortify.go b/pkg/fortify/fortify.go index 5da5c55d0..91c6586c8 100644 --- a/pkg/fortify/fortify.go +++ b/pkg/fortify/fortify.go @@ -37,6 +37,9 @@ import ( "github.com/sirupsen/logrus" ) +// ReportsDirectory defines the subfolder for the Fortify reports which are generated +const ReportsDirectory = "fortify" + // System is the interface abstraction of a specific SystemInstance type System interface { GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) diff --git a/pkg/fortify/fortify_test.go b/pkg/fortify/fortify_test.go index 1f0dad5bd..3388dd1d0 100644 --- a/pkg/fortify/fortify_test.go +++ b/pkg/fortify/fortify_test.go @@ -126,7 +126,7 @@ func TestGetProjectByName(t *testing.T) { "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -134,8 +134,8 @@ func TestGetProjectByName(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}}}`)) return } if req.URL.Path == "/projectVersions/10172" { @@ -195,7 +195,7 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -203,8 +203,8 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) return } if req.URL.Path == "/projects/777/versions" { @@ -227,7 +227,7 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -235,8 +235,8 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}}}`)) return } if req.URL.Path == "/projectVersions/0" { @@ -247,7 +247,7 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "project":{"id":815,"name":"autocreate","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -255,8 +255,8 @@ func TestGetProjectVersionDetailsByProjectIDAndVersionName(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}, - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/815/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/815/versions?start=0"}}}`)) return } }) @@ -295,7 +295,7 @@ func TestGetProjectVersionAttributesByProjectVersionID(t *testing.T) { header := rw.Header() header.Add("Content-type", "application/json") rw.Write([]byte( - `{"data": [{"_href": "https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, + `{"data": [{"_href": "https://fortify/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, "values": null,"guid": "gdgfdgfdgfdgfd","id": 4712,"value": "abcd"}],"count": 8,"responseCode": 200}`)) return } @@ -433,7 +433,7 @@ func TestProjectVersionCopyFromPartial(t *testing.T) { "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -441,8 +441,8 @@ func TestProjectVersionCopyFromPartial(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) return } }) @@ -472,7 +472,7 @@ func TestProjectVersionCopyCurrentState(t *testing.T) { "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -480,8 +480,8 @@ func TestProjectVersionCopyCurrentState(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) return } }) @@ -1102,7 +1102,7 @@ func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { header := rw.Header() header.Add("Content-type", "application/json") rw.Write([]byte( - `{"data": [{"_href": "https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, + `{"data": [{"_href": "https://fortify/ssc/api/v1/projectVersions/4711/attributes/4712","attributeDefinitionId": 31, "values": null,"guid": "gdgfdgfdgfdgfd","id": 4712,"value": "abcd"}],"count": 8,"responseCode": 200}`)) return } @@ -1126,7 +1126,7 @@ func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -1134,8 +1134,8 @@ func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) return } if req.URL.Path == "/projectVersions/10172" { @@ -1152,7 +1152,7 @@ func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { "project":{"id":4711,"name":"python-test","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"0","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -1160,8 +1160,8 @@ func TestLookupOrCreateProjectVersionDetailsForPullRequest(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) return } if req.URL.Path == "/projectVersions/10172/authEntities" { @@ -1234,7 +1234,7 @@ func TestMergeProjectVersionStateOfPRIntoMaster(t *testing.T) { "project":{"id":4711,"name":"product.some.com","description":"","creationDate":"2018-12-03T06:29:38.197+0000","createdBy":"someUser", "issueTemplateId":"dasdasdasdsadasdasdasdasdas"},"sourceBasePath":null,"mode":"BASIC","masterAttrGuid":"sddasdasda","obfuscatedId":null, "id":10172,"customTagValuesAutoApply":null,"issueTemplateId":"dasdasdasdsadasdasdasdasdas","loadProperties":null,"predictionPolicy":null, - "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify.mo.sap.corp/ssc/api/v1/projectVersions/10172", + "bugTrackerPluginId":null,"owner":"admin","_href":"https://fortify/ssc/api/v1/projectVersions/10172", "committed":true,"bugTrackerEnabled":false,"active":true,"snapshotOutOfDate":false,"issueTemplateModifiedTime":1578411924701, "securityGroup":null,"creationDate":"2018-02-09T16:59:41.297+0000","refreshRequired":false,"issueTemplateName":"someTemplate", "migrationVersion":null,"createdBy":"admin","name":"PR-815","siteId":null,"staleIssueTemplate":false,"autoPredict":null, @@ -1242,8 +1242,8 @@ func TestMergeProjectVersionStateOfPRIntoMaster(t *testing.T) { "lastFprUploadDate":"2018-02-09T16:59:53.497+0000","extraMessage":null,"analysisUploadEnabled":true,"batchBugSubmissionExists":false, "hasCustomIssues":false,"metricEvaluationDate":"2018-03-10T00:02:45.553+0000","deltaPeriod":7,"issueCountDelta":0,"percentAuditedDelta":0.0, "criticalPriorityIssueCountDelta":0,"percentCriticalPriorityIssuesAuditedDelta":0.0},"assignedIssuesCount":0,"status":null}], - "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}, - "first":{"href":"https://fortify.mo.sap.corp/ssc/api/v1/projects/4711/versions?start=0"}}}`)) + "count":1,"responseCode":200,"links":{"last":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}, + "first":{"href":"https://fortify/ssc/api/v1/projects/4711/versions?start=0"}}}`)) getPRProjectVersionCalled = true return } diff --git a/pkg/fortify/reporting.go b/pkg/fortify/reporting.go new file mode 100644 index 000000000..a9b77ae96 --- /dev/null +++ b/pkg/fortify/reporting.go @@ -0,0 +1,117 @@ +package fortify + +import ( + "crypto/sha1" + "fmt" + "path/filepath" + "strings" + "time" + + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/piperutils" + "github.com/SAP/jenkins-library/pkg/reporting" + "github.com/piper-validation/fortify-client-go/models" + + "github.com/pkg/errors" +) + +type FortifyReportData struct { + ProjectName string + ProjectVersion string + Violations int + CorporateTotal int + CorporateAudited int + AuditAllTotal int + AuditAllAudited int + SpotChecksTotal int + SpotChecksAudited int + SpotChecksGap int + Suspicious int + Exploitable int + Suppressed int +} + +func CreateCustomReport(data FortifyReportData, issueGroups []*models.ProjectVersionIssueGroup) reporting.ScanReport { + + scanReport := reporting.ScanReport{ + Title: "Fortify SAST Report", + Subheaders: []reporting.Subheader{ + {Description: "Fortify project name", Details: data.ProjectName}, + {Description: "Fortify project version", Details: data.ProjectVersion}, + }, + Overview: []reporting.OverviewRow{ + {Description: "Total number of compliance violations", Details: fmt.Sprint(data.Violations)}, + {Description: "Total number of issues suppressed", Details: fmt.Sprint(data.Suppressed)}, + {Description: "Unaudited corporate issues", Details: fmt.Sprint(data.CorporateTotal - data.CorporateAudited)}, + {Description: "Unaudited audit all issues", Details: fmt.Sprint(data.AuditAllTotal - data.AuditAllAudited)}, + {Description: "Unaudited spot check issues", Details: fmt.Sprint(data.SpotChecksTotal - data.SpotChecksAudited)}, + {Description: "Total number of issues", Details: fmt.Sprint(data.Suspicious)}, + {Description: "Total number of exploitable issues", Details: fmt.Sprint(data.Exploitable)}, + }, + ReportTime: time.Now(), + } + + detailTable := reporting.ScanDetailTable{ + NoRowsMessage: "No findings detected", + Headers: []string{ + "Issue group", + "Total count", + "Audited count", + }, + WithCounter: true, + CounterHeader: "Entry #", + } + + for _, group := range issueGroups { + row := reporting.ScanRow{} + row.AddColumn(fmt.Sprint(*group.CleanName), 0) + row.AddColumn(fmt.Sprint(*group.TotalCount), 0) + row.AddColumn(fmt.Sprint(*group.AuditedCount), 0) + + detailTable.Rows = append(detailTable.Rows, row) + } + scanReport.DetailTable = detailTable + + return scanReport +} + +func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectVersion string) ([]piperutils.Path, error) { + utils := piperutils.Files{} + reportPaths := []piperutils.Path{} + + // ignore templating errors since template is in our hands and issues will be detected with the automated tests + htmlReport, _ := scanReport.ToHTML() + htmlReportPath := filepath.Join(ReportsDirectory, "piper_fortify_report.html") + // Ensure reporting directory exists + if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil { + return reportPaths, errors.Wrapf(err, "failed to create report directory") + } + if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return reportPaths, errors.Wrapf(err, "failed to write html report") + } + reportPaths = append(reportPaths, piperutils.Path{Name: "Fortify Report", Target: htmlReportPath}) + + // JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub + // ignore JSON errors since structure is in our hands + jsonReport, _ := scanReport.ToJSON() + if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists { + err := utils.MkdirAll(reporting.StepReportDirectory, 0777) + if err != nil { + return reportPaths, errors.Wrap(err, "failed to create reporting directory") + } + } + if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("fortifyExecuteScan_sast_%v.json", reportShaFortify([]string{projectName, projectVersion}))), jsonReport, 0666); err != nil { + return reportPaths, errors.Wrapf(err, "failed to write json report") + } + // we do not add the json report to the overall list of reports for now, + // since it is just an intermediary report used as input for later + // and there does not seem to be real benefit in archiving it. + + return reportPaths, nil +} + +func reportShaFortify(parts []string) string { + reportShaData := []byte(strings.Join(parts, ",")) + return fmt.Sprintf("%x", sha1.Sum(reportShaData)) +}