From 763dfb172048be0eaa49e149cb373c2e1b44be38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Fri, 4 Jun 2021 16:14:33 +0100 Subject: [PATCH 1/9] feat(report): pdf report --- go.mod | 1 + go.sum | 11 +++ internal/console/helpers/helpers.go | 1 + internal/console/scan.go | 2 +- pkg/report/commons.go | 44 +++++++--- pkg/report/html.go | 18 ----- pkg/report/pdf.go | 119 ++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 32 deletions(-) create mode 100644 pkg/report/pdf.go diff --git a/go.mod b/go.mod index 40494edfc34..a2faf76df81 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.10.0 + github.com/johnfercher/maroto v0.31.0 github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 github.com/mitchellh/go-wordwrap v1.0.1 // indirect diff --git a/go.sum b/go.sum index 3210bc523f2..c67e7f465c1 100644 --- a/go.sum +++ b/go.sum @@ -224,6 +224,8 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= @@ -539,6 +541,7 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gojp/goreportcard v0.0.0-20191001233754-41818f5fd295/go.mod h1:/DA2Xpp+OaR3EHafQSnT9SKOfbG2NPQR/qp6Qr8AgIw= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -763,6 +766,8 @@ github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xl github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/johnfercher/maroto v0.31.0 h1:Ba3woJTMVlX257Bj/t0fyOuEddr77evNMpaA2YZUpAM= +github.com/johnfercher/maroto v0.31.0/go.mod h1:z/5eo/hH1g+01K4Mm0IVVbixHibtaNbZ9vHf+2H6fpM= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -784,6 +789,9 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.4.2 h1:3u2ojTwxPPu3ysIOc5iTwcECpvkFCAe2RJ/tQrvfLi0= +github.com/jung-kurt/gofpdf v1.4.2/go.mod h1:rZsO0wEsunjT/L9stF3fJjYbAHgqNYuQB4B8FWvBck0= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -1124,6 +1132,8 @@ github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvf github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 h1:nlG4Wa5+minh3S9LVFtNoY+GVRiudA2e3EVfcCi3RCA= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -1391,6 +1401,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/internal/console/helpers/helpers.go b/internal/console/helpers/helpers.go index 6f2e02c7afb..e4c4807ecd7 100644 --- a/internal/console/helpers/helpers.go +++ b/internal/console/helpers/helpers.go @@ -27,6 +27,7 @@ var reportGenerators = map[string]func(path, filename string, body interface{}) "sarif": report.PrintSarifReport, "html": report.PrintHTMLReport, "glsast": report.PrintGitlabSASTReport, + "pdf": report.PrintPdfReport, } // ProgressBar represents a Progress diff --git a/internal/console/scan.go b/internal/console/scan.go index 6033aa83bec..ce68de18bda 100644 --- a/internal/console/scan.go +++ b/internal/console/scan.go @@ -315,7 +315,7 @@ func initScanFlags(scanCmd *cobra.Command) { "", "directory path to store reports") scanCmd.Flags().StringSliceVar(&reportFormats, reportFormatsFlag, []string{"json"}, - "formats in which the results will be exported (all, json, sarif, html, glsast)", + "formats in which the results will be exported (all, json, sarif, html, glsast, pdf)", ) scanCmd.Flags().IntVar(&previewLines, previewLinesFlag, diff --git a/pkg/report/commons.go b/pkg/report/commons.go index 225cf911439..9fe227a2155 100644 --- a/pkg/report/commons.go +++ b/pkg/report/commons.go @@ -1,6 +1,7 @@ package report import ( + _ "embed" // used for embedding report static files "fmt" "html/template" "os" @@ -11,20 +12,37 @@ import ( "github.com/rs/zerolog/log" ) -var templateFuncs = template.FuncMap{ - "lower": strings.ToLower, - "sprintf": fmt.Sprintf, - "severity": getSeverities, - "getCurrentTime": getCurrentTime, - "trimSpaces": trimSpaces, -} +var ( + //go:embed template/html/report.tmpl + htmlTemplate string + //go:embed template/html/report.css + cssTemplate string + //go:embed template/html/report.js + jsTemplate string + //go:embed template/html/github.svg + githubSVG string + //go:embed template/html/info.svg + infoSVG string + //go:embed template/html/vulnerability_fill.svg + vulnerabilityFillSVG string + //go:embed template/html/vulnerability_out.svg + vulnerabilityOutSVG string -var stringsSeverity = map[string]model.Severity{ - "high": model.AllSeverities[0], - "medium": model.AllSeverities[1], - "low": model.AllSeverities[2], - "info": model.AllSeverities[3], -} + stringsSeverity = map[string]model.Severity{ + "high": model.AllSeverities[0], + "medium": model.AllSeverities[1], + "low": model.AllSeverities[2], + "info": model.AllSeverities[3], + } + + templateFuncs = template.FuncMap{ + "lower": strings.ToLower, + "sprintf": fmt.Sprintf, + "severity": getSeverities, + "getCurrentTime": getCurrentTime, + "trimSpaces": trimSpaces, + } +) func trimSpaces(value string) string { return strings.TrimPrefix(value, " ") diff --git a/pkg/report/html.go b/pkg/report/html.go index 076375f5301..7d0f9626ae7 100644 --- a/pkg/report/html.go +++ b/pkg/report/html.go @@ -2,7 +2,6 @@ package report import ( "bytes" - _ "embed" // used for embedding report static files "html/template" "os" "path/filepath" @@ -15,23 +14,6 @@ import ( minifyJS "github.com/tdewolff/minify/v2/js" ) -var ( - //go:embed template/html/report.tmpl - htmlTemplate string - //go:embed template/html/report.css - cssTemplate string - //go:embed template/html/report.js - jsTemplate string - //go:embed template/html/github.svg - githubSVG string - //go:embed template/html/info.svg - infoSVG string - //go:embed template/html/vulnerability_fill.svg - vulnerabilityFillSVG string - //go:embed template/html/vulnerability_out.svg - vulnerabilityOutSVG string -) - const ( textHTML = "text/html" ) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go new file mode 100644 index 00000000000..9cf80f0e2c7 --- /dev/null +++ b/pkg/report/pdf.go @@ -0,0 +1,119 @@ +package report + +import ( + "fmt" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/johnfercher/maroto/pkg/color" + "github.com/johnfercher/maroto/pkg/consts" + "github.com/johnfercher/maroto/pkg/pdf" + "github.com/johnfercher/maroto/pkg/props" +) + +func getHeader() []string { + return []string{"QueryName", "Severity", "Platform", "Category"} +} + +func extractContent(queries []model.VulnerableQuery) [][]string { + var contents [][]string + contents = make([][]string, 0) + for _, entry := range queries { + contents = append(contents, []string{entry.QueryName, string(entry.Severity), entry.Platform, entry.Category}) + } + return contents +} + +func PrintPdfReport(path, filename string, body interface{}) error { + grayColor := getGrayColor() + whiteColor := color.NewWhite() + header := getHeader() + summary := body.(*model.Summary) + contents := extractContent(summary.Queries) + + m := pdf.NewMaroto(consts.Portrait, consts.A4) + m.SetPageMargins(10, 15, 10) + + m.SetAliasNbPages("{nb}") + m.SetFirstPageNb(1) + + m.RegisterHeader(func() { + m.Row(20, func() { + m.Col(2, func() { + m.Text("HIGH", props.Text{ + Size: 8, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text("MEDIUM", props.Text{ + Size: 8, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text("LOW", props.Text{ + Size: 8, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text("INFO", props.Text{ + Size: 8, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text("TOTAL", props.Text{ + Size: 8, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.ColSpace(2) + }) + }) + + m.SetBackgroundColor(whiteColor) + + m.TableList( + header, + contents, + props.TableList{ + HeaderProp: props.TableListContent{ + Size: 9, + GridSizes: []uint{4, 2, 2, 3}, + }, + ContentProp: props.TableListContent{ + Size: 8, + GridSizes: []uint{4, 2, 2, 3}, + }, + Align: consts.Center, + AlternatedBackground: &grayColor, + HeaderContentSpace: 1, + Line: false, + }, + ) + + err := m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) + return err +} + +func getDarkGrayColor() color.Color { + return color.Color{ + Red: 55, + Green: 55, + Blue: 55, + } +} + +func getGrayColor() color.Color { + return color.Color{ + Red: 200, + Green: 200, + Blue: 200, + } +} From 586aa4417c66ef6e0f5d213cb06989dee2ed7025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Mon, 7 Jun 2021 15:50:35 +0100 Subject: [PATCH 2/9] progress on pdf report --- pkg/report/pdf.go | 184 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 135 insertions(+), 49 deletions(-) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index 9cf80f0e2c7..fbc81ed23df 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -2,6 +2,7 @@ package report import ( "fmt" + "path/filepath" "github.com/Checkmarx/kics/pkg/model" "github.com/johnfercher/maroto/pkg/color" @@ -10,106 +11,191 @@ import ( "github.com/johnfercher/maroto/pkg/props" ) -func getHeader() []string { - return []string{"QueryName", "Severity", "Platform", "Category"} +const ( + rowHeight = 20 + colWidthOne = 1 + colWidthTwo = 2 + textSize = 8 + pgMarginLeft = 10 + pgMarginTop = 15 + pgMarginRight = 10 +) + +var ( + grayColor = getGrayColor() +) + +func createFullReportTable(m pdf.Maroto, queries []model.VulnerableQuery) { + m.Row(rowHeight, func() { + m.Col(colWidthOne, func() { + m.Text("QueryName", props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { + m.Text("Severity", props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { + m.Text("Platform", props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { + m.Text("Category", props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + }) + createQueriesTable(m, queries) +} + +func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery) { + contents := make([][]string, 0) + for idx := range queries { + contents = createResultsTable(contents, &queries[idx]) + m.TableList( + []string{queries[idx].QueryName, string(queries[idx].Severity), queries[idx].Platform, queries[idx].Category}, + contents, + props.TableList{ + HeaderProp: props.TableListContent{ + Size: 10, + GridSizes: []uint{4, 2, 2, 3}, + }, + ContentProp: props.TableListContent{ + Size: textSize, + GridSizes: []uint{3, 1, 5, 2}, + }, + Align: consts.Center, + AlternatedBackground: &grayColor, + HeaderContentSpace: 1, + Line: false, + }, + ) + m.Line(1.0) + } } -func extractContent(queries []model.VulnerableQuery) [][]string { - var contents [][]string - contents = make([][]string, 0) - for _, entry := range queries { - contents = append(contents, []string{entry.QueryName, string(entry.Severity), entry.Platform, entry.Category}) +func createResultsTable(contents [][]string, query *model.VulnerableQuery) [][]string { + for idx := range query.Files { + fileLine := fmt.Sprintf("%s:%s", filepath.Base(query.Files[idx].FileName), fmt.Sprint(query.Files[idx].Line)) + contents = append(contents, []string{fileLine, "", query.Files[idx].SimilarityID, ""}) } + return contents } +// PrintPdfReport creates a report file on the PDF format func PrintPdfReport(path, filename string, body interface{}) error { - grayColor := getGrayColor() whiteColor := color.NewWhite() - header := getHeader() summary := body.(*model.Summary) - contents := extractContent(summary.Queries) + + highSeverityCount := fmt.Sprint(summary.SeverityCounters["HIGH"]) + mediumSeverityCount := fmt.Sprint(summary.SeverityCounters["MEDIUM"]) + lowSeverityCount := fmt.Sprint(summary.SeverityCounters["LOW"]) + infoSeverityCount := fmt.Sprint(summary.SeverityCounters["INFO"]) + totalCount := fmt.Sprint(summary.TotalCounter) m := pdf.NewMaroto(consts.Portrait, consts.A4) - m.SetPageMargins(10, 15, 10) + m.SetPageMargins(pgMarginLeft, pgMarginTop, pgMarginRight) m.SetAliasNbPages("{nb}") m.SetFirstPageNb(1) m.RegisterHeader(func() { - m.Row(20, func() { - m.Col(2, func() { + m.Row(rowHeight, func() { + m.Col(colWidthOne, func() { m.Text("HIGH", props.Text{ - Size: 8, + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { + m.Text(highSeverityCount, props.Text{ + Size: textSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colWidthOne, func() { m.Text("MEDIUM", props.Text{ - Size: 8, + Size: textSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colWidthOne, func() { + m.Text(mediumSeverityCount, props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { m.Text("LOW", props.Text{ - Size: 8, + Size: textSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colWidthOne, func() { + m.Text(lowSeverityCount, props.Text{ + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { m.Text("INFO", props.Text{ - Size: 8, + Size: textSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colWidthOne, func() { + m.Text(infoSeverityCount, props.Text{ + Size: textSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colWidthTwo, func() { m.Text("TOTAL", props.Text{ - Size: 8, + Size: textSize, Align: consts.Left, Extrapolate: false, }) }) - m.ColSpace(2) + m.Col(colWidthTwo, func() { + m.Text(totalCount, props.Text{ + Size: textSize, + Align: consts.Right, + Extrapolate: false, + }) + }) }) }) + m.Line(1.0) + m.SetBackgroundColor(whiteColor) - m.TableList( - header, - contents, - props.TableList{ - HeaderProp: props.TableListContent{ - Size: 9, - GridSizes: []uint{4, 2, 2, 3}, - }, - ContentProp: props.TableListContent{ - Size: 8, - GridSizes: []uint{4, 2, 2, 3}, - }, - Align: consts.Center, - AlternatedBackground: &grayColor, - HeaderContentSpace: 1, - Line: false, - }, - ) + createFullReportTable(m, summary.Queries) err := m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) return err } -func getDarkGrayColor() color.Color { - return color.Color{ - Red: 55, - Green: 55, - Blue: 55, - } -} - func getGrayColor() color.Color { return color.Color{ Red: 200, From 953ff484ff0d41b6a1cc1647d393470cd206fbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Tue, 8 Jun 2021 17:49:56 +0100 Subject: [PATCH 3/9] progress on pdf report --- pkg/report/assets/vuln | 1 + pkg/report/commons.go | 16 -- pkg/report/html.go | 18 ++ pkg/report/pdf.go | 498 ++++++++++++++++++++++++++++++----------- 4 files changed, 387 insertions(+), 146 deletions(-) create mode 100644 pkg/report/assets/vuln diff --git a/pkg/report/assets/vuln b/pkg/report/assets/vuln new file mode 100644 index 00000000000..7a201588606 --- /dev/null +++ b/pkg/report/assets/vuln @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAuIwAALiMBeKU/dgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAMBSURBVHic7Zw7axRRFIC/TaLBjSlFEStjZRuQIAELY2MhCFrYLYqCf0DRP6BWPhpJoyJpFEwhIogRRNAUMQoiFmIg4BtRIT6Cyu5a3A1uZDa758yduXd3zwenmeXsnPsxh5m5c2fAMAzDMLqd3tAF/Mda4ABwAhgA5oDfQSuKkGFgHFgAqnWxCFwHxoBCsOoiYBNwHHjFckGN4jVwGhgKUWwI1gD7gbtAhdYkJcVj4AgwmG/5+dCozdJGx7SptM3SRtu1qa82SxtRt2lWbdYxbZp3m7Vlm44C9wnbZmmiUqt/VDrwHmkC7pDeQfuefQq4+sekiRpZXYvJEmCyBJgsAX2hC6jjE/AtYfsgsC7nWhKJSdYx4ErC9hJwOddKGmBtKMBkCTBZAkyWAJMlwGQJMFkCTJYAkyXAZAkwWQI0sqreqwiDeBwaWb8UOTGyKE3QyPqpyImRH9IEzRRN0pyTD3YD6xO2D2e0v+8Z/e8ydhH+cZaP2CkduKYN5xU5MTIvTdA8++vHHcIxzbJK+YNbZShaVag9Gz5X5MXEMxTLL7UXpTPKvFhQ1a9tpWngsDK3ES+AdwnbNwJbPe9r2vP/rcgG/C8MKTXYV8nzfsokX6I0RduGH4AnytzQzAIfNYlpbqRvpMgNyaQ2MY2sq7hDup0oAxPa5DSy3gJTKfJDcAd4o01OO591PmV+3pwLXcAs4e/zWomnRLBacR/hRbQSe7MSIOUB4WWsFA+J4KhaYhvuTBNaSlKUyW5OTM0FwotJirNZDlpLEXhJeDn1MYebiomSEdwUTmhJ1VodI9kONz1HCS+qinvRqS0YJ6yoi9kP0R+9wDXCiJokvhflm9IP3CZfUbeA1XkMLgv6gEvkI2oCWJXPsLKjBzhDdq/cVYBTRHSF7oM9wFf8ilrA3Zt2JEPAPfyImgI251t+/hSAg7h5fI2k97iHGB3Vds0YAE4CX2hN0mf+faOmaykCh3CfF0iSNFP7vRiqwCViO5S34E4E24FHwE3c2/2GYRiGYUTBXy34jZrggyC2AAAAAElFTkSuQmCC diff --git a/pkg/report/commons.go b/pkg/report/commons.go index 9fe227a2155..bde741fa3cc 100644 --- a/pkg/report/commons.go +++ b/pkg/report/commons.go @@ -1,7 +1,6 @@ package report import ( - _ "embed" // used for embedding report static files "fmt" "html/template" "os" @@ -13,21 +12,6 @@ import ( ) var ( - //go:embed template/html/report.tmpl - htmlTemplate string - //go:embed template/html/report.css - cssTemplate string - //go:embed template/html/report.js - jsTemplate string - //go:embed template/html/github.svg - githubSVG string - //go:embed template/html/info.svg - infoSVG string - //go:embed template/html/vulnerability_fill.svg - vulnerabilityFillSVG string - //go:embed template/html/vulnerability_out.svg - vulnerabilityOutSVG string - stringsSeverity = map[string]model.Severity{ "high": model.AllSeverities[0], "medium": model.AllSeverities[1], diff --git a/pkg/report/html.go b/pkg/report/html.go index 7d0f9626ae7..076375f5301 100644 --- a/pkg/report/html.go +++ b/pkg/report/html.go @@ -2,6 +2,7 @@ package report import ( "bytes" + _ "embed" // used for embedding report static files "html/template" "os" "path/filepath" @@ -14,6 +15,23 @@ import ( minifyJS "github.com/tdewolff/minify/v2/js" ) +var ( + //go:embed template/html/report.tmpl + htmlTemplate string + //go:embed template/html/report.css + cssTemplate string + //go:embed template/html/report.js + jsTemplate string + //go:embed template/html/github.svg + githubSVG string + //go:embed template/html/info.svg + infoSVG string + //go:embed template/html/vulnerability_fill.svg + vulnerabilityFillSVG string + //go:embed template/html/vulnerability_out.svg + vulnerabilityOutSVG string +) + const ( textHTML = "text/html" ) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index fbc81ed23df..3704c74ab6b 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -1,201 +1,399 @@ package report import ( + _ "embed" // used for embedding report static files "fmt" + "os" "path/filepath" + "time" "github.com/Checkmarx/kics/pkg/model" "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/pkg/consts" "github.com/johnfercher/maroto/pkg/pdf" "github.com/johnfercher/maroto/pkg/props" + "github.com/rs/zerolog/log" ) const ( - rowHeight = 20 - colWidthOne = 1 - colWidthTwo = 2 - textSize = 8 - pgMarginLeft = 10 - pgMarginTop = 15 - pgMarginRight = 10 + defaultTextSize = 8 + pgMarginLeft = 10 + pgMarginTop = 15 + pgMarginRight = 10 ) var ( grayColor = getGrayColor() + //go:embed assets/vuln + vulnImageBase64 string ) -func createFullReportTable(m pdf.Maroto, queries []model.VulnerableQuery) { - m.Row(rowHeight, func() { - m.Col(colWidthOne, func() { - m.Text("QueryName", props.Text{ - Size: textSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colWidthOne, func() { - m.Text("Severity", props.Text{ - Size: textSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colWidthOne, func() { - m.Text("Platform", props.Text{ - Size: textSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colWidthOne, func() { - m.Text("Category", props.Text{ - Size: textSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - }) - createQueriesTable(m, queries) -} - -func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery) { - contents := make([][]string, 0) +func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath string) error { for idx := range queries { - contents = createResultsTable(contents, &queries[idx]) - m.TableList( - []string{queries[idx].QueryName, string(queries[idx].Severity), queries[idx].Platform, queries[idx].Category}, - contents, - props.TableList{ - HeaderProp: props.TableListContent{ - Size: 10, - GridSizes: []uint{4, 2, 2, 3}, - }, - ContentProp: props.TableListContent{ - Size: textSize, - GridSizes: []uint{3, 1, 5, 2}, - }, - Align: consts.Center, - AlternatedBackground: &grayColor, - HeaderContentSpace: 1, - Line: false, - }, - ) - m.Line(1.0) - } -} - -func createResultsTable(contents [][]string, query *model.VulnerableQuery) [][]string { - for idx := range query.Files { - fileLine := fmt.Sprintf("%s:%s", filepath.Base(query.Files[idx].FileName), fmt.Sprint(query.Files[idx].Line)) - contents = append(contents, []string{fileLine, "", query.Files[idx].SimilarityID, ""}) - } - - return contents -} - -// PrintPdfReport creates a report file on the PDF format -func PrintPdfReport(path, filename string, body interface{}) error { - whiteColor := color.NewWhite() - summary := body.(*model.Summary) - - highSeverityCount := fmt.Sprint(summary.SeverityCounters["HIGH"]) - mediumSeverityCount := fmt.Sprint(summary.SeverityCounters["MEDIUM"]) - lowSeverityCount := fmt.Sprint(summary.SeverityCounters["LOW"]) - infoSeverityCount := fmt.Sprint(summary.SeverityCounters["INFO"]) - totalCount := fmt.Sprint(summary.TotalCounter) - - m := pdf.NewMaroto(consts.Portrait, consts.A4) - m.SetPageMargins(pgMarginLeft, pgMarginTop, pgMarginRight) - - m.SetAliasNbPages("{nb}") - m.SetFirstPageNb(1) - - m.RegisterHeader(func() { - m.Row(rowHeight, func() { - m.Col(colWidthOne, func() { - m.Text("HIGH", props.Text{ - Size: textSize, + m.SetBackgroundColor(color.NewWhite()) + m.Row(8, func() { + m.Col(1, func() { + m.Base64Image(vulnImageBase64, consts.Png, props.Rect{ + Center: false, + Percent: 50, + Left: 2, + }) + }) + m.Col(9, func() { + m.Text(queries[idx].QueryName, props.Text{ + Size: 10, + Style: consts.Bold, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text(highSeverityCount, props.Text{ - Size: textSize, - Align: consts.Left, + m.Col(1, func() { + m.Text("Results", props.Text{ + Size: 8, + Style: consts.Bold, + Align: consts.Right, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text("MEDIUM", props.Text{ - Size: textSize, - Align: consts.Left, + m.Col(1, func() { + m.Text(fmt.Sprint(len(queries[idx].Files)), props.Text{ + Size: 8, + Style: consts.Bold, + Align: consts.Right, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text(mediumSeverityCount, props.Text{ - Size: textSize, + }) + m.Row(3, func() { + m.Col(2, func() { + m.Text("Severity", props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text("LOW", props.Text{ - Size: textSize, + m.Col(2, func() { + m.Text(string(queries[idx].Severity), props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text(lowSeverityCount, props.Text{ - Size: textSize, + }) + m.Row(3, func() { + m.Col(2, func() { + m.Text("Platform", props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text("INFO", props.Text{ - Size: textSize, + m.Col(2, func() { + m.Text(queries[idx].Platform, props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthOne, func() { - m.Text(infoSeverityCount, props.Text{ - Size: textSize, + }) + m.Row(5, func() { + m.Col(2, func() { + m.Text("Category", props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthTwo, func() { - m.Text("TOTAL", props.Text{ - Size: textSize, + m.Col(2, func() { + m.Text(queries[idx].Category, props.Text{ + Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(colWidthTwo, func() { - m.Text(totalCount, props.Text{ - Size: textSize, - Align: consts.Right, + }) + err := createResultsTable(m, &queries[idx], basePath) + if err != nil { + return err + } + } + return nil +} + +func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath string) error { + for idx := range query.Files { + if idx%2 == 0 { + m.SetBackgroundColor(grayColor) + } else { + m.SetBackgroundColor(color.NewWhite()) + } + relativePath, err := filepath.Rel(basePath, query.Files[idx].FileName) + if err != nil { + return err + } + fileLine := fmt.Sprintf("%s:%s", relativePath, fmt.Sprint(query.Files[idx].Line)) + m.Row(5, func() { + m.Col(12, func() { + m.Text(fileLine, props.Text{ + Size: defaultTextSize, + Align: consts.Left, Extrapolate: false, }) }) }) + } + m.Line(1.0) + return nil +} + +func createHeaderArea(m pdf.Maroto) { + m.SetBackgroundColor(getPurpleColor()) + m.Row(15, func() { + m.Col(6, func() { + m.Text(" KICS REPORT", props.Text{ + Size: 25, + Style: consts.Bold, + Align: consts.Left, + Extrapolate: false, + Color: color.NewWhite(), + }) + }) + m.ColSpace(6) + }) + m.SetBackgroundColor(color.NewWhite()) + m.Row(3, func() { + m.ColSpace(12) }) +} + +func createFooterArea(m pdf.Maroto) { + m.Row(5, func() { + m.Col(1, func() { + m.Text("http://kics.io") + }) + }) +} + +// PrintPdfReport creates a report file on the PDF format +func PrintPdfReport(path, filename string, body interface{}) error { + startTime := time.Now() + log.Info().Msg("Started generating pdf report") + + summary := body.(*model.Summary) + basePath, err := os.Getwd() + if err != nil { + return err + } + + m := pdf.NewMaroto(consts.Portrait, consts.A4) + m.SetPageMargins(pgMarginLeft, pgMarginTop, pgMarginRight) + + m.SetFirstPageNb(1) + m.SetAliasNbPages("{total}") + + m.RegisterHeader(func() { + createHeaderArea(m) + }) + m.RegisterFooter(func() { + createFooterArea(m) + }) + + m.SetBackgroundColor(color.NewWhite()) + + createFirstPageHeader(m, *summary) m.Line(1.0) - m.SetBackgroundColor(whiteColor) + createQueriesTable(m, summary.Queries, basePath) - createFullReportTable(m, summary.Queries) + err = m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) + log.Info().Msgf("Generate report duration: %v", time.Since(startTime)) - err := m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) return err } +func createDateArea(m pdf.Maroto, summary model.Summary) { + m.Row(4, func() { + m.Col(2, func() { + m.Text("START TIME", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text(summary.Start.Format("15:04:05, Jan 02 2006"), props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + }) + m.Row(6, func() { + m.Col(2, func() { + m.Text("END TIME", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(2, func() { + m.Text(summary.End.Format("15:04:05, Jan 02 2006"), props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + }) +} + +func createPlatformsArea(m pdf.Maroto, summary model.Summary) { + m.Row(4, func() { + m.Col(2, func() { + m.Text("PLATFORMS", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(10, func() { + m.Text(getPlatforms(summary.Queries), props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + }) +} + +func createSummaryArea(m pdf.Maroto, summary model.Summary) { + highSeverityCount := fmt.Sprint(summary.SeverityCounters["HIGH"]) + mediumSeverityCount := fmt.Sprint(summary.SeverityCounters["MEDIUM"]) + lowSeverityCount := fmt.Sprint(summary.SeverityCounters["LOW"]) + infoSeverityCount := fmt.Sprint(summary.SeverityCounters["INFO"]) + totalCount := fmt.Sprint(summary.TotalCounter) + + m.Row(5, func() { + m.Col(1, func() { + m.Text("HIGH", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getRedColor(), + }) + }) + m.Col(1, func() { + m.Text(highSeverityCount, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getRedColor(), + }) + }) + m.Col(1, func() { + m.Text("MEDIUM", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getOrangeColor(), + }) + }) + m.Col(1, func() { + m.Text(mediumSeverityCount, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getOrangeColor(), + }) + }) + m.Col(1, func() { + m.Text("LOW", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getYellowColor(), + }) + }) + m.Col(1, func() { + m.Text(lowSeverityCount, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getYellowColor(), + }) + }) + m.Col(1, func() { + m.Text("INFO", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getBlueColor(), + }) + }) + m.Col(1, func() { + m.Text(infoSeverityCount, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: getBlueColor(), + }) + }) + m.Col(1, func() { + m.Text("TOTAL", props.Text{ + Size: defaultTextSize, + Align: consts.Right, + Style: consts.Bold, + Extrapolate: false, + }) + }) + m.Col(3, func() { + m.Text(totalCount, props.Text{ + Size: defaultTextSize, + Align: consts.Right, + Style: consts.Bold, + Extrapolate: false, + }) + }) + }) +} + +func createFirstPageHeader(m pdf.Maroto, summary model.Summary) { + createSummaryArea(m, summary) + createPlatformsArea(m, summary) + createDateArea(m, summary) + m.Row(4, func() { + m.Col(2, func() { + m.Text("SCANNED PATHS:", props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: false, + }) + }) + }) + for idx := range summary.ScannedPaths { + m.Row(4, func() { + m.Col(12, func() { + m.Text(fmt.Sprintf("- %s", summary.ScannedPaths[idx]), props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Extrapolate: true, + }) + }) + }) + } + m.Row(2, func() { + m.ColSpace(12) + }) +} + func getGrayColor() color.Color { return color.Color{ Red: 200, @@ -203,3 +401,43 @@ func getGrayColor() color.Color { Blue: 200, } } + +func getRedColor() color.Color { + return color.Color{ + Red: 200, + Green: 0, + Blue: 0, + } +} + +func getYellowColor() color.Color { + return color.Color{ + Red: 206, + Green: 182, + Blue: 26, + } +} + +func getOrangeColor() color.Color { + return color.Color{ + Red: 255, + Green: 165, + Blue: 0, + } +} + +func getBlueColor() color.Color { + return color.Color{ + Red: 0, + Green: 0, + Blue: 200, + } +} + +func getPurpleColor() color.Color { + return color.Color{ + Red: 80, + Green: 62, + Blue: 158, + } +} From 0bbd1c71e3c54dc7e0c6f8c9077015f2eb27b9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Tue, 8 Jun 2021 18:00:17 +0100 Subject: [PATCH 4/9] progress on pdf report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rogério Peixoto --- pkg/report/pdf.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index 3704c74ab6b..aa033264356 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -41,7 +41,7 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath }) m.Col(9, func() { m.Text(queries[idx].QueryName, props.Text{ - Size: 10, + Size: 11, Style: consts.Bold, Align: consts.Left, Extrapolate: false, @@ -64,17 +64,17 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath }) }) }) - m.Row(3, func() { + m.Row(4, func() { m.Col(2, func() { m.Text("Severity", props.Text{ - Size: defaultTextSize, + Size: 10, Align: consts.Left, Extrapolate: false, }) }) m.Col(2, func() { m.Text(string(queries[idx].Severity), props.Text{ - Size: defaultTextSize, + Size: 10, Align: consts.Left, Extrapolate: false, }) @@ -346,6 +346,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getBlueColor(), }) }) + m.ColSpace(2) m.Col(1, func() { m.Text("TOTAL", props.Text{ Size: defaultTextSize, @@ -354,7 +355,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Extrapolate: false, }) }) - m.Col(3, func() { + m.Col(1, func() { m.Text(totalCount, props.Text{ Size: defaultTextSize, Align: consts.Right, From 69d530f93a6874dd86961c86a6f492af0024a716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Tue, 8 Jun 2021 18:43:34 +0100 Subject: [PATCH 5/9] fixing linter --- pkg/report/pdf.go | 164 +++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 66 deletions(-) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index aa033264356..95731c00824 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -20,6 +20,20 @@ const ( pgMarginLeft = 10 pgMarginTop = 15 pgMarginRight = 10 + rowXSmall = 3 + rowSmall = 4 + rowMedium = 5 + rowLarge = 8 + rowXLarge = 15 + colOne = 1 + colTwo = 2 + colThree = 3 + colFour = 4 + colFive = 5 + colSix = 6 + colNine = 9 + colTen = 10 + colFullPage = 12 ) var ( @@ -29,25 +43,32 @@ var ( ) func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath string) error { - for idx := range queries { + for i := range queries { m.SetBackgroundColor(color.NewWhite()) - m.Row(8, func() { - m.Col(1, func() { - m.Base64Image(vulnImageBase64, consts.Png, props.Rect{ + queryName := queries[i].QueryName + resultsCount := fmt.Sprint(len(queries[i].Files)) + severity := string(queries[i].Severity) + platform := queries[i].Platform + category := queries[i].Category + + var err error + m.Row(rowLarge, func() { + m.Col(colOne, func() { + err = m.Base64Image(vulnImageBase64, consts.Png, props.Rect{ Center: false, Percent: 50, Left: 2, }) }) - m.Col(9, func() { - m.Text(queries[idx].QueryName, props.Text{ + m.Col(colNine, func() { + m.Text(queryName, props.Text{ Size: 11, Style: consts.Bold, Align: consts.Left, Extrapolate: false, }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text("Results", props.Text{ Size: 8, Style: consts.Bold, @@ -55,8 +76,8 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath Extrapolate: false, }) }) - m.Col(1, func() { - m.Text(fmt.Sprint(len(queries[idx].Files)), props.Text{ + m.Col(colOne, func() { + m.Text(resultsCount, props.Text{ Size: 8, Style: consts.Bold, Align: consts.Right, @@ -64,55 +85,58 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath }) }) }) - m.Row(4, func() { - m.Col(2, func() { + if err != nil { + return err + } + m.Row(colFour, func() { + m.Col(colTwo, func() { m.Text("Severity", props.Text{ Size: 10, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { - m.Text(string(queries[idx].Severity), props.Text{ + m.Col(colTwo, func() { + m.Text(severity, props.Text{ Size: 10, Align: consts.Left, Extrapolate: false, }) }) }) - m.Row(3, func() { - m.Col(2, func() { + m.Row(colThree, func() { + m.Col(colTwo, func() { m.Text("Platform", props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { - m.Text(queries[idx].Platform, props.Text{ + m.Col(colTwo, func() { + m.Text(platform, props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) }) - m.Row(5, func() { - m.Col(2, func() { + m.Row(colFive, func() { + m.Col(colTwo, func() { m.Text("Category", props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { - m.Text(queries[idx].Category, props.Text{ + m.Col(colTwo, func() { + m.Text(category, props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) }) - err := createResultsTable(m, &queries[idx], basePath) + err = createResultsTable(m, &queries[i], basePath) if err != nil { return err } @@ -132,8 +156,8 @@ func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath str return err } fileLine := fmt.Sprintf("%s:%s", relativePath, fmt.Sprint(query.Files[idx].Line)) - m.Row(5, func() { - m.Col(12, func() { + m.Row(colFive, func() { + m.Col(colFullPage, func() { m.Text(fileLine, props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -148,8 +172,8 @@ func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath str func createHeaderArea(m pdf.Maroto) { m.SetBackgroundColor(getPurpleColor()) - m.Row(15, func() { - m.Col(6, func() { + m.Row(rowXLarge, func() { + m.Col(colSix, func() { m.Text(" KICS REPORT", props.Text{ Size: 25, Style: consts.Bold, @@ -158,17 +182,17 @@ func createHeaderArea(m pdf.Maroto) { Color: color.NewWhite(), }) }) - m.ColSpace(6) + m.ColSpace(colSix) }) m.SetBackgroundColor(color.NewWhite()) - m.Row(3, func() { - m.ColSpace(12) + m.Row(rowXSmall, func() { + m.ColSpace(colFullPage) }) } func createFooterArea(m pdf.Maroto) { - m.Row(5, func() { - m.Col(1, func() { + m.Row(rowMedium, func() { + m.Col(colOne, func() { m.Text("http://kics.io") }) }) @@ -200,28 +224,35 @@ func PrintPdfReport(path, filename string, body interface{}) error { m.SetBackgroundColor(color.NewWhite()) - createFirstPageHeader(m, *summary) + createFirstPageHeader(m, summary) m.Line(1.0) - createQueriesTable(m, summary.Queries, basePath) + err = createQueriesTable(m, summary.Queries, basePath) + if err != nil { + return err + } err = m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) + if err != nil { + return err + } + log.Info().Msgf("Generate report duration: %v", time.Since(startTime)) return err } -func createDateArea(m pdf.Maroto, summary model.Summary) { - m.Row(4, func() { - m.Col(2, func() { +func createDateArea(m pdf.Maroto, summary *model.Summary) { + m.Row(colFour, func() { + m.Col(colTwo, func() { m.Text("START TIME", props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colTwo, func() { m.Text(summary.Start.Format("15:04:05, Jan 02 2006"), props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -229,15 +260,15 @@ func createDateArea(m pdf.Maroto, summary model.Summary) { }) }) }) - m.Row(6, func() { - m.Col(2, func() { + m.Row(colSix, func() { + m.Col(colTwo, func() { m.Text("END TIME", props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(2, func() { + m.Col(colTwo, func() { m.Text(summary.End.Format("15:04:05, Jan 02 2006"), props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -247,16 +278,16 @@ func createDateArea(m pdf.Maroto, summary model.Summary) { }) } -func createPlatformsArea(m pdf.Maroto, summary model.Summary) { - m.Row(4, func() { - m.Col(2, func() { +func createPlatformsArea(m pdf.Maroto, summary *model.Summary) { + m.Row(rowSmall, func() { + m.Col(colTwo, func() { m.Text("PLATFORMS", props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, }) }) - m.Col(10, func() { + m.Col(colTen, func() { m.Text(getPlatforms(summary.Queries), props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -266,15 +297,15 @@ func createPlatformsArea(m pdf.Maroto, summary model.Summary) { }) } -func createSummaryArea(m pdf.Maroto, summary model.Summary) { +func createSummaryArea(m pdf.Maroto, summary *model.Summary) { highSeverityCount := fmt.Sprint(summary.SeverityCounters["HIGH"]) mediumSeverityCount := fmt.Sprint(summary.SeverityCounters["MEDIUM"]) lowSeverityCount := fmt.Sprint(summary.SeverityCounters["LOW"]) infoSeverityCount := fmt.Sprint(summary.SeverityCounters["INFO"]) totalCount := fmt.Sprint(summary.TotalCounter) - m.Row(5, func() { - m.Col(1, func() { + m.Row(rowMedium, func() { + m.Col(colOne, func() { m.Text("HIGH", props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -283,7 +314,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getRedColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text(highSeverityCount, props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -292,7 +323,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getRedColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text("MEDIUM", props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -301,7 +332,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getOrangeColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text(mediumSeverityCount, props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -310,7 +341,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getOrangeColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text("LOW", props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -319,7 +350,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getYellowColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text(lowSeverityCount, props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -328,7 +359,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getYellowColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text("INFO", props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -337,7 +368,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getBlueColor(), }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text(infoSeverityCount, props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -346,8 +377,8 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Color: getBlueColor(), }) }) - m.ColSpace(2) - m.Col(1, func() { + m.ColSpace(colTwo) + m.Col(colOne, func() { m.Text("TOTAL", props.Text{ Size: defaultTextSize, Align: consts.Right, @@ -355,7 +386,7 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { Extrapolate: false, }) }) - m.Col(1, func() { + m.Col(colOne, func() { m.Text(totalCount, props.Text{ Size: defaultTextSize, Align: consts.Right, @@ -366,12 +397,12 @@ func createSummaryArea(m pdf.Maroto, summary model.Summary) { }) } -func createFirstPageHeader(m pdf.Maroto, summary model.Summary) { +func createFirstPageHeader(m pdf.Maroto, summary *model.Summary) { createSummaryArea(m, summary) createPlatformsArea(m, summary) createDateArea(m, summary) - m.Row(4, func() { - m.Col(2, func() { + m.Row(rowSmall, func() { + m.Col(colTwo, func() { m.Text("SCANNED PATHS:", props.Text{ Size: defaultTextSize, Align: consts.Left, @@ -379,10 +410,11 @@ func createFirstPageHeader(m pdf.Maroto, summary model.Summary) { }) }) }) - for idx := range summary.ScannedPaths { - m.Row(4, func() { - m.Col(12, func() { - m.Text(fmt.Sprintf("- %s", summary.ScannedPaths[idx]), props.Text{ + for i := range summary.ScannedPaths { + scannedPaths := summary.ScannedPaths[i] + m.Row(rowSmall, func() { + m.Col(colFullPage, func() { + m.Text(fmt.Sprintf("- %s", scannedPaths), props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: true, @@ -390,8 +422,8 @@ func createFirstPageHeader(m pdf.Maroto, summary model.Summary) { }) }) } - m.Row(2, func() { - m.ColSpace(12) + m.Row(rowXSmall, func() { + m.ColSpace(colFullPage) }) } From bed279829e15726b0a92867320dc901a063d4e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Tue, 8 Jun 2021 18:54:23 +0100 Subject: [PATCH 6/9] fixing e2e tests --- e2e/fixtures/assets/scan_help | 2 +- go.mod | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/fixtures/assets/scan_help b/e2e/fixtures/assets/scan_help index 04c5058983d..9e4598fa939 100644 --- a/e2e/fixtures/assets/scan_help +++ b/e2e/fixtures/assets/scan_help @@ -37,7 +37,7 @@ Flags: -d, --payload-path string path to store internal representation JSON file --preview-lines int number of lines to be display in CLI results (min: 1, max: 30) (default 3) -q, --queries-path string path to directory with queries (default "./assets/queries") - --report-formats strings formats in which the results will be exported (all, json, sarif, html, glsast) (default [json]) + --report-formats strings formats in which the results will be exported (all, json, sarif, html, glsast, pdf) (default [json]) --timeout int number of seconds the query has to execute before being canceled (default 60) -t, --type strings case insensitive list of platform types to scan (Ansible, CloudFormation, Dockerfile, Kubernetes, OpenAPI, Terraform) diff --git a/go.mod b/go.mod index 32623e065d3..5919f73306f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.10.0 github.com/johnfercher/maroto v0.31.0 - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/buildkit v0.8.3 From 0635c99ca37272a73f5b1b198c859848264e8e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Tue, 8 Jun 2021 21:50:47 +0100 Subject: [PATCH 7/9] adding tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rogério Peixoto --- pkg/report/model/sarif.go | 9 ++++---- pkg/report/model/sarif_test.go | 4 ++-- pkg/report/pdf.go | 23 +++++++++---------- pkg/report/pdf_test.go | 40 ++++++++++++++++++++++++++++++++++ pkg/report/sarif_test.go | 30 ++++++++++++++++++++++--- 5 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 pkg/report/pdf_test.go diff --git a/pkg/report/model/sarif.go b/pkg/report/model/sarif.go index 07b80ca2ae2..aeb72ee55a5 100644 --- a/pkg/report/model/sarif.go +++ b/pkg/report/model/sarif.go @@ -120,7 +120,8 @@ type sarifTaxonomy struct { TaxonomyDefinitions []sarifTaxanomyDefinition `json:"taxa"` } -type sarifRun struct { +// SarifRun - sarifRun is a component of the SARIF report +type SarifRun struct { Tool sarifTool `json:"tool"` Results []sarifResult `json:"results"` Taxonomies []sarifTaxonomy `json:"taxonomies"` @@ -135,7 +136,7 @@ type sarifReport struct { basePath string `json:"-"` Schema string `json:"$schema"` SarifVersion string `json:"version"` - Runs []sarifRun `json:"runs"` + Runs []SarifRun `json:"runs"` } func initSarifTool() sarifTool { @@ -174,8 +175,8 @@ func initSarifTaxonomies() []sarifTaxonomy { } } -func initSarifRun() []sarifRun { - return []sarifRun{ +func initSarifRun() []SarifRun { + return []SarifRun{ { Tool: initSarifTool(), Results: make([]sarifResult, 0), diff --git a/pkg/report/model/sarif_test.go b/pkg/report/model/sarif_test.go index f70bd40fee4..628914d8b8b 100644 --- a/pkg/report/model/sarif_test.go +++ b/pkg/report/model/sarif_test.go @@ -57,7 +57,7 @@ var sarifTests = []sarifTest{ }, }, want: sarifReport{ - Runs: []sarifRun{ + Runs: []SarifRun{ { Tool: sarifTool{ Driver: sarifDriver{ @@ -142,7 +142,7 @@ var sarifTests = []sarifTest{ }, }, want: sarifReport{ - Runs: []sarifRun{ + Runs: []SarifRun{ { Tool: sarifTool{ Driver: sarifDriver{ diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index 95731c00824..791c1d5b6e3 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -136,26 +136,28 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath }) }) }) - err = createResultsTable(m, &queries[i], basePath) - if err != nil { - return err - } + createResultsTable(m, &queries[i], basePath) } return nil } -func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath string) error { +func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath string) { for idx := range query.Files { if idx%2 == 0 { m.SetBackgroundColor(grayColor) } else { m.SetBackgroundColor(color.NewWhite()) } + var filePath string relativePath, err := filepath.Rel(basePath, query.Files[idx].FileName) if err != nil { - return err + log.Error().Msgf("Cannot make %s relative to %s", query.Files[idx].FileName, basePath) + filePath = query.Files[idx].FileName + } else { + filePath = relativePath } - fileLine := fmt.Sprintf("%s:%s", relativePath, fmt.Sprint(query.Files[idx].Line)) + + fileLine := fmt.Sprintf("%s:%s", filePath, fmt.Sprint(query.Files[idx].Line)) m.Row(colFive, func() { m.Col(colFullPage, func() { m.Text(fileLine, props.Text{ @@ -167,7 +169,6 @@ func createResultsTable(m pdf.Maroto, query *model.VulnerableQuery, basePath str }) } m.Line(1.0) - return nil } func createHeaderArea(m pdf.Maroto) { @@ -203,7 +204,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { startTime := time.Now() log.Info().Msg("Started generating pdf report") - summary := body.(*model.Summary) + summary := body.(model.Summary) basePath, err := os.Getwd() if err != nil { return err @@ -224,7 +225,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { m.SetBackgroundColor(color.NewWhite()) - createFirstPageHeader(m, summary) + createFirstPageHeader(m, &summary) m.Line(1.0) @@ -233,7 +234,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { return err } - err = m.OutputFileAndClose(fmt.Sprintf("%s.pdf", filename)) + err = m.OutputFileAndClose(filepath.Join(path, fmt.Sprintf("%s.pdf", filename))) if err != nil { return err } diff --git a/pkg/report/pdf_test.go b/pkg/report/pdf_test.go new file mode 100644 index 00000000000..e22f7a893cd --- /dev/null +++ b/pkg/report/pdf_test.go @@ -0,0 +1,40 @@ +package report + +import ( + "fmt" + "os" + "testing" + + "github.com/Checkmarx/kics/test" +) + +var pdfTests = []reportTestCase{ + { + caseTest: jsonCaseTest{ + summary: test.SummaryMock, + path: "./testdir", + filename: "testpdf", + }, + }, + { + caseTest: jsonCaseTest{ + summary: test.SummaryMock, + path: "./testdir/newdir", + filename: "testpdf2", + }, + }, +} + +// TestPrintPdfReport tests the functions [PrintPdfReport()] and all the methods called by them +func TestPrintPdfReport(t *testing.T) { + for i, test := range pdfTests { + t.Run(fmt.Sprintf("PDF report test case %d", i), func(t *testing.T) { + if err := os.MkdirAll(test.caseTest.path, os.ModePerm); err != nil { + t.Fatal(err) + } + err := PrintPdfReport(test.caseTest.path, test.caseTest.filename, test.caseTest.summary) + checkFileExists(t, err, &test, "pdf") + os.RemoveAll(test.caseTest.path) + }) + } +} diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index 846b5a8a281..b21c5ba6774 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -1,20 +1,31 @@ package report import ( + "encoding/json" "fmt" "os" "path/filepath" "testing" "github.com/Checkmarx/kics/pkg/model" + reportModel "github.com/Checkmarx/kics/pkg/report/model" "github.com/Checkmarx/kics/test" "github.com/stretchr/testify/require" ) -var sarifTests = []struct { +type reportTestCase struct { caseTest jsonCaseTest expectedResult model.Summary -}{ +} + +type sarifReport struct { + basePath string `json:"-"` + Schema string `json:"$schema"` + SarifVersion string `json:"version"` + Runs []reportModel.SarifRun `json:"runs"` +} + +var sarifTests = []reportTestCase{ { caseTest: jsonCaseTest{ summary: test.SummaryMock, @@ -33,9 +44,22 @@ func TestPrintSarifReport(t *testing.T) { t.Fatal(err) } err := PrintSarifReport(test.caseTest.path, test.caseTest.filename, test.caseTest.summary) + checkFileExists(t, err, &test, "sarif") + jsonResult, err := os.ReadFile(filepath.Join(test.caseTest.path, test.caseTest.filename+".sarif")) require.NoError(t, err) - require.FileExists(t, filepath.Join(test.caseTest.path, test.caseTest.filename+".sarif")) + var resultSarif sarifReport + err = json.Unmarshal(jsonResult, &resultSarif) + require.NoError(t, err) + require.Equal(t, "", resultSarif.basePath) + require.Equal(t, "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", resultSarif.Schema) + require.Equal(t, "2.1.0", resultSarif.SarifVersion) + require.Len(t, resultSarif.Runs, len(test.expectedResult.Queries)) os.RemoveAll(test.caseTest.path) }) } } + +func checkFileExists(t *testing.T, err error, tc *reportTestCase, extension string) { + require.NoError(t, err) + require.FileExists(t, filepath.Join(tc.caseTest.path, tc.caseTest.filename+fmt.Sprintf(".%s", extension))) +} From 1f1f6c400e8c2b19dd646916e470a728d88c88e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Wed, 9 Jun 2021 12:26:40 +0100 Subject: [PATCH 8/9] addressing requested changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rogério Peixoto --- pkg/report/commons.go | 12 +++ pkg/report/html.go | 13 --- pkg/report/pdf.go | 192 +++++++++++++----------------------------- 3 files changed, 69 insertions(+), 148 deletions(-) diff --git a/pkg/report/commons.go b/pkg/report/commons.go index bde741fa3cc..45d0732f701 100644 --- a/pkg/report/commons.go +++ b/pkg/report/commons.go @@ -50,3 +50,15 @@ func closeFile(path, filename string, file *os.File) { log.Info().Str("fileName", filename).Msgf("Results saved to file %s", path) fmt.Printf("Results saved to file %s\n", path) } + +func getPlatforms(queries model.VulnerableQuerySlice) string { + platforms := make([]string, 0) + alreadyAdded := make(map[string]string) + for idx := range queries { + if _, ok := alreadyAdded[queries[idx].Platform]; !ok { + alreadyAdded[queries[idx].Platform] = "" + platforms = append(platforms, queries[idx].Platform) + } + } + return strings.Join(platforms, ", ") +} diff --git a/pkg/report/html.go b/pkg/report/html.go index 076375f5301..fc58cbfe3e4 100644 --- a/pkg/report/html.go +++ b/pkg/report/html.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" - "github.com/Checkmarx/kics/pkg/model" "github.com/tdewolff/minify/v2" minifyCSS "github.com/tdewolff/minify/v2/css" minifyHtml "github.com/tdewolff/minify/v2/html" @@ -71,18 +70,6 @@ func getPaths(paths []string) string { return strings.Join(paths, ", ") } -func getPlatforms(queries model.VulnerableQuerySlice) string { - platforms := make([]string, 0) - alreadyAdded := make(map[string]string) - for idx := range queries { - if _, ok := alreadyAdded[queries[idx].Platform]; !ok { - alreadyAdded[queries[idx].Platform] = "" - platforms = append(platforms, queries[idx].Platform) - } - } - return strings.Join(platforms, ", ") -} - // PrintHTMLReport creates a report file on HTML format func PrintHTMLReport(path, filename string, body interface{}) error { if !strings.HasSuffix(filename, ".html") { diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index 791c1d5b6e3..cc7c4878d6b 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -42,6 +42,23 @@ var ( vulnImageBase64 string ) +func createQueryEntryMetadataField(m pdf.Maroto, label, value string, textSize int) { + m.Col(colTwo, func() { + m.Text(label, props.Text{ + Size: float64(textSize), + Align: consts.Left, + Extrapolate: false, + }) + }) + m.Col(colTwo, func() { + m.Text(value, props.Text{ + Size: float64(textSize), + Align: consts.Left, + Extrapolate: false, + }) + }) +} + func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath string) error { for i := range queries { m.SetBackgroundColor(color.NewWhite()) @@ -89,52 +106,13 @@ func createQueriesTable(m pdf.Maroto, queries []model.VulnerableQuery, basePath return err } m.Row(colFour, func() { - m.Col(colTwo, func() { - m.Text("Severity", props.Text{ - Size: 10, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colTwo, func() { - m.Text(severity, props.Text{ - Size: 10, - Align: consts.Left, - Extrapolate: false, - }) - }) + createQueryEntryMetadataField(m, "Severity", severity, 10) }) m.Row(colThree, func() { - m.Col(colTwo, func() { - m.Text("Platform", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colTwo, func() { - m.Text(platform, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) + createQueryEntryMetadataField(m, "Platform", platform, defaultTextSize) }) m.Row(colFive, func() { - m.Col(colTwo, func() { - m.Text("Category", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colTwo, func() { - m.Text(category, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) + createQueryEntryMetadataField(m, "Category", category, defaultTextSize) }) createResultsTable(m, &queries[i], basePath) } @@ -194,7 +172,7 @@ func createHeaderArea(m pdf.Maroto) { func createFooterArea(m pdf.Maroto) { m.Row(rowMedium, func() { m.Col(colOne, func() { - m.Text("http://kics.io") + m.Text("https://kics.io") }) }) } @@ -204,7 +182,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { startTime := time.Now() log.Info().Msg("Started generating pdf report") - summary := body.(model.Summary) + summary := body.(*model.Summary) basePath, err := os.Getwd() if err != nil { return err @@ -225,7 +203,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { m.SetBackgroundColor(color.NewWhite()) - createFirstPageHeader(m, &summary) + createFirstPageHeader(m, summary) m.Line(1.0) @@ -244,10 +222,10 @@ func PrintPdfReport(path, filename string, body interface{}) error { return err } -func createDateArea(m pdf.Maroto, summary *model.Summary) { +func createDateField(m pdf.Maroto, label string, summary *model.Summary) { m.Row(colFour, func() { m.Col(colTwo, func() { - m.Text("START TIME", props.Text{ + m.Text(label, props.Text{ Size: defaultTextSize, Align: consts.Left, Extrapolate: false, @@ -261,22 +239,11 @@ func createDateArea(m pdf.Maroto, summary *model.Summary) { }) }) }) - m.Row(colSix, func() { - m.Col(colTwo, func() { - m.Text("END TIME", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - m.Col(colTwo, func() { - m.Text(summary.End.Format("15:04:05, Jan 02 2006"), props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Extrapolate: false, - }) - }) - }) +} + +func createDateArea(m pdf.Maroto, summary *model.Summary) { + createDateField(m, "START TIME", summary) + createDateField(m, "END TIME", summary) } func createPlatformsArea(m pdf.Maroto, summary *model.Summary) { @@ -298,6 +265,27 @@ func createPlatformsArea(m pdf.Maroto, summary *model.Summary) { }) } +func createSummaryResultsField(m pdf.Maroto, label, value string, mColor color.Color) { + m.Col(colOne, func() { + m.Text(label, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: mColor, + }) + }) + m.Col(colOne, func() { + m.Text(value, props.Text{ + Size: defaultTextSize, + Align: consts.Left, + Style: consts.Bold, + Extrapolate: false, + Color: mColor, + }) + }) +} + func createSummaryArea(m pdf.Maroto, summary *model.Summary) { highSeverityCount := fmt.Sprint(summary.SeverityCounters["HIGH"]) mediumSeverityCount := fmt.Sprint(summary.SeverityCounters["MEDIUM"]) @@ -306,79 +294,13 @@ func createSummaryArea(m pdf.Maroto, summary *model.Summary) { totalCount := fmt.Sprint(summary.TotalCounter) m.Row(rowMedium, func() { - m.Col(colOne, func() { - m.Text("HIGH", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getRedColor(), - }) - }) - m.Col(colOne, func() { - m.Text(highSeverityCount, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getRedColor(), - }) - }) - m.Col(colOne, func() { - m.Text("MEDIUM", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getOrangeColor(), - }) - }) - m.Col(colOne, func() { - m.Text(mediumSeverityCount, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getOrangeColor(), - }) - }) - m.Col(colOne, func() { - m.Text("LOW", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getYellowColor(), - }) - }) - m.Col(colOne, func() { - m.Text(lowSeverityCount, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getYellowColor(), - }) - }) - m.Col(colOne, func() { - m.Text("INFO", props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getBlueColor(), - }) - }) - m.Col(colOne, func() { - m.Text(infoSeverityCount, props.Text{ - Size: defaultTextSize, - Align: consts.Left, - Style: consts.Bold, - Extrapolate: false, - Color: getBlueColor(), - }) - }) + createSummaryResultsField(m, "HIGH", highSeverityCount, getRedColor()) + createSummaryResultsField(m, "MEDIUM", mediumSeverityCount, getOrangeColor()) + createSummaryResultsField(m, "LOW", lowSeverityCount, getYellowColor()) + createSummaryResultsField(m, "INFO", infoSeverityCount, getBlueColor()) + m.ColSpace(colTwo) + m.Col(colOne, func() { m.Text("TOTAL", props.Text{ Size: defaultTextSize, From 4e39187752c5bd6b09e44144094d81759b81da7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Peixoto?= Date: Wed, 9 Jun 2021 15:42:52 +0100 Subject: [PATCH 9/9] fixing tests --- pkg/report/pdf.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/report/pdf.go b/pkg/report/pdf.go index cc7c4878d6b..4dfcbe4787a 100644 --- a/pkg/report/pdf.go +++ b/pkg/report/pdf.go @@ -182,7 +182,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { startTime := time.Now() log.Info().Msg("Started generating pdf report") - summary := body.(*model.Summary) + summary := body.(model.Summary) basePath, err := os.Getwd() if err != nil { return err @@ -203,7 +203,7 @@ func PrintPdfReport(path, filename string, body interface{}) error { m.SetBackgroundColor(color.NewWhite()) - createFirstPageHeader(m, summary) + createFirstPageHeader(m, &summary) m.Line(1.0)