From 543c7ce5142650c3240229fcd055beccedc1d165 Mon Sep 17 00:00:00 2001 From: afdesk Date: Thu, 9 Mar 2023 20:32:35 +0600 Subject: [PATCH 1/7] feat: add a json tempalte for swit integration --- cfg.yaml | 6 ++++++ rego-templates/swit-json.rego | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 rego-templates/swit-json.rego diff --git a/cfg.yaml b/cfg.yaml index b180c8e5..53cb2904 100644 --- a/cfg.yaml +++ b/cfg.yaml @@ -12,6 +12,10 @@ routes: actions: [ stdout ] template: raw-json +- name: stdout2 + actions: [ stdout ] + template: swit-json + #- name: route1 # Route name. Must be unique # input: contains(input.image, "alpine") # REGO rule to match input message against route # input-files: # Array filePaths to files with REGO rules @@ -48,6 +52,8 @@ templates: url: # URL to custom REGO file - name: raw-json # route message "As Is" to external webhook rego-package: postee.rawmessage.json +- name: swit-json + rego-package: postee.swit.json - name: vuls-cyclonedx # export vulnerabilities to CycloneDX XML rego-package: postee.vuls.cyclondx - name: trivy-operator-slack diff --git a/rego-templates/swit-json.rego b/rego-templates/swit-json.rego new file mode 100644 index 00000000..9e0b03d7 --- /dev/null +++ b/rego-templates/swit-json.rego @@ -0,0 +1,5 @@ +package postee.swit.json + +title:="-" #not used with webhook + +result=sprintf("%s,",[{"text":sprintf("%s",[input]) }]) \ No newline at end of file From 065c72c77a5789a931279a77390be2d7ce02ee83 Mon Sep 17 00:00:00 2001 From: afdesk Date: Fri, 10 Mar 2023 19:54:41 +0600 Subject: [PATCH 2/7] draft: add swit-text template --- cfg.yaml | 2 + rego-templates/swit-text.rego | 309 ++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 rego-templates/swit-text.rego diff --git a/cfg.yaml b/cfg.yaml index 53cb2904..72b93afd 100644 --- a/cfg.yaml +++ b/cfg.yaml @@ -54,6 +54,8 @@ templates: rego-package: postee.rawmessage.json - name: swit-json rego-package: postee.swit.json +- name: swit-text + rego-package: postee.swit.text - name: vuls-cyclonedx # export vulnerabilities to CycloneDX XML rego-package: postee.vuls.cyclondx - name: trivy-operator-slack diff --git a/rego-templates/swit-text.rego b/rego-templates/swit-text.rego new file mode 100644 index 00000000..27c04f69 --- /dev/null +++ b/rego-templates/swit-text.rego @@ -0,0 +1,309 @@ +package postee.swit.text + + +import future.keywords +import future.keywords.if +import data.postee.by_flag +import data.postee.with_default + +################################################ Templates ################################################ +#main template to render message +html_tpl:=` +Name: %s +

Registry: %s

+

Malware found: %s

+

Sensitive data found: %s

+ +

Vulnerability summary

+%s + +%s + +%s + +%s + +%s + +%s + +%s +

Resourse policy name: %s

+

Resourse policy application scopes: %s

+%s +` + +summary_tpl =`Name: %s +Registry: %s +%s +%s + +vulnerabilities: +* critical: %d, +* high: %d, +* medium: %d, +* low: %d, +* negligible: %d + +%s` + +vlnrb_tpl = ` +

%s severity vulnerabilities

+%s +` + +assurance_control_tpl = ` +

Assurance controls

