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