Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/exporters/es/elasticsearch.go
2070 views
1
package es
2
3
import (
4
"bytes"
5
"crypto/tls"
6
"encoding/base64"
7
"fmt"
8
"io"
9
"net/http"
10
"time"
11
12
"github.com/pkg/errors"
13
14
"github.com/projectdiscovery/nuclei/v3/pkg/output"
15
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
16
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
17
"github.com/projectdiscovery/retryablehttp-go"
18
"github.com/projectdiscovery/useragent"
19
)
20
21
// Options contains necessary options required for elasticsearch communication
22
type Options struct {
23
// Host is the hostname of the elasticsearch instance
24
Host string `yaml:"host" validate:"required_without=IP"`
25
// IP for elasticsearch instance
26
IP string `yaml:"ip" validate:"required,ip"`
27
// Port is the port of elasticsearch instance
28
Port int `yaml:"port" validate:"gte=0,lte=65535"`
29
// SSL (optional) enables ssl for elasticsearch connection
30
SSL bool `yaml:"ssl"`
31
// SSLVerification (optional) disables SSL verification for elasticsearch
32
SSLVerification bool `yaml:"ssl-verification"`
33
// Username for the elasticsearch instance
34
Username string `yaml:"username" validate:"required"`
35
// Password is the password for elasticsearch instance
36
Password string `yaml:"password" validate:"required"`
37
// IndexName is the name of the elasticsearch index
38
IndexName string `yaml:"index-name" validate:"required"`
39
40
HttpClient *retryablehttp.Client `yaml:"-"`
41
ExecutionId string `yaml:"-"`
42
}
43
44
type data struct {
45
Event *output.ResultEvent `json:"event"`
46
Timestamp string `json:"@timestamp"`
47
}
48
49
// Exporter type for elasticsearch
50
type Exporter struct {
51
url string
52
authentication string
53
elasticsearch *http.Client
54
}
55
56
// New creates and returns a new exporter for elasticsearch
57
func New(option *Options) (*Exporter, error) {
58
var ei *Exporter
59
60
dialers := protocolstate.GetDialersWithId(option.ExecutionId)
61
if dialers == nil {
62
return nil, fmt.Errorf("dialers not initialized for %s", option.ExecutionId)
63
}
64
65
var client *http.Client
66
if option.HttpClient != nil {
67
client = option.HttpClient.HTTPClient
68
} else {
69
client = &http.Client{
70
Timeout: 5 * time.Second,
71
Transport: &http.Transport{
72
MaxIdleConns: 10,
73
MaxIdleConnsPerHost: 10,
74
DialContext: dialers.Fastdialer.Dial,
75
DialTLSContext: dialers.Fastdialer.DialTLS,
76
TLSClientConfig: &tls.Config{InsecureSkipVerify: option.SSLVerification},
77
},
78
}
79
}
80
81
// preparing url for elasticsearch
82
scheme := "http://"
83
if option.SSL {
84
scheme = "https://"
85
}
86
// if authentication is required
87
var authentication string
88
if len(option.Username) > 0 && len(option.Password) > 0 {
89
auth := base64.StdEncoding.EncodeToString([]byte(option.Username + ":" + option.Password))
90
auth = "Basic " + auth
91
authentication = auth
92
}
93
var addr string
94
if option.Host != "" {
95
addr = option.Host
96
} else {
97
addr = option.IP
98
}
99
if option.Port != 0 {
100
addr += fmt.Sprintf(":%d", option.Port)
101
}
102
url := fmt.Sprintf("%s%s/%s/_doc", scheme, addr, option.IndexName)
103
104
ei = &Exporter{
105
url: url,
106
authentication: authentication,
107
elasticsearch: client,
108
}
109
return ei, nil
110
}
111
112
// Export exports a passed result event to elasticsearch
113
func (exporter *Exporter) Export(event *output.ResultEvent) error {
114
// creating a request
115
req, err := http.NewRequest(http.MethodPost, exporter.url, nil)
116
if err != nil {
117
return errors.Wrap(err, "could not make request")
118
}
119
if len(exporter.authentication) > 0 {
120
req.Header.Add("Authorization", exporter.authentication)
121
}
122
userAgent := useragent.PickRandom()
123
req.Header.Set("User-Agent", userAgent.Raw)
124
req.Header.Add("Content-Type", "application/json")
125
126
d := data{
127
Event: event,
128
Timestamp: time.Now().Format(time.RFC3339),
129
}
130
b, err := json.Marshal(&d)
131
if err != nil {
132
return err
133
}
134
req.Body = io.NopCloser(bytes.NewReader(b))
135
136
res, err := exporter.elasticsearch.Do(req)
137
if err != nil {
138
return err
139
}
140
defer func() {
141
_ = res.Body.Close()
142
}()
143
144
b, err = io.ReadAll(res.Body)
145
if err != nil {
146
return errors.New(err.Error() + "error thrown by elasticsearch " + string(b))
147
}
148
149
if res.StatusCode >= 300 {
150
return errors.New("elasticsearch responded with an error: " + string(b))
151
}
152
return nil
153
}
154
155
// Close closes the exporter after operation
156
func (exporter *Exporter) Close() error {
157
return nil
158
}
159
160