+%s +` + +#Extra % is required in width:100% +table_tpl:=` + +%s +
+` + +cell_tpl:=`%s +` + +header_tpl:=`%s +` + +row_tpl:=` + +%s +` + +colored_text_tpl:="%s" + +########################################################################################################### + +############################################## Html rendering ############################################# + +render_table_headers(headers) = row { + count(headers) > 0 + ths := [th | + header := headers[_] + th := sprintf(header_tpl, [header]) + ] + + row := sprintf(row_tpl, [concat("", ths)]) +} + + +render_table_headers(headers) = "" { #if headers not specified return empty results + count(headers) == 0 +} + + +render_table(headers, content_array) = s { + rows := [tr | + cells:=content_array[_] + tds:= [td | + ctext:=cells[_] + td := to_cell(ctext) + ] + tr=sprintf(row_tpl, [concat("", tds)]) + ] + + s:=sprintf(table_tpl, [concat("", array.concat([render_table_headers(headers)],rows))]) +} + +## why I added it? +to_cell(txt) = c { + c:= sprintf(cell_tpl, [txt]) +} + +to_colored_text(color, txt) = spn { + spn :=sprintf(colored_text_tpl, [color, txt]) +} + +####################################### Template specific functions ####################################### +to_severity_color(color, level) = spn { + spn:=to_colored_text(color, format_int(with_default(input.vulnerability_summary,level,0), 10)) +} +# TODO refactor to support different properties +check_failed(item) = false { +not item.failed #Either absent or false +} +check_failed(item) = true { + item.failed +} + +# 2 dimension array for vulnerabilities summary +severities_stats := [ + ["critical", to_severity_color("#c00000", "critical")], + ["high", to_severity_color("#e0443d", "high")], + ["medium", to_severity_color("#f79421", "medium")], + ["low", to_severity_color("#e1c930", "low")], + ["negligible", to_severity_color("green", "negligible")] + ] + +# 2 dimension array for assurance controls +assurance_controls := [ control | + item := input.image_assurance_results.checks_performed[i] + control := [format_int(i+1, 10), item.control,item.policy_name, + by_flag( + "FAIL", + "PASS", + check_failed(item) + ) + ] +] + +vlnrb_headers := ["Vulnerability ID", "Resource name", "Installed version", "Fix version"] + + +render_vlnrb(severity, list) = sprintf(vlnrb_tpl, [severity, render_table(vlnrb_headers, list)]) { + count(list) > 0 +} + +render_vlnrb(severity, list) = "" { #returns empty string if list of vulnerabilities is passed + count(list) == 0 +} + +assurance_control_headers := ["#","Control","Policy Name", "Status"] + +render_assurance_control(list) = sprintf(assurance_control_tpl, [render_table(assurance_control_headers, list)]) { + count(list) > 0 +} + +render_assurance_control(list) = "" { #returns empty string if list of assurance control is passed + count(list) == 0 +} + +# builds 2-dimension array for vulnerability table +vln_list(severity) = vlnrb { + some i, j + vlnrb := [r | + item := input.resources[i] + + + resource := item.resource + vlnname := item.vulnerabilities[j].name + fxvrsn := with_default(item.vulnerabilities[j],"fix_version", "none") + resource_name = with_default(resource, "name", "none") + resource_version = with_default(resource, "version", "none") + + item.vulnerabilities[j].aqua_severity == severity # only items with severity matched + r := [vlnname, resource_name, resource_version, fxvrsn] + ] +} +########################################################################################################### +postee := with_default(input, "postee", {}) +aqua_server := with_default(postee, "AquaServer", "") +server_url := trim_suffix(aqua_server, "images/") + +report_type := "function" if{ + input.entity_type == 1 +} else = "vm" if{ + input.entity_type == 2 +} else = "image" + +title = sprintf(`Aqua security | %s | %s | Scan report`, [report_type, input.image]) + +## url formats: +## function: /#/functions// +## vm: /#/infrastructure//node +## image: /#/image// +href := sprintf("%s%s/%s/%s", [server_url, "functions", urlquery.encode(input.registry), urlquery.encode(input.image)]) if{ + report_type == "function" +} else = sprintf("%s%s/%s/%s", [server_url, "infrastructure", urlquery.encode(input.image), "node"]){ + report_type == "vm" +} else = sprintf("%s%s/%s/%s", [server_url, "image", urlquery.encode(input.registry), urlquery.encode(input.image)]) + +text := sprintf("%s%s/%s/%s", [server_url, "functions", input.registry, input.image]) if{ + report_type == "function" +} else = sprintf("%s%s/%s/%s", [server_url, "infrastructure", input.image, "node"]) { + report_type == "vm" +} else = sprintf("%s%s/%s/%s", [server_url, report_type, input.registry, input.image]) + +url := by_flag("", href, server_url == "") + +# some vulnerability_summary fields may not exist +vulnerability_summary_critical := input.vulnerability_summary.critical +vulnerability_summary_high := input.vulnerability_summary.high +vulnerability_summary_medium := input.vulnerability_summary.medium +vulnerability_summary_low := input.vulnerability_summary.low +vulnerability_summary_negligible := input.vulnerability_summary.negligible + +aggregation_pkg := "postee.vuls.html.aggregation" + +############################################## result values ############################################# +content = msg { + + msg := sprintf(html_tpl, [ + input.image, + input.registry, + by_flag( + "Yes", + "No", + input.scan_options.scan_malware #reflects current logic + ), + by_flag( + "Yes", + "No", + input.scan_options.scan_sensitive_data #reflects current logic + ), + render_table([], severities_stats), + render_assurance_control(assurance_controls), + render_vlnrb("Critical", vln_list("critical")), + render_vlnrb("High", vln_list("high")), + render_vlnrb("Medium", vln_list("medium")), + render_vlnrb("Low", vln_list("low")), + render_vlnrb("Negligible", vln_list("negligible")), + with_default(input,"response_policy_name", ""), + with_default(input,"application_scope", "none"), + by_flag( + "", + sprintf(`

