diff --git a/cmd/main.go b/cmd/main.go index a01d98e1..15b130a4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,6 +29,7 @@ const ( jsonFormat = "json" yamlFormat = "yaml" sarifFormat = "sarif" + htmlFormat = "html" configFileFlag = "config" logLevelFlagName = "log-level" @@ -114,8 +115,8 @@ func Execute() { rootCmd.PersistentFlags().StringVar(&configFilePath, configFileFlag, "", "config file path") cobra.CheckErr(rootCmd.MarkPersistentFlagFilename(configFileFlag, "yaml", "yml", "json")) rootCmd.PersistentFlags().StringVar(&logLevelVar, logLevelFlagName, "info", "log level (trace, debug, info, warn, error, fatal)") - rootCmd.PersistentFlags().StringSliceVar(&reportPathVar, reportPathFlagName, []string{}, "path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif)") - rootCmd.PersistentFlags().StringVar(&stdoutFormatVar, stdoutFormatFlagName, "yaml", "stdout output format, available formats are: json, yaml, sarif") + rootCmd.PersistentFlags().StringSliceVar(&reportPathVar, reportPathFlagName, []string{}, "path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif, .html)") + rootCmd.PersistentFlags().StringVar(&stdoutFormatVar, stdoutFormatFlagName, "yaml", "stdout output format, available formats are: json, yaml, sarif, html") rootCmd.PersistentFlags().StringArrayVar(&customRegexRuleVar, customRegexRuleFlagName, []string{}, "custom regexes to apply to the scan, must be valid Go regex") rootCmd.PersistentFlags().StringSliceVar(&includeRuleVar, includeRuleFlagName, []string{}, "include rules by name or tag to apply to the scan (adds to list, starts from empty)") @@ -144,15 +145,15 @@ func Execute() { } func validateFormat(stdout string, reportPath []string) { - if !(strings.EqualFold(stdout, yamlFormat) || strings.EqualFold(stdout, jsonFormat) || strings.EqualFold(stdout, sarifFormat)) { - log.Fatal().Msgf(`invalid output format: %s, available formats are: json, yaml and sarif`, stdout) + if !(strings.EqualFold(stdout, yamlFormat) || strings.EqualFold(stdout, jsonFormat) || strings.EqualFold(stdout, sarifFormat) || strings.EqualFold(stdout, htmlFormat)) { + log.Fatal().Msgf(`invalid output format: %s, available formats are: json, yaml, sarif, html`, stdout) } for _, path := range reportPath { fileExtension := filepath.Ext(path) format := strings.TrimPrefix(fileExtension, ".") - if !(strings.EqualFold(format, yamlFormat) || strings.EqualFold(format, jsonFormat) || strings.EqualFold(format, sarifFormat)) { - log.Fatal().Msgf(`invalid report extension: %s, available extensions are: json, yaml and sarif`, format) + if !(strings.EqualFold(format, yamlFormat) || strings.EqualFold(format, jsonFormat) || strings.EqualFold(format, sarifFormat) || strings.EqualFold(format, htmlFormat)) { + log.Fatal().Msgf(`invalid report extension: %s, available extensions are: json, yaml, sarif, html`, format) } } } diff --git a/reporting/html.go b/reporting/html.go new file mode 100644 index 00000000..e671d72e --- /dev/null +++ b/reporting/html.go @@ -0,0 +1,37 @@ +package reporting + +import ( + "bytes" + _ "embed" + "html/template" + "log" +) + +var ( + //go:embed html/report.tmpl + htmlTemplate string + //go:embed html/report.css + cssTemplate string + //go:embed html/github.svg + githubSVG string + //go:embed html/checkmarx_logo.html + checkmarxLogo string +) + +func writeHtml(report Report) string { + tmpl := template.Must(template.New("report").Funcs(getFuncMap()).Parse(htmlTemplate)) + var buffer bytes.Buffer + err := tmpl.Execute(&buffer, report) + if err != nil { + log.Fatalf("failed to create HTML report with error: %v", err) + } + return buffer.String() +} + +func getFuncMap() template.FuncMap { + return template.FuncMap{ + "includeCSS": func() template.CSS { return template.CSS(cssTemplate) }, + "includeSVG": func() template.HTML { return template.HTML(githubSVG) }, + "includeLogo": func() template.HTML { return template.HTML(checkmarxLogo) }, + } +} diff --git a/reporting/html/checkmarx_logo.html b/reporting/html/checkmarx_logo.html new file mode 100644 index 00000000..7c127dcd --- /dev/null +++ b/reporting/html/checkmarx_logo.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/reporting/html/github.svg b/reporting/html/github.svg new file mode 100644 index 00000000..c8774ea7 --- /dev/null +++ b/reporting/html/github.svg @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/reporting/html/report.css b/reporting/html/report.css new file mode 100644 index 00000000..bee5fbcb --- /dev/null +++ b/reporting/html/report.css @@ -0,0 +1,171 @@ +* { + margin: 0; + padding: 0; + outline: 0; + box-sizing: border-box; + } + + body { + font-family: sans-serif; + } + + .container { + display: flex; + align-items: center; + flex-direction: column; + margin: 5px; + border: 1px solid #bebebe; + } + + .run-info { + display: flex; + flex-wrap: wrap; + border: 1px solid #bebebe; + margin-top: 10px; + width: 50vw; + } + + .run-info > span { + flex-basis: 50%; + text-align: center; + } + + .counters { + display: flex; + flex-direction: row; + margin: 22px 0; + } + + .report-header-footer { + display: flex; + flex-direction: row; + justify-content: space-between; + border-bottom: 1px solid #bebebe; + width: 100%; + padding: 15px 21px; + background-color: #503e9e; + height: 50px; + font-weight: bold; + font-size: 14px; + color: #fff; + cursor: default; + user-select: none; + } + + .report-header-footer > a { + color: inherit; + text-decoration: inherit; + } + + .report-header-footer > .title { + font-size: 18px; + } + + .report-header-footer > .title > span { + color: #000; + } + + .report-header-footer > .timestamp { + font-weight: normal; + font-style: italic; + opacity: 0.5; + } + + .badge { + color: #fff; + border: 2px solid #e8e8e8; + border-radius: 50%; + cursor: default; + user-select: none; + padding: 3px; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + position: absolute; + left: 60%; + top: 50%; + } + + .separator { + border-top: 1px solid #979797; + opacity: 0.5; + width: 95%; + margin: 22px 0; + } + + .secret-info { + border: 1px #969696 solid; + border-radius: 2px; + display: flex; + flex-direction: column; + margin: 6px 9px; + } + + .secret-info-header { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 6px 9px; + } + + .secret-info-details { + display: flex; + flex-direction: column; + margin: 6px 9px; + } + + .secret-info-details > span > strong { + width: 5vw; + } + + .social-networks { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin-bottom: 24px; + } + + .social-networks > a { + margin: 0 15px; + } + + .social-networks > a > div > svg { + width: 20px; + height: 20px; + } + + .footer-text { + font-style: italic; + opacity: 0.5; + font-weight: normal; + width: 100%; + display: flex; + align-self: center; + justify-content: center; + } + + a.checkmarx, + a.checkmarx:visited, + a.checkmarx:hover, + a.checkmarx:active { + cursor: pointer; + font-weight: bold; + text-decoration: underline; + color: #fff; + opacity: 0.8; + } + + .hide { + display: none; + } + + summary { + cursor: pointer; + user-select: none; + font-size: 18px; + font-weight: bold; + } \ No newline at end of file diff --git a/reporting/html/report.tmpl b/reporting/html/report.tmpl new file mode 100644 index 00000000..c9f878ad --- /dev/null +++ b/reporting/html/report.tmpl @@ -0,0 +1,63 @@ + + +
+ + +