nuclei/v2/pkg/reporting/reporting.go

193 lines
5.5 KiB
Go
Raw Normal View History

package reporting
import (
2021-02-08 01:43:51 +05:30
"strings"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/dedupe"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/disk"
2021-06-05 18:01:08 +05:30
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/sarif"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/jira"
2021-02-08 02:07:19 +05:30
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"go.uber.org/multierr"
)
// Options is a configuration file for nuclei reporting module
type Options struct {
2021-02-08 01:43:51 +05:30
// AllowList contains a list of allowed events for reporting module
AllowList *Filter `yaml:"allow-list"`
// DenyList contains a list of denied events for reporting module
DenyList *Filter `yaml:"deny-list"`
// Github contains configuration options for Github Issue Tracker
Github *github.Options `yaml:"github"`
// Gitlab contains configuration options for Gitlab Issue Tracker
Gitlab *gitlab.Options `yaml:"gitlab"`
// Jira contains configuration options for Jira Issue Tracker
Jira *jira.Options `yaml:"jira"`
// DiskExporter contains configuration options for Disk Exporter Module
DiskExporter *disk.Options `yaml:"disk"`
2021-06-05 18:01:08 +05:30
// SarifExporter contains configuration options for Sarif Exporter Module
SarifExporter *sarif.Options `yaml:"sarif"`
}
2021-02-08 01:43:51 +05:30
// Filter filters the received event and decides whether to perform
// reporting for it or not.
type Filter struct {
Severity string `yaml:"severity"`
severity []string
Tags string `yaml:"tags"`
tags []string
}
// Compile compiles the filter creating match structures.
func (f *Filter) Compile() {
parts := strings.Split(f.Severity, ",")
for _, part := range parts {
f.severity = append(f.severity, strings.TrimSpace(part))
}
parts = strings.Split(f.Tags, ",")
for _, part := range parts {
f.tags = append(f.tags, strings.TrimSpace(part))
}
}
// GetMatch returns true if a filter matches result event
func (f *Filter) GetMatch(event *output.ResultEvent) bool {
2021-02-08 02:07:19 +05:30
severity := types.ToString(event.Info["severity"])
2021-02-08 01:43:51 +05:30
if len(f.severity) > 0 {
2021-02-26 13:13:11 +05:30
return stringSliceContains(f.severity, severity)
2021-02-08 01:43:51 +05:30
}
tags := event.Info["tags"]
2021-02-08 02:07:19 +05:30
tagParts := strings.Split(types.ToString(tags), ",")
2021-02-08 01:43:51 +05:30
for i, tag := range tagParts {
tagParts[i] = strings.TrimSpace(tag)
}
for _, tag := range f.tags {
if stringSliceContains(tagParts, tag) {
return true
}
}
return false
}
// Tracker is an interface implemented by an issue tracker
type Tracker interface {
// CreateIssue creates an issue in the tracker
CreateIssue(event *output.ResultEvent) error
}
// Exporter is an interface implemented by an issue exporter
type Exporter interface {
2021-06-05 18:01:08 +05:30
// Close closes the exporter after operation
Close() error
// Export exports an issue to an exporter
Export(event *output.ResultEvent) error
}
// Client is a client for nuclei issue tracking module
type Client struct {
trackers []Tracker
exporters []Exporter
options *Options
dedupe *dedupe.Storage
}
// New creates a new nuclei issue tracker reporting client
func New(options *Options, db string) (*Client, error) {
2021-02-08 01:43:51 +05:30
if options.AllowList != nil {
options.AllowList.Compile()
}
if options.DenyList != nil {
options.DenyList.Compile()
}
client := &Client{options: options}
if options.Github != nil {
tracker, err := github.New(options.Github)
if err != nil {
return nil, errors.Wrap(err, "could not create reporting client")
}
client.trackers = append(client.trackers, tracker)
}
if options.Gitlab != nil {
tracker, err := gitlab.New(options.Gitlab)
if err != nil {
return nil, errors.Wrap(err, "could not create reporting client")
}
client.trackers = append(client.trackers, tracker)
}
if options.Jira != nil {
tracker, err := jira.New(options.Jira)
if err != nil {
return nil, errors.Wrap(err, "could not create reporting client")
}
client.trackers = append(client.trackers, tracker)
}
if options.DiskExporter != nil {
exporter, err := disk.New(options.DiskExporter)
if err != nil {
return nil, errors.Wrap(err, "could not create exporting client")
}
client.exporters = append(client.exporters, exporter)
}
2021-06-05 18:01:08 +05:30
if options.SarifExporter != nil {
exporter, err := sarif.New(options.SarifExporter)
if err != nil {
return nil, errors.Wrap(err, "could not create exporting client")
}
client.exporters = append(client.exporters, exporter)
}
2021-02-07 23:41:33 +05:30
storage, err := dedupe.New(db)
if err != nil {
return nil, err
}
client.dedupe = storage
return client, nil
}
// Close closes the issue tracker reporting client
func (c *Client) Close() {
c.dedupe.Close()
2021-06-05 18:01:08 +05:30
for _, exporter := range c.exporters {
exporter.Close()
}
}
// CreateIssue creates an issue in the tracker
func (c *Client) CreateIssue(event *output.ResultEvent) error {
2021-02-08 01:43:51 +05:30
if c.options.AllowList != nil && !c.options.AllowList.GetMatch(event) {
return nil
}
if c.options.DenyList != nil && c.options.DenyList.GetMatch(event) {
return nil
}
unique, err := c.dedupe.Index(event)
if unique {
for _, tracker := range c.trackers {
if trackerErr := tracker.CreateIssue(event); trackerErr != nil {
err = multierr.Append(err, trackerErr)
}
}
for _, exporter := range c.exporters {
if exportErr := exporter.Export(event); exportErr != nil {
err = multierr.Append(err, exportErr)
}
}
}
return err
}
2021-02-08 01:43:51 +05:30
func stringSliceContains(slice []string, item string) bool {
for _, i := range slice {
if strings.EqualFold(i, item) {
return true
}
}
return false
}