diff --git a/.gitignore b/.gitignore index 77d11c509f..985aa89d53 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,29 @@ *.iml *.ipr out/ + +Created by https://www.toptal.com/developers/gitignore/api/vim +# Edit at https://www.toptal.com/developers/gitignore?templates=vim + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim diff --git a/hooks/notification/.helm-docs.gotmpl b/hooks/notification/.helm-docs.gotmpl index e17f5d7ba0..6723c08bdb 100644 --- a/hooks/notification/.helm-docs.gotmpl +++ b/hooks/notification/.helm-docs.gotmpl @@ -46,6 +46,7 @@ Please take a look at the documentation for each type (e.g. for slack see [Confi - [Slack](#configuration-of-a-slack-notification) - [Slack App](#configuration-of-a-slack-app-notification) - [Email](#configuration-of-an-email-notification) +- [MS Teams](#configuration-of-a-ms-teams-notification) ### Configuration of a Notification @@ -204,7 +205,7 @@ This configuration needs to be specified under `env` in the values yaml. The identifier for this config has to be `SMTP_CONFIG`. A basic configuration could look like this: -``` +```yaml notificationChannels: - name: email type: email @@ -227,6 +228,37 @@ env: value: secureCodeBox ``` +### Configuration Of A MS Teams Notification + +To configure a MS Teams notification you need to set the type to `ms-teams`. +In `endPoint` you need to specify the MS Teams webhook. +To use the template provided by the secureCodeBox set template to `msteams-messageCard`. + +The default template allows you to specify an additional set of information. +If you use an external web based vulnerability management system with some kind of dashboard, you can set the variable `VULNMANAG_ENABLED` to true and point the `VULNMANAG_DASHBOARD_URL` to the URL of your vulnerability management. +This will add a button in the notification that links directly to your dashboard. +You can also add a button that opens your findings directly in your dashboard. +To do this you need to specify `dashboardFingingsUrl`. +You will have to replace the id of the scan in this url with `{{ `{{ uid }}` }}` so that nunjucks can parse these urls. + +A basic configuration could look like this: + +```yaml +notificationChannels: + - name: ms-teams + type: ms-teams + template: msteams-messageCard + rules: [] + endPoint: "https://somewhere.xyz/sadf12" +env: + - name: VULNMANAG_ENABLED + value: true + - name: VULNMANAG_DASHBOARD_URL + value: "somedashboard.url" + - name: VULNMANAG_DASHBOARD_FINDINGS_URL + value: "somedashboard.url/findings/{{ `{{ uid }}` }}" +``` + ### Custom Message Templates CAUTION: Nunjucks templates allow code to be injected! Use templates from trusted sources only! @@ -244,4 +276,4 @@ To fill your template with data we provide the following objects. {{- end }} {{- define "extra.scannerLinksSection" -}} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/hooks/notification/NotifierFactory.test.ts b/hooks/notification/NotifierFactory.test.ts index 7d793ba51b..24db902caf 100644 --- a/hooks/notification/NotifierFactory.test.ts +++ b/hooks/notification/NotifierFactory.test.ts @@ -7,6 +7,7 @@ import { NotificationChannel } from "./model/NotificationChannel"; import { Scan } from "./model/Scan"; import { NotifierFactory } from "./NotifierFactory" import { SlackNotifier } from "./Notifiers/SlackNotifier"; +import { MSTeamsNotifier } from "./Notifiers/MSTeamsNotifier"; import { NotifierType } from "./NotifierType"; const finding: Finding = { @@ -85,9 +86,7 @@ test("Should Create MS Teams Notifier", async () => { const findings: Finding[] = [] findings.push(finding) - const t = () => { - NotifierFactory.create(chan, scan, findings, []); - } + const s = NotifierFactory.create(chan, scan, findings, []); - expect(t).toThrow("This Type is not Implemented :("); + expect(s instanceof MSTeamsNotifier).toBe(true); }) diff --git a/hooks/notification/NotifierFactory.ts b/hooks/notification/NotifierFactory.ts index 8bf7de6ac3..63380703ff 100644 --- a/hooks/notification/NotifierFactory.ts +++ b/hooks/notification/NotifierFactory.ts @@ -7,6 +7,7 @@ import { NotifierType } from "./NotifierType"; import { SlackNotifier } from "./Notifiers/SlackNotifier"; import { SlackAppNotifier } from "./Notifiers/SlackAppNotifier"; import { EMailNotifier } from "./Notifiers/EMailNotifier"; +import { MSTeamsNotifier } from "./Notifiers/MSTeamsNotifier"; import { NotificationChannel } from "./model/NotificationChannel"; import { Scan } from "./model/Scan"; import { Finding } from "./model/Finding"; @@ -25,6 +26,8 @@ export class NotifierFactory { return new EMailNotifier(channel, scan, findings, args); case NotifierType.SLACK_APP: return new SlackAppNotifier(channel, scan, findings, args); + case NotifierType.MS_TEAMS: + return new MSTeamsNotifier(channel, scan, findings, args); default: throw new Error("This Type is not Implemented :("); } diff --git a/hooks/notification/Notifiers/AbstractNotifier.ts b/hooks/notification/Notifiers/AbstractNotifier.ts index f3138d414b..45f0d58e4d 100644 --- a/hooks/notification/Notifiers/AbstractNotifier.ts +++ b/hooks/notification/Notifiers/AbstractNotifier.ts @@ -50,6 +50,7 @@ export abstract class AbstractNotifier implements Notifier { findings: this.findings, scan: this.scan, args: this.args, + renderString: nunjucks.renderString, } ); try { diff --git a/hooks/notification/Notifiers/AbstractWebHookNotifier.ts b/hooks/notification/Notifiers/AbstractWebHookNotifier.ts new file mode 100644 index 0000000000..cd46691b6d --- /dev/null +++ b/hooks/notification/Notifiers/AbstractWebHookNotifier.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2020 iteratec GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +import { NotifierType } from "../NotifierType" +import { AbstractNotifier } from "./AbstractNotifier" +import { Finding } from "../model/Finding" +import axios from 'axios'; +import { NotificationChannel } from "../model/NotificationChannel"; +import { Scan } from "../model/Scan"; + +export abstract class AbstractWebHookNotifier extends AbstractNotifier { + + protected abstract type: NotifierType; + + constructor(channel: NotificationChannel, scan: Scan, findings: Finding[], args: Object) { + super(channel, scan, findings, args); + } + + public async sendMessage(): Promise { + await this.sendPostRequest(this.renderMessage()); + } + + protected async sendPostRequest(message: string) { + try { + await axios.post(this.channel.endPoint, message) + } catch (e) { + console.log(`There was an Error sending the Message for the "${this.type}": "${this.channel.name}"`); + console.log(e); + } + } +} diff --git a/hooks/notification/Notifiers/MSTeamsNotifier.test.ts b/hooks/notification/Notifiers/MSTeamsNotifier.test.ts new file mode 100644 index 0000000000..bdba89a4ec --- /dev/null +++ b/hooks/notification/Notifiers/MSTeamsNotifier.test.ts @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2020 iteratec GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +import { MSTeamsNotifier } from "./MSTeamsNotifier"; +import axios from 'axios' +import { NotificationChannel } from "../model/NotificationChannel"; +import { NotifierType } from "../NotifierType"; +import { Scan } from "../model/Scan"; + +jest.mock('axios'); + +beforeEach(() => { + jest.clearAllMocks(); +}) + +const channel: NotificationChannel = { + name: "Channel Name", + type: NotifierType.MS_TEAMS, + template: "msteams-messageCard", + rules: [], + endPoint: "https://iteratec.webhook.office.com/webhookb2/f2a7b22a-6558-4db5-8d4b-860f8d4c6848@e96afb08-eeaf-49be-90d6-526571a42d8a/IncomingWebhook/cd8d3c70bb504eb8b44385a8e0ebe6f5/812e82db-e6d9-4651-b0c8-3becfca82658" +}; + +test("Should Send Message With Findings And Severities", async () => { + + const scan: Scan = { + metadata: { + uid: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", + name: "demo-scan-1601086432", + namespace: "my-scans", + creationTimestamp: new Date("2021-01-01T14:29:25Z"), + labels: { + company: "iteratec", + "attack-surface": "external", + }, + }, + spec: { + scanType: "Nmap", + parameters: ["-Pn", "localhost"], + }, + status: { + findingDownloadLink: + "https://my-secureCodeBox-instance.com/scan-b9as-sdweref--sadf-asdfsdf-dasdgf-asdffdsfa7/findings.json", + findings: { + categories: { + "A Client Error response code was returned by the server": 1, + "Information Disclosure - Sensitive Information in URL": 1, + "Strict-Transport-Security Header Not Set": 1, + }, + count: 3, + severities: { + high: 10, + medium: 5, + low: 2, + informational: 1, + }, + }, + finishedAt: new Date("2020-05-25T02:38:13Z"), + rawResultDownloadLink: + "https://my-secureCodeBox-instance.com/scan-blkfsdg-sdgfsfgd-sfg-sdfg-dfsg-gfs98-e8af2172caa7/zap-results.json?Expires=1601691232", + rawResultFile: "zap-results.json", + rawResultType: "zap-json", + state: "Done", + }, + }; + + const teamsNotifier = new MSTeamsNotifier(channel, scan, [], []); + teamsNotifier.sendMessage(); + expect(axios.post).toBeCalled(); +}); + +test("Should Send Minimal Template For Empty Findings", async () => { + const scan: Scan = { + metadata: { + uid: "09988cdf-1fc7-4f85-95ee-1b1d65dbc7cc", + name: "demo-scan-1601086432", + namespace: "my-scans", + creationTimestamp: new Date("2021-01-01T14:29:25Z"), + labels: { + company: "iteratec", + "attack-surface": "external", + }, + }, + spec: { + scanType: "Nmap", + parameters: ["-Pn", "localhost"], + }, + status: { + findingDownloadLink: + "https://my-secureCodeBox-instance.com/scan-b9as-sdweref--sadf-asdfsdf-dasdgf-asdffdsfa7/findings.json", + findings: {}, + finishedAt: new Date("2020-05-25T02:38:13Z"), + rawResultDownloadLink: + "https://my-secureCodeBox-instance.com/scan-blkfsdg-sdgfsfgd-sfg-sdfg-dfsg-gfs98-e8af2172caa7/zap-results.json?Expires=1601691232", + rawResultFile: "zap-results.json", + rawResultType: "zap-json", + state: "Done", + }, + }; + + const n = new MSTeamsNotifier(channel, scan, [], []); + n.sendMessage(); + expect(axios.post).toBeCalled(); +}) diff --git a/hooks/notification/Notifiers/MSTeamsNotifier.ts b/hooks/notification/Notifiers/MSTeamsNotifier.ts new file mode 100644 index 0000000000..5e50920595 --- /dev/null +++ b/hooks/notification/Notifiers/MSTeamsNotifier.ts @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2020 iteratec GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +import { NotifierType } from "../NotifierType" +import { AbstractWebHookNotifier } from "./AbstractWebHookNotifier" +import { Finding } from "../model/Finding" +import axios from 'axios'; +import { NotificationChannel } from "../model/NotificationChannel"; +import { Scan } from "../model/Scan"; + +export class MSTeamsNotifier extends AbstractWebHookNotifier { + + protected type: NotifierType = NotifierType.MS_TEAMS + + constructor(channel: NotificationChannel, scan: Scan, findings: Finding[], args: Object) { + super(channel, scan, findings, args); + } +} diff --git a/hooks/notification/Notifiers/SlackNotifier.ts b/hooks/notification/Notifiers/SlackNotifier.ts index 7980894481..a964b2d0cb 100644 --- a/hooks/notification/Notifiers/SlackNotifier.ts +++ b/hooks/notification/Notifiers/SlackNotifier.ts @@ -3,30 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 import { NotifierType } from "../NotifierType" -import { AbstractNotifier } from "./AbstractNotifier" +import { AbstractWebHookNotifier } from "./AbstractWebHookNotifier" import { Finding } from "../model/Finding" import axios from 'axios'; import { NotificationChannel } from "../model/NotificationChannel"; import { Scan } from "../model/Scan"; -export class SlackNotifier extends AbstractNotifier { +export class SlackNotifier extends AbstractWebHookNotifier { protected type: NotifierType = NotifierType.SLACK constructor(channel: NotificationChannel, scan: Scan, findings: Finding[], args: Object) { super(channel, scan, findings, args); } - - public async sendMessage(): Promise { - await this.sendPostRequest(this.renderMessage()); - } - - protected async sendPostRequest(message: string) { - try { - await axios.post(this.channel.endPoint, message) - } catch (e) { - console.log(`There was an Error sending the Message for the Slack Notifier "${this.channel.name}"`); - console.log(e); - } - } } diff --git a/hooks/notification/README.md b/hooks/notification/README.md index af712a84bf..99d57f7b83 100644 --- a/hooks/notification/README.md +++ b/hooks/notification/README.md @@ -65,6 +65,7 @@ Please take a look at the documentation for each type (e.g. for slack see [Confi - [Slack](#configuration-of-a-slack-notification) - [Slack App](#configuration-of-a-slack-app-notification) - [Email](#configuration-of-an-email-notification) +- [MS Teams](#configuration-of-a-ms-teams-notification) ### Configuration of a Notification @@ -223,7 +224,7 @@ This configuration needs to be specified under `env` in the values yaml. The identifier for this config has to be `SMTP_CONFIG`. A basic configuration could look like this: -``` +```yaml notificationChannels: - name: email type: email @@ -246,6 +247,37 @@ env: value: secureCodeBox ``` +### Configuration Of A MS Teams Notification + +To configure a MS Teams notification you need to set the type to `ms-teams`. +In `endPoint` you need to specify the MS Teams webhook. +To use the template provided by the secureCodeBox set template to `msteams-messageCard`. + +The default template allows you to specify an additional set of information. +If you use an external web based vulnerability management system with some kind of dashboard, you can set the variable `VULNMANAG_ENABLED` to true and point the `VULNMANAG_DASHBOARD_URL` to the URL of your vulnerability management. +This will add a button in the notification that links directly to your dashboard. +You can also add a button that opens your findings directly in your dashboard. +To do this you need to specify `dashboardFingingsUrl`. +You will have to replace the id of the scan in this url with `{{ uid }}` so that nunjucks can parse these urls. + +A basic configuration could look like this: + +```yaml +notificationChannels: + - name: ms-teams + type: ms-teams + template: msteams-messageCard + rules: [] + endPoint: "https://somewhere.xyz/sadf12" +env: + - name: VULNMANAG_ENABLED + value: true + - name: VULNMANAG_DASHBOARD_URL + value: "somedashboard.url" + - name: VULNMANAG_DASHBOARD_FINDINGS_URL + value: "somedashboard.url/findings/{{ uid }}" +``` + ### Custom Message Templates CAUTION: Nunjucks templates allow code to be injected! Use templates from trusted sources only! diff --git a/hooks/notification/docs/README.ArtifactHub.md b/hooks/notification/docs/README.ArtifactHub.md index 2202a63645..795c1aed92 100644 --- a/hooks/notification/docs/README.ArtifactHub.md +++ b/hooks/notification/docs/README.ArtifactHub.md @@ -73,6 +73,7 @@ Please take a look at the documentation for each type (e.g. for slack see [Confi - [Slack](#configuration-of-a-slack-notification) - [Slack App](#configuration-of-a-slack-app-notification) - [Email](#configuration-of-an-email-notification) +- [MS Teams](#configuration-of-a-ms-teams-notification) ### Configuration of a Notification @@ -231,7 +232,7 @@ This configuration needs to be specified under `env` in the values yaml. The identifier for this config has to be `SMTP_CONFIG`. A basic configuration could look like this: -``` +```yaml notificationChannels: - name: email type: email @@ -254,6 +255,37 @@ env: value: secureCodeBox ``` +### Configuration Of A MS Teams Notification + +To configure a MS Teams notification you need to set the type to `ms-teams`. +In `endPoint` you need to specify the MS Teams webhook. +To use the template provided by the secureCodeBox set template to `msteams-messageCard`. + +The default template allows you to specify an additional set of information. +If you use an external web based vulnerability management system with some kind of dashboard, you can set the variable `VULNMANAG_ENABLED` to true and point the `VULNMANAG_DASHBOARD_URL` to the URL of your vulnerability management. +This will add a button in the notification that links directly to your dashboard. +You can also add a button that opens your findings directly in your dashboard. +To do this you need to specify `dashboardFingingsUrl`. +You will have to replace the id of the scan in this url with `{{ uid }}` so that nunjucks can parse these urls. + +A basic configuration could look like this: + +```yaml +notificationChannels: + - name: ms-teams + type: ms-teams + template: msteams-messageCard + rules: [] + endPoint: "https://somewhere.xyz/sadf12" +env: + - name: VULNMANAG_ENABLED + value: true + - name: VULNMANAG_DASHBOARD_URL + value: "somedashboard.url" + - name: VULNMANAG_DASHBOARD_FINDINGS_URL + value: "somedashboard.url/findings/{{ uid }}" +``` + ### Custom Message Templates CAUTION: Nunjucks templates allow code to be injected! Use templates from trusted sources only! diff --git a/hooks/notification/notification-templates/msteams-messageCard.njk b/hooks/notification/notification-templates/msteams-messageCard.njk new file mode 100644 index 0000000000..539b1ca345 --- /dev/null +++ b/hooks/notification/notification-templates/msteams-messageCard.njk @@ -0,0 +1,66 @@ +type: message +attachments: + - contentType: application/vnd.microsoft.card.adaptive + contentUrl: null + content: + type: AdaptiveCard + body: + - type: TextBlock + size: Medium + weight: Bolder + text: "New {{ scan.spec.scanType }} security scan results are available!" + wrap: true + - type: ColumnSet + columns: + - type: Column + items: + - type: Image + style: Person + url: "https://www.securecodebox.io/favicon.png" + size: Small + width: auto + - type: Column + items: + - type: TextBlock + weight: Bolder + text: "Scan **'{{ scan.metadata.name }}'**" + wrap: true + - type: TextBlock + spacing: None + text: Created at {{ scan.metadata.creationTimestamp }} + isSubtle: true + wrap: true + width: stretch + - type: TextBlock + text: Findings Category Overview + wrap: true + weight: Bolder + size: Medium + - type: FactSet + facts: + {% for category, counter in scan.status.findings.categories -%} + - title: "{{ category }}:" + value: "{{ counter }}" + {% endfor %} + - type: TextBlock + text: Findings Severity Overview + wrap: true + weight: Bolder + size: Medium + - type: FactSet + facts: + {% for severity, counter in scan.status.findings.severities -%} + - title: "{{ severity }}:" + value: "{{ counter }}" + {% endfor %} + {% if args["VULNMANAG_ENABLED"] == true -%} + actions: + - type: Action.OpenUrl + title: Open Dashboard + url: "{{ renderString(args["VULNMANAG_DASHBOARD_URL"].toString(), { uid: scan.metadata.uid }) }}" + - type: Action.OpenUrl + title: Show Results in Dashboard + url: "{{ renderString(args["VULNMANAG_DASHBOARD_FINDINGS_URL"], { uid: scan.metadata.uid }) }}" + {% endif %} + "$schema": http://adaptivecards.io/schemas/adaptive-card.json + version: '1.3'