Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/exporters/mongo/mongo.go
2070 views
1
package mongo
2
3
import (
4
"context"
5
"github.com/pkg/errors"
6
"github.com/projectdiscovery/gologger"
7
"github.com/projectdiscovery/nuclei/v3/pkg/output"
8
"go.mongodb.org/mongo-driver/mongo"
9
"net/url"
10
"os"
11
"strings"
12
"sync"
13
14
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
15
)
16
17
type Exporter struct {
18
options *Options
19
mutex *sync.Mutex
20
rows []output.ResultEvent
21
collection *mongo.Collection
22
connection *mongo.Client
23
}
24
25
// Options contains the configuration options for MongoDB exporter client
26
type Options struct {
27
// ConnectionString is the connection string to the MongoDB database
28
ConnectionString string `yaml:"connection-string"`
29
// CollectionName is the name of the MongoDB collection in which to store the results
30
CollectionName string `yaml:"collection-name"`
31
// OmitRaw excludes the Request and Response from the results (helps with filesize)
32
OmitRaw bool `yaml:"omit-raw"`
33
// BatchSize determines the number of results to be kept in memory before writing it to the database or 0 to
34
// persist all in memory and write all results at the end (default)
35
BatchSize int `yaml:"batch-size"`
36
}
37
38
// New creates a new MongoDB exporter integration client based on options.
39
func New(options *Options) (*Exporter, error) {
40
exporter := &Exporter{
41
mutex: &sync.Mutex{},
42
options: options,
43
rows: []output.ResultEvent{},
44
}
45
46
// If the environment variable for the connection string is set, then use that instead. This allows for easier
47
// management of sensitive items such as credentials
48
envConnectionString := os.Getenv("MONGO_CONNECTION_STRING")
49
if envConnectionString != "" {
50
options.ConnectionString = envConnectionString
51
gologger.Info().Msgf("Using connection string from environment variable MONGO_CONNECTION_STRING")
52
}
53
54
// Create the connection to the database
55
clientOptions := mongooptions.Client().ApplyURI(options.ConnectionString)
56
57
// Create a new client and connect to the MongoDB server
58
client, err := mongo.Connect(context.TODO(), clientOptions)
59
if err != nil {
60
gologger.Error().Msgf("Error creating MongoDB client: %s", err)
61
return nil, err
62
}
63
64
// Ensure the connection is valid
65
err = client.Ping(context.Background(), nil)
66
if err != nil {
67
gologger.Error().Msgf("Error connecting to MongoDB: %s", err)
68
return nil, err
69
}
70
71
// Get the database from the connection string to set the database and collection
72
parsed, err := url.Parse(options.ConnectionString)
73
if err != nil {
74
gologger.Error().Msgf("Error parsing connection string: %s", options.ConnectionString)
75
return nil, err
76
}
77
78
databaseName := strings.TrimPrefix(parsed.Path, "/")
79
80
if databaseName == "" {
81
return nil, errors.New("error getting database name from connection string")
82
}
83
84
exporter.connection = client
85
exporter.collection = client.Database(databaseName).Collection(options.CollectionName)
86
87
return exporter, nil
88
}
89
90
// Export writes a result document to the configured MongoDB collection
91
// in the database configured by the connection string
92
func (exporter *Exporter) Export(event *output.ResultEvent) error {
93
exporter.mutex.Lock()
94
defer exporter.mutex.Unlock()
95
96
if exporter.options.OmitRaw {
97
event.Request = ""
98
event.Response = ""
99
}
100
101
// Add the row to the queue to be processed
102
exporter.rows = append(exporter.rows, *event)
103
104
// If the batch size is greater than 0 and the number of rows has reached the batch, flush it to the database
105
if exporter.options.BatchSize > 0 && len(exporter.rows) >= exporter.options.BatchSize {
106
err := exporter.WriteRows()
107
if err != nil {
108
// The error is already logged, return it to bubble up to the caller
109
return err
110
}
111
}
112
113
return nil
114
}
115
116
// WriteRows writes all rows from the rows list to the MongoDB collection and removes them from the list
117
func (exporter *Exporter) WriteRows() error {
118
// Loop through the rows and write them, removing them as they're entered
119
for len(exporter.rows) > 0 {
120
data := exporter.rows[0]
121
122
// Write the data to the database
123
_, err := exporter.collection.InsertOne(context.TODO(), data)
124
if err != nil {
125
gologger.Fatal().Msgf("Error inserting record into MongoDB collection: %s", err)
126
return err
127
}
128
129
// Remove the item from the list
130
exporter.rows = exporter.rows[1:]
131
}
132
133
return nil
134
}
135
136
func (exporter *Exporter) Close() error {
137
exporter.mutex.Lock()
138
defer exporter.mutex.Unlock()
139
140
// Write all pending rows
141
err := exporter.WriteRows()
142
if err != nil {
143
// The error is already logged, return it to bubble up to the caller
144
return err
145
}
146
147
// Close the database connection
148
err = exporter.connection.Disconnect(context.TODO())
149
if err != nil {
150
gologger.Error().Msgf("Error disconnecting from MongoDB: %s", err)
151
return err
152
}
153
154
return nil
155
}
156
157