From 7e9a0fe825593a90cc5e42d61c2471f6d9b7d8c1 Mon Sep 17 00:00:00 2001 From: Martijn van der Ploeg <73637849+martijnvdp@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:39:34 +0200 Subject: [PATCH] improve trivy operator slack template (#597) --- rego-templates/trivy-operator-slack.rego | 160 ++++++++++++++++++++--- 1 file changed, 142 insertions(+), 18 deletions(-) diff --git a/rego-templates/trivy-operator-slack.rego b/rego-templates/trivy-operator-slack.rego index 63df60d7..de54724e 100644 --- a/rego-templates/trivy-operator-slack.rego +++ b/rego-templates/trivy-operator-slack.rego @@ -1,20 +1,144 @@ package postee.trivyoperator.slack -title:=sprintf("Trivy Operator %s Report for - %s", [input.kind, input.metadata.name]) - -result:= res { - res:= [ - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*CRITICAL:* %d", [input.report.summary.criticalCount])}}, - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*HIGH:* %d", [input.report.summary.highCount])}}, - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*MEDIUM:* %d", [input.report.summary.mediumCount])}}, - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*LOW:* %d", [input.report.summary.lowCount])}}, - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*UNKNOWN:* %d", [input.report.summary.unknownCount])}}, - { "type":"section", - "text": {"type":"mrkdwn","text": sprintf("*NONE:* %d", [input.report.summary.noneCount])}}, - ] -} \ No newline at end of file +import data.postee.flat_array #converts [[{...},{...}], [{...},{...}]] to [{...},{...},{...},{...}] +import data.postee.with_default + +############################################# Common functions ############################################ + +# render_sections split collection of cells provided to chunks of 5 rows each and wraps every chunk with section element +render_sections(rows, caption, headers) = result { + count(rows) > 0 # only if some vulnerabilities are found + rows_and_header := array.concat(headers, rows) + a := flat_array([s | + # code below converts 2 dimension array like [[row1, row2, ... row5], ....] + group_size := 10 #it's 5 but every row is represented by 2 items + num_chunks := ceil(count(rows_and_header) / group_size) - 1 + indices := {b | b := numbers.range(0, num_chunks)[_] * group_size} + some k + fields := [array.slice(rows_and_header, i, i + group_size) | i := indices[_]][k] + # builds markdown section based on slice + + s := with_caption(fields, caption, k) + ]) + result := array.concat(a, [{"type": "divider"}]) +} + +render_sections(rows, caption, headers) = [] { #do not render section if provided collection is empty + count(rows) == 0 +} + +with_caption(fields, caption, position) = s { + position == 0 + s := [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": caption, + }, + }, + { + "type": "section", + "fields": fields, + }, + ] +} +with_caption(fields, caption, position) = s { + position > 0 + s := [ + { + "type": "section", + "fields": fields, + }, + ] +} + + +########################################################################################################### + +vln_list(severity) = l { + # builds list of rows for section for the given severity + some i + vlnrb := [r | + item := input.report.vulnerabilities[i] + vlnname := item.vulnerabilityID + + fxvrsn := with_default(item, "fixedVersion", "none") + resource_name = with_default(item, "resource", "none") + resource_version = with_default(item, "installedVersion", "none") + url = with_default(item, "primaryLink","") + item.severity == severity # only items with severity matched + + r := [ + {"type": "mrkdwn", "text": sprintf("<%s|%s>",[url,vlnname])}, + {"type": "mrkdwn", "text": concat(" / ", [resource_name, resource_version, fxvrsn])}, + ] + ] + + caption := sprintf("*%s severity vulnerabilities*", [severity]) #TODO make first char uppercase + + headers := [ + {"type": "mrkdwn", "text": "*Vulnerability ID*"}, + {"type": "mrkdwn", "text": "*Resource / Version / Fixed version*"}, + ] + + # split rows and wrap slices with markdown section + l := render_sections(flat_array(vlnrb), caption, headers) +} + +image_name := sprintf("%s:%s", [ + with_default(input.report.artifact,"repository","unknown"), + with_default(input.report.artifact,"tag","unknown") +]) +########################################################################################################### +postee := with_default(input, "postee", {}) + +title = sprintf("Vulnerability scan report %s", [image_name]) # title is + +result = res { + + header := [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": sprintf("Vulnerability issue with image:%s in namespace %s",[image_name, with_default(input.metadata,"namespace","unknown")]), + }, + } + ] + + summary := [ + { + "type": "divider" + }, + { + "type": "context", + "elements": [ + {"type": "mrkdwn", "text": "*Summary totals:*"}, + ], + }, + { + "type": "context", + "elements": [ + {"type": "mrkdwn", "text": sprintf("Critical: *%d*", [input.report.summary.criticalCount])}, + {"type": "mrkdwn", "text": sprintf("High: *%d*", [input.report.summary.highCount])}, + {"type": "mrkdwn", "text": sprintf("Medium: *%d*", [input.report.summary.mediumCount])}, + {"type": "mrkdwn", "text": sprintf("Low: *%d*", [input.report.summary.lowCount])}, + {"type": "mrkdwn", "text": sprintf("Unknown: *%d*", [input.report.summary.unknownCount])}, + ], + }, + { + "type": "divider" + } + ] + + res := flat_array([ + header, + summary, + vln_list("CRITICAL"), + vln_list("HIGH"), + vln_list("MEDIUM"), + vln_list("LOW"), + vln_list("UNKNOWN") + ]) +}