mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 06:25:29 +00:00
Support for Jira custom fields
This commit is contained in:
parent
62bc659914
commit
6ab4bf25f9
@ -50,8 +50,23 @@
|
||||
# # issue-type is the name of the created issue type (case sensitive)
|
||||
# issue-type: Bug
|
||||
# # SeverityAsLabel (optional) sends the severity as the label of the created issue
|
||||
# # User custom fields for Jira Cloud instead
|
||||
# severity-as-label: true
|
||||
#
|
||||
# # Whatever your final status is that you want to use as a closed ticket - Closed, Done, Remediated, etc
|
||||
# # When checking for duplicates, the JQL query will filter out status's that match this.
|
||||
# # If it finds a match _and_ the ticket does have this status, a new one will be created.
|
||||
# status-not: Closed
|
||||
# # Customfield supports name, id and freeform. name and id are to be used when the custom field is a dropdown.
|
||||
# # freeform can be used if the custom field is just a text entry
|
||||
# # Variables can be used to pull various pieces of data from the finding itself.
|
||||
# # Supported variables: $CVSSMetrics, $CVEID, $CWEID, $Host, $Severity, $CVSSScore, $Name
|
||||
# custom_fields:
|
||||
# customfield_00001:
|
||||
# name: "Nuclei"
|
||||
# customfield_00002:
|
||||
# freeform: $CVSSMetrics
|
||||
# customfield_00003:
|
||||
# freeform: $CVSSScore
|
||||
# elasticsearch contains configuration options for elasticsearch exporter
|
||||
#elasticsearch:
|
||||
# # IP for elasticsearch instance
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/andygrunwald/go-jira"
|
||||
"github.com/trivago/tgo/tcontainer"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
|
||||
@ -44,9 +45,13 @@ type Options struct {
|
||||
// issue.
|
||||
SeverityAsLabel bool `yaml:"severity-as-label" json:"severity_as_label"`
|
||||
// Severity (optional) is the severity of the issue.
|
||||
Severity []string `yaml:"severity" json:"severity"`
|
||||
|
||||
Severity []string `yaml:"severity" json:"severity"`
|
||||
HttpClient *retryablehttp.Client `yaml:"-" json:"-"`
|
||||
// for each customfield specified in the configuration options
|
||||
// we will create a map of customfield name to the value
|
||||
// that will be used to create the issue
|
||||
CustomFields map[string]interface{} `yaml:"custom_fields"`
|
||||
StatusNot string `yaml:"status-not" json:"status_not"`
|
||||
}
|
||||
|
||||
// New creates a new issue tracker integration client based on options.
|
||||
@ -80,15 +85,51 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
||||
if label := i.options.IssueType; label != "" {
|
||||
labels = append(labels, label)
|
||||
}
|
||||
|
||||
// for each custom value, take the name of the custom field and
|
||||
// set the value of the custom field to the value specified in the
|
||||
// configuration options
|
||||
customFields := tcontainer.NewMarshalMap()
|
||||
for name, value := range i.options.CustomFields {
|
||||
//customFields[name] = map[string]interface{}{"value": value}
|
||||
if valueMap, ok := value.(map[interface{}]interface{}); ok {
|
||||
// Iterate over nested map
|
||||
for nestedName, nestedValue := range valueMap {
|
||||
if strings.HasPrefix(nestedValue.(string), "$") {
|
||||
nestedValue = strings.TrimPrefix(nestedValue.(string), "$")
|
||||
switch nestedValue {
|
||||
case "CVSSMetrics":
|
||||
nestedValue = event.Info.Classification.CVSSMetrics
|
||||
case "CVEID":
|
||||
nestedValue = event.Info.Classification.CVEID
|
||||
case "CWEID":
|
||||
nestedValue = event.Info.Classification.CWEID
|
||||
case "CVSSScore":
|
||||
nestedValue = event.Info.Classification.CVSSScore
|
||||
case "Host":
|
||||
nestedValue = event.Host
|
||||
case "Severity":
|
||||
nestedValue = event.Info.SeverityHolder
|
||||
case "Name":
|
||||
nestedValue = event.Info.Name
|
||||
}
|
||||
}
|
||||
switch nestedName {
|
||||
case "id":
|
||||
customFields[name] = map[string]interface{}{"id": nestedValue}
|
||||
case "name":
|
||||
customFields[name] = map[string]interface{}{"value": nestedValue}
|
||||
case "freeform":
|
||||
customFields[name] = nestedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fields := &jira.IssueFields{
|
||||
Assignee: &jira.User{AccountID: i.options.AccountID},
|
||||
Reporter: &jira.User{AccountID: i.options.AccountID},
|
||||
Description: jiraFormatDescription(event),
|
||||
Unknowns: customFields,
|
||||
Type: jira.IssueType{Name: i.options.IssueType},
|
||||
Project: jira.Project{Key: i.options.ProjectName},
|
||||
Summary: summary,
|
||||
Labels: labels,
|
||||
}
|
||||
// On-prem version of Jira server does not use AccountID
|
||||
if !i.options.Cloud {
|
||||
@ -99,6 +140,7 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
||||
Project: jira.Project{Key: i.options.ProjectName},
|
||||
Summary: summary,
|
||||
Labels: labels,
|
||||
Unknowns: customFields,
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +178,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
||||
// FindExistingIssue checks if the issue already exists and returns its ID
|
||||
func (i *Integration) FindExistingIssue(event *output.ResultEvent) (string, error) {
|
||||
template := format.GetMatchedTemplate(event)
|
||||
jql := fmt.Sprintf("summary ~ \"%s\" AND summary ~ \"%s\" AND status = \"Open\"", template, event.Host)
|
||||
jql := fmt.Sprintf("summary ~ \"%s\" AND summary ~ \"%s\" AND status != \"%s\"", template, event.Host, i.options.StatusNot)
|
||||
|
||||
searchOptions := &jira.SearchOptions{
|
||||
MaxResults: 1, // if any issue exists, then we won't create a new one
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user