JFrog Xray: Creating Jira Issues using webhooks in a breeze
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.
5 easy steps to implement Xray webhooks that work with Jira
Here’s what we’ll need to do this:
- Create a ‘light’ server which will know how to listen to remote requests.
- Configure an Xray webhook that’s associated with a policy that will trigger it.
- Understand the structure of an Xray Webhook .json file, in order to parse the information correctly. (see Infected Files Structure snippet in section below)
- 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)
- 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!