4

Snyk has native integrations to Slack, ServiceNow, Jira and others But no integration to MS Teams How can a team get Snyk notifications pushed to MS Teams?

Mathias Conradt
  • 28,420
  • 21
  • 138
  • 192
Jonathan Gruber
  • 408
  • 1
  • 16

1 Answers1

3

You can make use of Snyk's outbound webhooks

Register any endpoint of yours that can consume JSON payload; sample payload is at https://snyk.docs.apiary.io/#introduction/consuming-webhooks

There is a sample Azure Function specific for MS Teams: https://github.com/harrykimpel/snyk-webhook-subscription/blob/main/azure-function-microsoft-teams.cs and additional info in this repo's README and blog post

Another script that does the same but for Azure Boards is this here, which is Node.js based: https://github.com/mathiasconradt/snyk-azure-boards-webhook

Below sample Azure function shows to to consume the incoming JSON payload from Snyk, then forward it further to another system, such as MS Teams:

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json.Linq;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    //log.LogInformation("data: " + requestBody);

    string count = data.newIssues.Count.ToString();
    log.LogInformation("data.newIssues.Count: " + count);
    string responseMessage = "No new issues found. Nothing to process!";

    if (data.newIssues.Count > 0)
    {
        log.LogInformation("New issues found!");

        name = name ?? data?.name;
        string projectName = data.project.name;
        string browseUrl = data.project.browseUrl;
        int x = 0;

        for (int i = 0; i < data.newIssues.Count; i++)
        {
            // send data to Azure Boards
            StringBuilder sb = new StringBuilder();

            //var item = (JObject)data.newIssues[i];
            //do something with item
            string id = data.newIssues[i].id.ToString();
            //log.LogInformation("data.newIssues[i].id:" + id);
            string descr = data.newIssues[i].issueData.description.ToString();
            //log.LogInformation("data.newIssues[i].issueData.description:" + descr);

            sb.Append("{");
            sb.Append("  \"@context\": \"https://schema.org/extensions\",");
            sb.Append("  \"@type\": \"MessageCard\",");
            sb.Append("  \"themeColor\": \"0072C6\",");
            sb.Append("  \"title\": \"" + projectName + "\",");
            sb.Append("  \"text\": \"" + id + "<br><br>" + descr + "\",");
            sb.Append("  \"potentialAction\": [ ");
            sb.Append("    {");
            sb.Append("      \"@type\": \"OpenUri\",");
            sb.Append("      \"name\": \"Project Details\",");
            sb.Append("      \"targets\": [");
            sb.Append("        { \"os\": \"default\", \"uri\": \"" + browseUrl + "\" }");
            sb.Append("      ]");
            sb.Append("    }");
            sb.Append("  ]");
            sb.Append("}");

            string payload = sb.ToString();
            //log.LogInformation("content: " + payload);

            var content = new StringContent(payload);

            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var MS_TEAMS_WEBHOOK = Environment.GetEnvironmentVariable("MS_TEAMS_WEBHOOK");
            var url = MS_TEAMS_WEBHOOK;
            using var client = new HttpClient();
            var response = await client.PostAsync(url, content);

            string result = response.Content.ReadAsStringAsync().Result;
            //log.LogInformation("response.StatusCode: " + response.StatusCode);
            if (response.StatusCode == HttpStatusCode.OK)
            {
                x++;
            }
            //log.LogInformation("result: " + result);
        }

        // write output as summary
        string output = "Successfully processed " + x + " issues.";
        log.LogInformation(output);
        responseMessage = output;
    }

    return new OkObjectResult(responseMessage);
}

Another example based on Node.js but for Azure Boards. If you prefer Node.js, simply replace the outbound logic with MS Teams API.

// index.js
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const crypto = require('crypto');
const PORT = process.env.PORT || 5000;
const ISSUE_TEMPLATE = [
  {
    "op": "add",
    "path": "/fields/System.Title",
    "from": null,
    "value": null
  },
  {
    "op": "add",
    "path": "/fields/System.Description",
    "from": null,
    "value": null
  },
{
    "op": "add",
    "path": "/fields/System.WorkItemType",
    "from": null,
    "value": "Issue"
  }
];

const app = express()
  .use(bodyParser.urlencoded({ extended: true }))
  .use(bodyParser.json())
  .use(bodyParser.raw())
  .get('/snyk', (req, res) => {  
    console.log('process.env.AZURE_DEVOPS_USER ' + process.env.AZURE_DEVOPS_USER);
    res.sendStatus(200);
  })
  .post('/snyk', (req, res) => {
      console.log('Got body:', req.body);

      var verified = this.verifySignature(req);
      console.log('verified: ', verified);

      if (verified && req.body.newIssues) {
        var newIssues = req.body.newIssues;
        newIssues.forEach(issue => {        
          var it = JSON.parse(JSON.stringify(ISSUE_TEMPLATE));
          it[0].value = issue.issueData.title + " [" + issue.issueData.id + "]";
          it[1].value = issue.issueData.description;
          this.createIssuePostman(it);
        });
      }
      res.sendStatus(200);
  })
  .listen(PORT, () => console.log(`Listening on ${ PORT }`));

module.exports.verifySignature = function (request) {
  const hmac = crypto.createHmac( 'sha256' , process.env.SNYK_WEBHOOKS_SECRET);
  const buffer = JSON .stringify(request.body);
  hmac.update(buffer, 'utf8' );
  const signature = `sha256=${hmac.digest('hex')}` ;
  return signature === request.headers[ 'x-hub-signature' ];
}

module.exports.createIssuePostman = function(issue) {
  console.log('createIssuePostman: ' + issue[0].value);
  var auth = 'Basic ' + Buffer.from(process.env.AZURE_DEVOPS_USER + ':' + process.env.AZURE_DEVOPS_ACCESS_TOKEN).toString('base64');
  var config = {
    method: 'post',
    url: 'https://dev.azure.com/' + process.env.AZURE_DEVOPS_ORGANIZATION + '/' + process.env.AZURE_DEVOPS_PROJECT + '/_apis/wit/workitems/$Issue?validateOnly=false&api-version=6.0',
    headers: {       
      'Authorization': auth,
      'Content-Type': 'application/json-patch+json'
    },
    data : issue
  };
  axios(config)
  .then(function (response) {
    console.log(JSON.stringify(response.data));
  })
  .catch(function (error) {
    console.log(error);
  });
}
Mathias Conradt
  • 28,420
  • 21
  • 138
  • 192