mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 23:35:27 +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 is the name of the created issue type (case sensitive)
|
||||||
# issue-type: Bug
|
# issue-type: Bug
|
||||||
# # SeverityAsLabel (optional) sends the severity as the label of the created issue
|
# # SeverityAsLabel (optional) sends the severity as the label of the created issue
|
||||||
|
# # User custom fields for Jira Cloud instead
|
||||||
# severity-as-label: true
|
# 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 contains configuration options for elasticsearch exporter
|
||||||
#elasticsearch:
|
#elasticsearch:
|
||||||
# # IP for elasticsearch instance
|
# # IP for elasticsearch instance
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/andygrunwald/go-jira"
|
"github.com/andygrunwald/go-jira"
|
||||||
|
"github.com/trivago/tgo/tcontainer"
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
|
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
|
||||||
@ -44,9 +45,13 @@ type Options struct {
|
|||||||
// issue.
|
// issue.
|
||||||
SeverityAsLabel bool `yaml:"severity-as-label" json:"severity_as_label"`
|
SeverityAsLabel bool `yaml:"severity-as-label" json:"severity_as_label"`
|
||||||
// Severity (optional) is the severity of the issue.
|
// 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:"-"`
|
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.
|
// 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 != "" {
|
if label := i.options.IssueType; label != "" {
|
||||||
labels = append(labels, 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{
|
fields := &jira.IssueFields{
|
||||||
Assignee: &jira.User{AccountID: i.options.AccountID},
|
|
||||||
Reporter: &jira.User{AccountID: i.options.AccountID},
|
|
||||||
Description: jiraFormatDescription(event),
|
Description: jiraFormatDescription(event),
|
||||||
|
Unknowns: customFields,
|
||||||
Type: jira.IssueType{Name: i.options.IssueType},
|
Type: jira.IssueType{Name: i.options.IssueType},
|
||||||
Project: jira.Project{Key: i.options.ProjectName},
|
Project: jira.Project{Key: i.options.ProjectName},
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
Labels: labels,
|
|
||||||
}
|
}
|
||||||
// On-prem version of Jira server does not use AccountID
|
// On-prem version of Jira server does not use AccountID
|
||||||
if !i.options.Cloud {
|
if !i.options.Cloud {
|
||||||
@ -99,6 +140,7 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
|
|||||||
Project: jira.Project{Key: i.options.ProjectName},
|
Project: jira.Project{Key: i.options.ProjectName},
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
Labels: labels,
|
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
|
// FindExistingIssue checks if the issue already exists and returns its ID
|
||||||
func (i *Integration) FindExistingIssue(event *output.ResultEvent) (string, error) {
|
func (i *Integration) FindExistingIssue(event *output.ResultEvent) (string, error) {
|
||||||
template := format.GetMatchedTemplate(event)
|
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{
|
searchOptions := &jira.SearchOptions{
|
||||||
MaxResults: 1, // if any issue exists, then we won't create a new one
|
MaxResults: 1, // if any issue exists, then we won't create a new one
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user