JFrog Xray: Creating Jira Issues using webhooks in a breeze

Xray Vulnerabilities in Jira using golang

JFrog Xray offers an end-to-end security scanning solution covering the full development lifecycle of your artifacts. This includes vulnerability analysis, security and license compliance, artifact flow control, distribution and more. When Xray finds a security or a licence issue, it will trigger a violation for it. One of the most common use cases during the development cycle is to have the ability to track these issues using commonly used tools such as Jira, and Xray Webhooks allow you to do that.

First, what’s a webhook?

A Webhook is an API concept which is used to alter web requests behaviors with custom callbacks, used by third party users, developers or programs which are not necessarily affiliated with the originating website or application. This can also be referred to as “Reverse API”.

Let’s say that we have a violation triggered by Xray, and we have an Xray policy with a watch that’s configured to send a Webhook to a specific third party application. To make the .json file that’s automatically created readable and usable by the third party application, we can use a simple script that can parse the data and display it in a Jira ticket or even a Slack message.

JFrog Xray Violation

5 easy steps to implement Xray webhooks that work with Jira

Here’s what we’ll need to do this:

  1. Create a ‘light’ server which will know how to listen to remote requests.
  2. Configure an Xray webhook that’s associated with a policy that will trigger it.
  3. Understand the structure of an Xray Webhook .json file, in order to parse the information correctly. (see Infected Files Structure snippet in section below)
  4. Once we’re able to parse the information, we need to define what we want to send to our JIRA server and what information the issue will contain. (see Create Jira from Xray Handler snippet in section below)
  5. Implement the Jira server interaction with this information.

Why is Go the best way to go?

Go is a SUPER easy language to use! With just a few lines of code, we can easily create a server that’s up and running in no time, and knows how to listen to HTTP requests. From my experience, compared to other languages, this is much easier to do using the Go programming language.

Also, to help us create the Jira client object and use it to open an issue, we can use go-jira, a really nice open source library that’s also available on GoCenter. Here’s a nice example.

All that’s left to do is create a user on the Jira server and provide it with access permissions to create issues.

Go Example

Let’s see this in action! Here are the Go structure and handler snippets that you can use:

1. Violation Structure

This structure defines the Xray violation we are creating a Jira ticket for. Xray creates a violation, which is captured by the webhook in JSON format.

type Violation struct {
  Created            string    `json:"created"`
  TopSeverity        string    `json:"top_severity"`
  WatchName          string    `json:"watch_name"`
  PolicyName         string    `json:"policy_name"`
  Issues             Issues    `json:"issues"`
}

2. Issue Structure

Part of the violation are the license or security issues.


type Issue struct {
  Severity           string             `json:"severity"`
  Type               string             `json:"type"` // Issue type license/security
  Summary            string             `json:"summary"`
  Description        string             `json:"description"`
  ImpactedArtifacts  ImpactedArtifacts  `json:"impacted_artifacts"`
}

type Issues []Issue

3. Impacted Artifacts Structure

Each issue contains a number of artifacts in Artifactory that are impacted by the violation.

type ImpactedArtifact struct {
  Name             string         `json:"name"` // Artifact name
  DisplayName      string         `json:"display_name"`
  Path             string         `json:"path"`  // Artifact path in Artifactory
  PackageType      string         `json:"pkg_type"`
  SHA256           string         `json:"sha256"` // Artifact SHA 256 checksum
  SHA1             string         `json:"sha1"`
  Depth            int            `json:"depth"`  // Artifact depth in its hierarchy
  ParentSHA        string         `json:"parent_sha"`
  InfectedFiles    InfectedFiles  `json:"infected_files"`
}

type ImpactedArtifacts []ImpactedArtifact

4. Infected Files Structure

Each artifact can contain a number of infected files, these are the infected components.

type InfectedFile struct {
Name           string    `json:"name"`
Path           string    `json:"path"`  // artifact path in Artifactory
SHA256         string    `json:"sha256"`// artifact SHA 256 checksum
Depth          int       `json:"depth"` // Artifact depth in its hierarchy
ParentSHA      string    `json:"parent_sha"` // Parent artifact SHA1
DisplayName    string    `json:"display_name"`
PackageType    string    `json:"pkg_type"`
}

type InfectedFiles []InfectedFile

Create Jira from Xray Handler Example

Here’s the complete example using the above structures.

func CreateJira(w http.ResponseWriter, r *http.Request) {
  var violation Violation
  var jiraAccountConfiguration JiraAccountConf
  var jiraIssueConfiguration JiraIssueConf


  jiraAccountConfiguration = ReadJiraConfigurationFile("config/jira-account.json")
  jiraIssueConfiguration = ReadJiraIssueFile ("config/jira-create-issue.json")

  body, err := ioutil.ReadAll(io.LimitReader(r.Body, 5048576))
  if err != nil{
     panic(err)
  }

  if err := json.Unmarshal(body, &violation); err != nil {
     w.Header().Set("Content-Type", "application/json; charset=UTF-8")
     w.WriteHeader(200)
     if err := json.NewEncoder(w).Encode(err); err != nil {
        panic(err)
     }
  }

  fmt.Println("Opening jira account configuration file")

  tp := jira.BasicAuthTransport{
     Username: strings.TrimSpace(jiraAccountConfiguration.UserName),
     Password: strings.TrimSpace(jiraAccountConfiguration.Password),
  }

  client, err := jira.NewClient(tp.Client(), strings.TrimSpace(jiraAccountConfiguration.ConnectionString))
  if err != nil {
     fmt.Printf("\nerror: %v\n", err)
     return
  }

  i := jira.Issue{
     Fields: &jira.IssueFields{
        Assignee: &jira.User{
           Name: jiraIssueConfiguration.Assignee,
        },
        Reporter: &jira.User{
           Name: jiraIssueConfiguration.Reporter,
        },
        Description: "The watch " + violation.WatchName + " with policy " + violation.PolicyName +
         " created a violation with " + getIssuesCount(violation) + " number of issues",
        Type: jira.IssueType{
           Name: jiraIssueConfiguration.IssueType,   //  BUG/IMPROVEMENT/TASK ETC
        },
        Project: jira.Project{
           Key: jiraIssueConfiguration.Project,     //  PROJECT_NAME on Jira Server
        },
        Summary: "Violation was found with " + violation.TopSeverity + " severity",
     },

  }

  issue, _, err := client.Issue.Create(&i)
  if err != nil {
     panic(err)
  }
  fmt.Println("%s: %+v\n", issue.Key)
}

Go ahead and give it a try yourself!