From 61a5cbed0ff7619fc9b5bda40b21c146d3b2984e Mon Sep 17 00:00:00 2001 From: Kevin Zhu Date: Wed, 6 Mar 2024 12:59:49 +1100 Subject: [PATCH] feat: dashboard improvements: - unwound SCA vulnerabilities in summary; - format CWEs in SAST; - Better out of the box experience; - Update grafana to 10.3.0 to match client --- docker-compose.yaml | 2 +- example/Nullify Demo Dashboard.json | 1233 +++++++++++++++++---------- src/api/sastEvents.ts | 7 - src/api/sastSummary.ts | 4 +- src/api/scaCommon.ts | 10 +- src/api/scaEvents.ts | 7 - src/api/scaSummary.ts | 115 ++- src/types.ts | 8 +- 8 files changed, 908 insertions(+), 478 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 79a1339..945a0ab 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,7 +8,7 @@ services: context: ./.config args: grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise} - grafana_version: ${GRAFANA_VERSION:-10.2.0} + grafana_version: ${GRAFANA_VERSION:-10.3.0} ports: - 3000:3000/tcp volumes: diff --git a/example/Nullify Demo Dashboard.json b/example/Nullify Demo Dashboard.json index 842fe34..713a951 100644 --- a/example/Nullify Demo Dashboard.json +++ b/example/Nullify Demo Dashboard.json @@ -1,8 +1,8 @@ { "__inputs": [ { - "name": "DS_NULLIFY", - "label": "Nullify", + "name": "DS_NULLIFY_DATASOURCE", + "label": "Nullify Datasource", "description": "", "type": "datasource", "pluginId": "nullify-grafana-datasource", @@ -21,7 +21,7 @@ "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "10.1.0" + "version": "10.3.0" }, { "type": "datasource", @@ -62,7 +62,7 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -70,6 +70,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -98,93 +99,16 @@ "value": null } ] - } + }, + "unitScale": true }, "overrides": [ - { - "matcher": { - "id": "byName", - "options": "S1 - CRITICAL" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "S2 - HIGH" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-orange", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "S3 - MEDIUM" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-yellow", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "S4 - LOW" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "dark-blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "UNKNOWN" - }, - "properties": [ - { - "id": "color", - "value": { - "mode": "fixed" - } - } - ] - }, { "matcher": { "id": "byName", "options": "Total" }, "properties": [ - { - "id": "custom.axisPlacement", - "value": "hidden" - }, { "id": "custom.hideFrom", "value": { @@ -203,7 +127,7 @@ "x": 0, "y": 0 }, - "id": 2, + "id": 10, "options": { "barRadius": 0, "barWidth": 0.97, @@ -232,7 +156,7 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "endpoint": "sast/summary", "queryParameters": { @@ -243,16 +167,37 @@ "refId": "A" } ], - "title": "SAST - By Language and Severity", + "title": "SAST - CWE by Language", "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "equal", + "options": { + "value": "CWE-000" + } + }, + "fieldName": "cwe" + } + ], + "match": "any", + "type": "exclude" + } + }, { "id": "groupBy", "options": { "fields": { - "formatted_severity": { + "cwe": { "aggregations": [], "operation": "groupby" }, + "formatted_severity": { + "aggregations": [] + }, "id": { "aggregations": [ "count" @@ -269,9 +214,9 @@ { "id": "groupingToMatrix", "options": { - "columnField": "formatted_severity", + "columnField": "language", "emptyValue": "null", - "rowField": "language", + "rowField": "cwe", "valueField": "id (count)" } }, @@ -280,13 +225,7 @@ "options": { "mode": "reduceRow", "reduce": { - "include": [ - "S3 - MEDIUM", - "S2 - HIGH", - "S4 - LOW", - "UNKNOWN", - "S1 - CRITICAL" - ], + "include": [], "reducer": "sum" }, "replaceFields": false @@ -298,6 +237,7 @@ "excludeByName": { "Total": false }, + "includeByName": {}, "indexByName": { "S1 - CRITICAL": 6, "S2 - HIGH": 5, @@ -308,11 +248,24 @@ "language\\formatted_severity": 1 }, "renameByName": { + "Go": "", "S4 - LOW": "", "UNKNOWN": "", + "cwe\\language": "CWE ID", "language\\formatted_severity": "Language" } } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "CWE ID" + } + ] + } } ], "type": "barchart" @@ -320,7 +273,7 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -328,6 +281,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -371,7 +325,8 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, @@ -381,7 +336,7 @@ "x": 12, "y": 0 }, - "id": 6, + "id": 4, "options": { "legend": { "calcs": [], @@ -394,14 +349,13 @@ "sort": "none" } }, - "pluginVersion": "10.3.1", "targets": [ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "secrets/events", + "endpoint": "sast/events", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -410,60 +364,21 @@ "refId": "A" } ], - "title": "Secrets Detection - First committed last 7 days", - "transformations": [ - { - "id": "filterByValue", - "options": { - "filters": [ - { - "config": { - "id": "greater", - "options": { - "value": 0 - } - }, - "fieldName": "finding_firstCommitTimestamp" - } - ], - "match": "any", - "type": "include" - } - }, - { - "id": "groupBy", - "options": { - "fields": { - "finding_firstCommitTimestamp": { - "aggregations": [], - "operation": "groupby" - }, - "finding_timeStamp": { - "aggregations": [] - }, - "id": { - "aggregations": [ - "count" - ], - "operation": "aggregate" - } - } - } - } - ], + "title": "SAST - Findings over time", "type": "timeseries" }, { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -490,93 +405,224 @@ { "color": "green", "value": null - }, - { - "color": "red", - "value": 80 } ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 10 - }, - "id": 1, - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 45, - "xTickLabelSpacing": 0 - }, - "pluginVersion": "10.0.3", - "targets": [ - { - "constant": 6.5, - "datasource": { - "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" - }, - "endpoint": "sast/summary", - "queryParameters": { - "githubRepositoryIdsOrQueries": [ - "$repository" - ] }, - "refId": "A" - } - ], - "title": "SAST - By Severity", - "transformations": [ - { - "id": "groupBy", - "options": { - "fields": { - "formatted_severity": { - "aggregations": [], - "operation": "groupby" - }, - "id": { - "aggregations": [ - "count" - ], - "operation": "aggregate" - }, - "severity": { - "aggregations": [] - } - } - } + "unitScale": true }, - { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "S1 - CRITICAL" + }, + "properties": [ { - "field": "formatted_severity" + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } } ] - } + }, + { + "matcher": { + "id": "byName", + "options": "S2 - HIGH" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "S3 - MEDIUM" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "S4 - LOW" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "hidden" + }, + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": true, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "text": { + "valueSize": 12 + }, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "datasource": { + "type": "nullify-grafana-datasource", + "uid": "${DS_NULLIFY_DATASOURCE}" + }, + "endpoint": "sast/summary", + "queryParameters": { + "githubRepositoryIdsOrQueries": [ + "$repository" + ] + }, + "refId": "A" + } + ], + "title": "SAST - By Language and Severity", + "transformations": [ + { + "id": "groupBy", + "options": { + "fields": { + "formatted_severity": { + "aggregations": [], + "operation": "groupby" + }, + "id": { + "aggregations": [ + "count" + ], + "operation": "aggregate" + }, + "language": { + "aggregations": [], + "operation": "groupby" + } + } + } + }, + { + "id": "groupingToMatrix", + "options": { + "columnField": "formatted_severity", + "emptyValue": "null", + "rowField": "language", + "valueField": "id (count)" + } + }, + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "include": [ + "S3 - MEDIUM", + "S2 - HIGH", + "S4 - LOW", + "UNKNOWN", + "S1 - CRITICAL" + ], + "reducer": "sum" + }, + "replaceFields": false + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Total": false + }, + "indexByName": { + "S1 - CRITICAL": 6, + "S2 - HIGH": 5, + "S3 - MEDIUM": 4, + "S4 - LOW": 3, + "Total": 0, + "UNKNOWN": 2, + "language\\formatted_severity": 1 + }, + "renameByName": { + "S4 - LOW": "", + "UNKNOWN": "", + "language\\formatted_severity": "Language" + } + } } ], "type": "barchart" @@ -584,7 +630,7 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -592,6 +638,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -631,41 +678,170 @@ "value": null }, { - "color": "red", - "value": 80 + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "nullify-grafana-datasource", + "uid": "${DS_NULLIFY_DATASOURCE}" + }, + "endpoint": "sca/events", + "queryParameters": { + "eventTypes": [ + "new-branch-summary" + ], + "githubRepositoryIdsOrQueries": [ + "$repository" + ] + }, + "refId": "A" + } + ], + "title": "SCA - Findings over time", + "type": "timeseries" + }, + { + "datasource": { + "type": "nullify-grafana-datasource", + "uid": "${DS_NULLIFY_DATASOURCE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Total" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": false, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "palette-classic" + } } ] } - }, - "overrides": [] + ] }, "gridPos": { - "h": 9, + "h": 10, "w": 12, - "x": 12, - "y": 10 + "x": 0, + "y": 20 }, - "id": 8, + "id": 13, "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": true, + "groupWidth": 0.7, "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "text": { + "valueSize": 12 + }, "tooltip": { "mode": "single", "sort": "none" - } + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 }, - "pluginVersion": "10.3.1", + "pluginVersion": "10.3.0", "targets": [ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "secrets/events", + "endpoint": "sca/summary", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -674,7 +850,7 @@ "refId": "A" } ], - "title": "Secrets Detection - Detections last 7 days", + "title": "SCA - By CVE and Package", "transformations": [ { "id": "filterByValue", @@ -682,45 +858,102 @@ "filters": [ { "config": { - "id": "greater", - "options": { - "value": 0 - } + "id": "isNull", + "options": {} }, - "fieldName": "finding_timeStamp" + "fieldName": "vulnerabilityCveId" } ], "match": "any", - "type": "include" + "type": "exclude" } }, { "id": "groupBy", "options": { "fields": { - "finding_firstCommitTimestamp": { - "aggregations": [] - }, - "finding_timeStamp": { + "cwe": { "aggregations": [], "operation": "groupby" }, + "formatted_severity": { + "aggregations": [] + }, "id": { "aggregations": [ "count" ], "operation": "aggregate" + }, + "language": { + "aggregations": [], + "operation": "groupby" + }, + "package": { + "aggregations": [], + "operation": "groupby" + }, + "vulnerabilityCveId": { + "aggregations": [], + "operation": "groupby" } } } + }, + { + "id": "groupingToMatrix", + "options": { + "columnField": "package", + "emptyValue": "null", + "rowField": "vulnerabilityCveId", + "valueField": "id (count)" + } + }, + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "include": [], + "reducer": "sum" + }, + "replaceFields": false + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Total": false + }, + "includeByName": {}, + "indexByName": { + "S1 - CRITICAL": 6, + "S2 - HIGH": 5, + "S3 - MEDIUM": 4, + "S4 - LOW": 3, + "Total": 0, + "UNKNOWN": 2, + "language\\formatted_severity": 1 + }, + "renameByName": { + "Go": "", + "S4 - LOW": "", + "Total": "", + "UNKNOWN": "", + "cwe\\language": "CWE ID", + "language\\formatted_severity": "Language", + "vulnerabilityCveId\\package": "" + } + } } ], - "type": "timeseries" + "type": "barchart" }, { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -728,21 +961,33 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", - "fillOpacity": 80, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", "lineWidth": 1, + "pointSize": 5, "scaleDistribution": { "type": "linear" }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, "thresholdsStyle": { "mode": "off" } @@ -760,46 +1005,38 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, "gridPos": { "h": 10, "w": 12, - "x": 0, - "y": 19 + "x": 12, + "y": 20 }, - "id": 3, + "id": 6, "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, - "orientation": "auto", - "showValue": "auto", - "stacking": "none", "tooltip": { "mode": "single", "sort": "none" - }, - "xTickLabelMaxLength": 200, - "xTickLabelRotation": 45, - "xTickLabelSpacing": 0 + } }, + "pluginVersion": "10.3.1", "targets": [ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "sast/summary", + "endpoint": "secrets/events", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -808,17 +1045,35 @@ "refId": "A" } ], - "title": "SAST - By CWE", + "title": "Secrets Detection - First committed last 7 days", "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "greater", + "options": { + "value": 0 + } + }, + "fieldName": "finding_firstCommitTimestamp" + } + ], + "match": "any", + "type": "include" + } + }, { "id": "groupBy", "options": { "fields": { - "cwe": { + "finding_firstCommitTimestamp": { "aggregations": [], "operation": "groupby" }, - "filePath": { + "finding_timeStamp": { "aggregations": [] }, "id": { @@ -829,37 +1084,14 @@ } } } - }, - { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "cwe" - } - ] - } - }, - { - "id": "convertFieldType", - "options": { - "conversions": [ - { - "destinationType": "string", - "targetField": "cwe" - } - ], - "fields": {} - } } ], - "type": "barchart" + "type": "timeseries" }, { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -867,6 +1099,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -891,29 +1124,121 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "CRITICAL" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "HIGH" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "MEDIUM" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "LOW" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "UNKNOWN" + }, + "properties": [ + { + "id": "color", + "value": { + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "hidden" }, { - "color": "red", - "value": 80 + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": false, + "viz": true + } } ] } - }, - "overrides": [] + ] }, "gridPos": { "h": 10, "w": 12, - "x": 12, - "y": 19 + "x": 0, + "y": 30 }, - "id": 5, + "id": 15, "options": { "barRadius": 0, "barWidth": 0.97, - "fullHighlight": false, + "fullHighlight": true, "groupWidth": 0.7, "legend": { "calcs": [], @@ -922,8 +1247,11 @@ "showLegend": true }, "orientation": "auto", - "showValue": "auto", - "stacking": "none", + "showValue": "never", + "stacking": "normal", + "text": { + "valueSize": 12 + }, "tooltip": { "mode": "single", "sort": "none" @@ -935,9 +1263,9 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "secrets/summary", + "endpoint": "sca/summary", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -946,24 +1274,105 @@ "refId": "A" } ], - "title": "Secret Detection - By Secret Type", + "title": "SCA - Open Vulns by Package with Fix", "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "equal", + "options": { + "value": true + } + }, + "fieldName": "vulnerabilityHasFix" + } + ], + "match": "any", + "type": "include" + } + }, { "id": "groupBy", "options": { "fields": { + "formatted_severity": { + "aggregations": [], + "operation": "groupby" + }, "id": { "aggregations": [ - "distinctCount" + "count" ], "operation": "aggregate" }, - "secretType": { + "language": { + "aggregations": [], + "operation": "groupby" + }, + "package": { + "aggregations": [], + "operation": "groupby" + }, + "vulnerabilitySeverity": { "aggregations": [], "operation": "groupby" } } } + }, + { + "id": "groupingToMatrix", + "options": { + "columnField": "vulnerabilitySeverity", + "emptyValue": "null", + "rowField": "package", + "valueField": "id (count)" + } + }, + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "include": [ + "S3 - MEDIUM", + "S2 - HIGH", + "S4 - LOW", + "UNKNOWN", + "S1 - CRITICAL" + ], + "reducer": "sum" + }, + "replaceFields": false + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Total": false + }, + "includeByName": {}, + "indexByName": { + "S1 - CRITICAL": 6, + "S2 - HIGH": 5, + "S3 - MEDIUM": 4, + "S4 - LOW": 3, + "Total": 0, + "UNKNOWN": 2, + "language\\formatted_severity": 1 + }, + "renameByName": { + "CRITICAL": "", + "S4 - LOW": "", + "UNKNOWN": "", + "language\\formatted_severity": "Language", + "package\\vulnerabilitySeverity": "" + } + } } ], "type": "barchart" @@ -971,7 +1380,7 @@ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { @@ -979,6 +1388,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1022,17 +1432,18 @@ "value": 80 } ] - } + }, + "unitScale": true }, "overrides": [] }, "gridPos": { "h": 10, "w": 12, - "x": 0, - "y": 29 + "x": 12, + "y": 30 }, - "id": 4, + "id": 8, "options": { "legend": { "calcs": [], @@ -1045,13 +1456,14 @@ "sort": "none" } }, + "pluginVersion": "10.3.1", "targets": [ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "sast/events", + "endpoint": "secrets/events", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -1060,21 +1472,61 @@ "refId": "A" } ], - "title": "SAST - Findings over time", + "title": "Secrets Detection - Detections last 7 days", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "greater", + "options": { + "value": 0 + } + }, + "fieldName": "finding_timeStamp" + } + ], + "match": "any", + "type": "include" + } + }, + { + "id": "groupBy", + "options": { + "fields": { + "finding_firstCommitTimestamp": { + "aggregations": [] + }, + "finding_timeStamp": { + "aggregations": [], + "operation": "groupby" + }, + "id": { + "aggregations": [ + "count" + ], + "operation": "aggregate" + } + } + } + } + ], "type": "timeseries" }, { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "fieldConfig": { "defaults": { "color": { - "fixedColor": "text", "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1101,80 +1553,24 @@ { "color": "green", "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "numLow (sum)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "numCritical (sum)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "red", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "numMedium (sum)" - }, - "properties": [ + }, { - "id": "color", - "value": { - "fixedColor": "yellow", - "mode": "fixed" - } + "color": "red", + "value": 80 } ] }, - { - "matcher": { - "id": "byName", - "options": "numHigh (sum)" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "orange", - "mode": "fixed" - } - } - ] - } - ] + "unitScale": true + }, + "overrides": [] }, "gridPos": { "h": 10, "w": 12, - "x": 12, - "y": 29 + "x": 0, + "y": 40 }, - "id": 9, + "id": 5, "options": { "barRadius": 0, "barWidth": 0.97, @@ -1188,7 +1584,7 @@ }, "orientation": "auto", "showValue": "auto", - "stacking": "normal", + "stacking": "none", "tooltip": { "mode": "single", "sort": "none" @@ -1196,14 +1592,13 @@ "xTickLabelRotation": 45, "xTickLabelSpacing": 0 }, - "pluginVersion": "10.3.1", "targets": [ { "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, - "endpoint": "sca/summary", + "endpoint": "secrets/summary", "queryParameters": { "githubRepositoryIdsOrQueries": [ "$repository" @@ -1212,69 +1607,31 @@ "refId": "A" } ], - "title": "SCA - Open vulns by package", + "title": "Secret Detection - By Secret Type", "transformations": [ { "id": "groupBy", "options": { "fields": { - "numCritical": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - }, - "numHigh": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - }, - "numLow": { - "aggregations": [ - "sum" - ], - "operation": "aggregate" - }, - "numMedium": { + "id": { "aggregations": [ - "sum" + "distinctCount" ], "operation": "aggregate" }, - "numVulnerabilities": { - "aggregations": [], - "operation": "aggregate" - }, - "package": { + "secretType": { "aggregations": [], "operation": "groupby" } } } - }, - { - "id": "organize", - "options": { - "excludeByName": {}, - "includeByName": {}, - "indexByName": { - "numCritical (sum)": 4, - "numHigh (sum)": 3, - "numLow (sum)": 1, - "numMedium (sum)": 2, - "package": 0 - }, - "renameByName": {} - } } ], "type": "barchart" } ], "refresh": "", - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [], "templating": { "list": [ @@ -1282,7 +1639,7 @@ "current": {}, "datasource": { "type": "nullify-grafana-datasource", - "uid": "${DS_NULLIFY}" + "uid": "${DS_NULLIFY_DATASOURCE}" }, "definition": "Nullify Repository Query", "description": "Filter for the repositories for which to query data", @@ -1311,6 +1668,6 @@ "timezone": "", "title": "Nullify Demo Dashboard", "uid": "ecf0f5c8-bf81-479a-b72f-94b5c2855d04", - "version": 2, + "version": 15, "weekStart": "" } \ No newline at end of file diff --git a/src/api/sastEvents.ts b/src/api/sastEvents.ts index 2a1bbeb..ffcd240 100644 --- a/src/api/sastEvents.ts +++ b/src/api/sastEvents.ts @@ -340,13 +340,6 @@ export const processSastEvents = async ( type: FieldType.string, values: events.map((event) => event.type), }, - { - name: 'numFindings', - type: FieldType.number, - values: events.map((event) => - event.type === SastEventType.NewBranchSummary ? event.data.numFindings : undefined - ), - }, { name: 'numCritical', type: FieldType.number, diff --git a/src/api/sastSummary.ts b/src/api/sastSummary.ts index 9c270af..9187d73 100644 --- a/src/api/sastSummary.ts +++ b/src/api/sastSummary.ts @@ -66,8 +66,8 @@ export const processSastSummary = async ( }, { name: 'cwe', - type: FieldType.number, - values: parseResult.data.vulnerabilities.map((vuln) => vuln.cwe), + type: FieldType.string, + values: parseResult.data.vulnerabilities.map((vuln) => `CWE-${vuln.cwe.toString().padStart(3, '0')}`), }, { name: 'language', diff --git a/src/api/scaCommon.ts b/src/api/scaCommon.ts index abbfc2f..3d4c727 100644 --- a/src/api/scaCommon.ts +++ b/src/api/scaCommon.ts @@ -31,10 +31,10 @@ export const ScaEventsDependencyFinding = z.object({ version: z.string().optional(), filePath: z.string().optional(), line: z.number().optional(), - numCritical: z.number().optional(), - numHigh: z.number().optional(), - numMedium: z.number().optional(), - numLow: z.number().optional(), - numUnknown: z.number().optional(), + numCritical: z.number().default(0), + numHigh: z.number().default(0), + numMedium: z.number().default(0), + numLow: z.number().default(0), + numUnknown: z.number().default(0), vulnerabilities: z.array(ScaEventsVulnerability).optional().nullable(), }); diff --git a/src/api/scaEvents.ts b/src/api/scaEvents.ts index 6fd2c97..1324131 100644 --- a/src/api/scaEvents.ts +++ b/src/api/scaEvents.ts @@ -288,13 +288,6 @@ export const processScaEvents = async ( type: FieldType.string, values: events.map((event) => event.type), }, - { - name: 'numFindings', - type: FieldType.number, - values: events.map((event) => - event.type === ScaEventType.NewBranchSummary ? event.data.numFindings : undefined - ), - }, { name: 'numCritical', type: FieldType.number, diff --git a/src/api/scaSummary.ts b/src/api/scaSummary.ts index c6c8c69..9fa7e72 100644 --- a/src/api/scaSummary.ts +++ b/src/api/scaSummary.ts @@ -15,6 +15,29 @@ interface ScaSummaryApiRequest { severity?: string; } +export interface UnwoundScaEventsDependencyFinding { + id: string | undefined; + isDirect: boolean | undefined; + package: string | undefined; + packageFilePath: string | undefined; + version: string | undefined; + filePath: string | undefined; + line: number | undefined; + numCritical: number | undefined; + numHigh: number | undefined; + numMedium: number | undefined; + numLow: number | undefined; + numUnknown: number | undefined; + vulnerabilityHasFix: boolean | undefined; + vulnerabilityTitle: string | undefined; + vulnerabilitySeverity: string | undefined; + vulnerabilityCveId: string | undefined; + vulnerabilityCwe: string[] | undefined; + vulnerabilityIntroduced: string | undefined; + vulnerabilityFixed: string | undefined; + vulnerabilityVersion: string | undefined; +} + export const processScaSummary = async ( queryOptions: ScaSummaryQueryOptions, request_fn: (endpoint_path: string, params?: Record) => Promise> @@ -46,63 +69,127 @@ export const processScaSummary = async ( }; } + const unwoundFindings = parseResult.data.vulnerabilities?.flatMap(finding => { + let result: UnwoundScaEventsDependencyFinding[] = []; + for (const vuln of finding.vulnerabilities ?? []) { + result.push({ + id: finding.id, + isDirect: finding.isDirect, + package: finding.package, + packageFilePath: finding.packageFilePath, + version: finding.version, + filePath: finding.filePath, + line: finding.line, + numCritical: finding.numCritical, + numHigh: finding.numHigh, + numMedium: finding.numMedium, + numLow: finding.numLow, + numUnknown: finding.numUnknown, + vulnerabilityHasFix: vuln.hasFix, + vulnerabilityTitle: vuln.title, + vulnerabilitySeverity: vuln.severity, + vulnerabilityCveId: vuln.cves?.find((cve) => cve.id?.startsWith('CVE'))?.id, + vulnerabilityCwe: vuln.cwes ?? undefined, + vulnerabilityIntroduced: vuln.introduced, + vulnerabilityFixed: vuln.fixed, + vulnerabilityVersion: vuln.version, + }); + } + return result + }); + return createDataFrame({ refId: queryOptions.refId, fields: [ { name: 'id', type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.id), + values: unwoundFindings?.map((vuln) => vuln.id), }, { name: 'isDirect', - type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.isDirect), + type: FieldType.boolean, + values: unwoundFindings?.map((vuln) => vuln.isDirect), }, { name: 'package', type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.package), + values: unwoundFindings?.map((vuln) => vuln.package), }, { name: 'packageFilePath', type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.packageFilePath), + values: unwoundFindings?.map((vuln) => vuln.packageFilePath), }, { name: 'version', type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.version), + values: unwoundFindings?.map((vuln) => vuln.version), }, { name: 'filePath', type: FieldType.string, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.filePath), + values: unwoundFindings?.map((vuln) => vuln.filePath), }, { name: 'numCritical', type: FieldType.number, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.numCritical ?? 0), + values: unwoundFindings?.map((vuln) => vuln.numCritical), }, { name: 'numHigh', type: FieldType.number, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.numHigh ?? 0), + values: unwoundFindings?.map((vuln) => vuln.numHigh), }, { name: 'numMedium', type: FieldType.number, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.numMedium ?? 0), + values: unwoundFindings?.map((vuln) => vuln.numMedium), }, { name: 'numLow', type: FieldType.number, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.numLow ?? 0), + values: unwoundFindings?.map((vuln) => vuln.numLow), }, { - name: 'numVulnerabilities', - type: FieldType.number, - values: parseResult.data.vulnerabilities?.map((vuln) => vuln.vulnerabilities?.length ?? 0), + name: 'vulnerabilityHasFix', + type: FieldType.boolean, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityHasFix), + }, + { + name: 'vulnerabilityTitle', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityTitle), + }, + { + name: 'vulnerabilitySeverity', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilitySeverity), + }, + { + name: 'vulnerabilityCveId', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityCveId ?? null), + }, + { + name: 'vulnerabilityCwe', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityCwe ?? null), + }, + { + name: 'vulnerabilityIntroduced', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityIntroduced), + }, + { + name: 'vulnerabilityFixed', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityFixed), + }, + { + name: 'vulnerabilityVersion', + type: FieldType.string, + values: unwoundFindings?.map((vuln) => vuln.vulnerabilityVersion), }, ], }); diff --git a/src/types.ts b/src/types.ts index ab8d326..cb10531 100644 --- a/src/types.ts +++ b/src/types.ts @@ -192,10 +192,10 @@ export enum SecretsEventType { } export const SecretsEventTypeDescriptions: Record = { - [ScaEventType.NewFinding]: 'New Finding', - [ScaEventType.NewFindings]: 'New Findings', - [ScaEventType.NewAllowlistedFinding]: 'New Allowlisted Finding', - [ScaEventType.NewAllowlistedFindings]: 'New Allowlisted Findings', + [SecretsEventType.NewFinding]: 'New Finding', + [SecretsEventType.NewFindings]: 'New Findings', + [SecretsEventType.NewAllowlistedFinding]: 'New Allowlisted Finding', + [SecretsEventType.NewAllowlistedFindings]: 'New Allowlisted Findings', }; export type NullifyQueryOptions =