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