See more: %s

`,[href, text]), #link + server_url == "") + ]) +} + +result=sprintf("%s,",[{"text":sprintf("%s",[content]) }]) + +result_date = input.scan_started.seconds + +result_category = "Serverless functions Scanning" if { + report_type == "function" +}else = "Security - VM Scan results" if { + report_type == "vm" +}else = "Security Image Scan results" + +result_subcategory = "Security incident" +result_assigned_to := by_flag(input.application_scope_owners[0], "", count(input.application_scope_owners) == 1) +result_assigned_group := by_flag(input.application_scope[0], "", count(input.application_scope) == 1) + +result_severity := 1 if { + input.vulnerability_summary.critical > 0 +} else = 2 if { + input.vulnerability_summary.high > 0 +} else = 3 + +result_summary := summary{ + summary = sprintf(summary_tpl,[ + input.image, + input.registry, + by_flag( + "Malware found: Yes", + "Malware found: No", + input.scan_options.scan_malware #reflects current logic + ), + by_flag( + "Sensitive data found: Yes", + "Sensitive data found: No", + input.scan_options.scan_sensitive_data #reflects current logic + ), + vulnerability_summary_critical, + vulnerability_summary_high, + vulnerability_summary_medium, + vulnerability_summary_low, + vulnerability_summary_negligible, + by_flag( + "", + sprintf(`See more: %s`,[text]), #link + server_url == ""), + ]) +} \ No newline at end of file From adfdf086ea58af96a9f6ae75f3d61f3257243bda Mon Sep 17 00:00:00 2001 From: afdesk Date: Mon, 13 Mar 2023 12:22:26 +0600 Subject: [PATCH 3/7] update swit-text rego template --- rego-templates/swit-text.rego | 67 ++++++++++++----------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/rego-templates/swit-text.rego b/rego-templates/swit-text.rego index 27c04f69..9ca1e800 100644 --- a/rego-templates/swit-text.rego +++ b/rego-templates/swit-text.rego @@ -7,31 +7,22 @@ import data.postee.by_flag import data.postee.with_default ################################################ Templates ################################################ -#main template to render message -html_tpl:=` -Name: %s -

Registry: %s

-

Malware found: %s

-

Sensitive data found: %s

- -

Vulnerability summary

-%s - +swit_tpl:=`Name: %s +Registry: %s +Malware found: %s +Sensitive data found: %s + +Vulnerability summary %s - %s - %s - %s - %s - %s -

Resourse policy name: %s

-

Resourse policy application scopes: %s

%s -` +Resourse policy name: %s +Resourse policy application scopes: %s +%s` summary_tpl =`Name: %s Registry: %s @@ -47,39 +38,26 @@ vulnerabilities: %s` -vlnrb_tpl = ` -

%s severity vulnerabilities

-%s -` +vlnrb_tpl = `%s severity vulnerabilities +%s` -assurance_control_tpl = ` -

Assurance controls

-%s -` +assurance_control_tpl = `Assurance controls +%s` #Extra % is required in width:100% -table_tpl:=` - -%s -
-` +table_tpl:=`%s` -cell_tpl:=`%s -` +cell_tpl:=`%s |` -header_tpl:=`%s -` +header_tpl:=` %s |` -row_tpl:=` - -%s -` +row_tpl:=`| %s +` -colored_text_tpl:="%s" +colored_text_tpl:="%s" ########################################################################################################### - -############################################## Html rendering ############################################# +############################################## Template rendering ######################################### render_table_headers(headers) = row { count(headers) > 0 @@ -116,7 +94,8 @@ to_cell(txt) = c { } to_colored_text(color, txt) = spn { - spn :=sprintf(colored_text_tpl, [color, txt]) + spn :=sprintf(colored_text_tpl, [txt]) +# spn :=sprintf(colored_text_tpl, [color, txt]) } ####################################### Template specific functions ####################################### @@ -233,7 +212,7 @@ aggregation_pkg := "postee.vuls.html.aggregation" ############################################## result values ############################################# content = msg { - msg := sprintf(html_tpl, [ + msg := sprintf(swit_tpl, [ input.image, input.registry, by_flag( From 6291b2eb029cf92307b12a5c2ee548416081a009 Mon Sep 17 00:00:00 2001 From: AMF Date: Fri, 31 Mar 2023 20:39:16 +0600 Subject: [PATCH 4/7] docs: add config --- docs/blueprints/swit-integration.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/blueprints/swit-integration.md diff --git a/docs/blueprints/swit-integration.md b/docs/blueprints/swit-integration.md new file mode 100644 index 00000000..a5c6e1d5 --- /dev/null +++ b/docs/blueprints/swit-integration.md @@ -0,0 +1,12 @@ +# Configure your SWIT App + +At first, you need to get Swit incoming webhook. The documentation is [here](https://help.swit.io/swit-store/webhook). + +Then you can use this webhook as a Postee's action: + +```yaml +- name: swit + type: webhook + enable: true + url: https://hook.swit.io/chat// +``` From 399ad57f335e7454e1aead11b0347d4b6e6e6f86 Mon Sep 17 00:00:00 2001 From: AMF Date: Fri, 31 Mar 2023 20:41:07 +0600 Subject: [PATCH 5/7] fix a title --- docs/blueprints/swit-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blueprints/swit-integration.md b/docs/blueprints/swit-integration.md index a5c6e1d5..f2823a89 100644 --- a/docs/blueprints/swit-integration.md +++ b/docs/blueprints/swit-integration.md @@ -1,4 +1,4 @@ -# Configure your SWIT App +# Configure your SWIT action At first, you need to get Swit incoming webhook. The documentation is [here](https://help.swit.io/swit-store/webhook). From 0e0f01f7c244f042919bb8eb0e0a5f2bff37a076 Mon Sep 17 00:00:00 2001 From: AMF Date: Fri, 31 Mar 2023 21:17:02 +0600 Subject: [PATCH 6/7] update config file --- cfg.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cfg.yaml b/cfg.yaml index 72b93afd..f2f77cef 100644 --- a/cfg.yaml +++ b/cfg.yaml @@ -12,9 +12,9 @@ routes: actions: [ stdout ] template: raw-json -- name: stdout2 - actions: [ stdout ] - template: swit-json +- name: webhook-swit + actions: [ webhook ] + template: swit-text #- name: route1 # Route name. Must be unique # input: contains(input.image, "alpine") # REGO rule to match input message against route From 7bf827853811ce69e4aeebcb58ce8e572fa3871b Mon Sep 17 00:00:00 2001 From: AMF Date: Fri, 31 Mar 2023 21:18:44 +0600 Subject: [PATCH 7/7] fix: update `see more` link --- rego-templates/swit-text.rego | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rego-templates/swit-text.rego b/rego-templates/swit-text.rego index 9ca1e800..804b0fae 100644 --- a/rego-templates/swit-text.rego +++ b/rego-templates/swit-text.rego @@ -236,7 +236,7 @@ content = msg { with_default(input,"application_scope", "none"), by_flag( "", - sprintf(`

See more: %s

`,[href, text]), #link + sprintf(`See more: %s`,[href]), #link server_url == "") ]) }