Skip to content

Commit 9518184

Browse files
authored
refact pkg/acquisition: split wineventlog.go (#4036)
1 parent 45e9455 commit 9518184

File tree

9 files changed

+581
-549
lines changed

9 files changed

+581
-549
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package wineventlogacquisition
2+
3+
import (
4+
"context"
5+
"encoding/xml"
6+
"errors"
7+
"fmt"
8+
"net/url"
9+
"strconv"
10+
"strings"
11+
"syscall"
12+
13+
yaml "github.com/goccy/go-yaml"
14+
"github.com/google/winops/winlog"
15+
"github.com/google/winops/winlog/wevtapi"
16+
log "github.com/sirupsen/logrus"
17+
"golang.org/x/sys/windows"
18+
19+
"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
20+
"github.com/crowdsecurity/crowdsec/pkg/metrics"
21+
)
22+
23+
type Configuration struct {
24+
configuration.DataSourceCommonCfg `yaml:",inline"`
25+
EventChannel string `yaml:"event_channel"`
26+
EventLevel string `yaml:"event_level"`
27+
EventIDs []int `yaml:"event_ids"`
28+
XPathQuery string `yaml:"xpath_query"`
29+
EventFile string
30+
PrettyName string `yaml:"pretty_name"`
31+
}
32+
33+
type QueryList struct {
34+
Select Select `xml:"Query>Select"`
35+
}
36+
37+
type Select struct {
38+
Path string `xml:"Path,attr,omitempty"`
39+
Query string `xml:",chardata"`
40+
}
41+
42+
func logLevelToInt(logLevel string) ([]string, error) {
43+
switch strings.ToUpper(logLevel) {
44+
case "CRITICAL":
45+
return []string{"1"}, nil
46+
case "ERROR":
47+
return []string{"2"}, nil
48+
case "WARNING":
49+
return []string{"3"}, nil
50+
case "INFORMATION":
51+
return []string{"0", "4"}, nil
52+
case "VERBOSE":
53+
return []string{"5"}, nil
54+
default:
55+
return nil, errors.New("invalid log level")
56+
}
57+
}
58+
59+
func (s *Source) buildXpathQuery() (string, error) {
60+
var query string
61+
queryComponents := [][]string{}
62+
if s.config.EventIDs != nil {
63+
eventIds := []string{}
64+
for _, id := range s.config.EventIDs {
65+
eventIds = append(eventIds, fmt.Sprintf("EventID=%d", id))
66+
}
67+
queryComponents = append(queryComponents, eventIds)
68+
}
69+
if s.config.EventLevel != "" {
70+
levels, err := logLevelToInt(s.config.EventLevel)
71+
logLevels := []string{}
72+
if err != nil {
73+
return "", err
74+
}
75+
for _, level := range levels {
76+
logLevels = append(logLevels, fmt.Sprintf("Level=%s", level))
77+
}
78+
queryComponents = append(queryComponents, logLevels)
79+
}
80+
if len(queryComponents) > 0 {
81+
andList := []string{}
82+
for _, component := range queryComponents {
83+
andList = append(andList, fmt.Sprintf("(%s)", strings.Join(component, " or ")))
84+
}
85+
query = fmt.Sprintf("*[System[%s]]", strings.Join(andList, " and "))
86+
} else {
87+
query = "*"
88+
}
89+
queryList := QueryList{Select: Select{Path: s.config.EventChannel, Query: query}}
90+
xpathQuery, err := xml.Marshal(queryList)
91+
if err != nil {
92+
if s.logger != nil {
93+
s.logger.Errorf("Failed to marshal XPath query: %v", err)
94+
}
95+
s.logger.Errorf("Serialize failed: %v", err)
96+
return "", err
97+
}
98+
if s.logger != nil {
99+
s.logger.Debugf("xpathQuery: %s", xpathQuery)
100+
}
101+
return string(xpathQuery), nil
102+
}
103+
104+
func (s *Source) generateConfig(query string, live bool) (*winlog.SubscribeConfig, error) {
105+
var config winlog.SubscribeConfig
106+
var err error
107+
108+
if live {
109+
// Create a subscription signaler.
110+
config.SignalEvent, err = windows.CreateEvent(
111+
nil, // Default security descriptor.
112+
1, // Manual reset.
113+
1, // Initial state is signaled.
114+
nil) // Optional name.
115+
if err != nil {
116+
return &config, fmt.Errorf("windows.CreateEvent failed: %v", err)
117+
}
118+
config.Flags = wevtapi.EvtSubscribeToFutureEvents
119+
} else {
120+
config.ChannelPath, err = syscall.UTF16PtrFromString(s.config.EventFile)
121+
if err != nil {
122+
return &config, fmt.Errorf("syscall.UTF16PtrFromString failed: %v", err)
123+
}
124+
config.Flags = wevtapi.EvtQueryFilePath | wevtapi.EvtQueryForwardDirection
125+
}
126+
config.Query, err = syscall.UTF16PtrFromString(query)
127+
if err != nil {
128+
return &config, fmt.Errorf("syscall.UTF16PtrFromString failed: %v", err)
129+
}
130+
131+
return &config, nil
132+
}
133+
134+
func (s *Source) UnmarshalConfig(yamlConfig []byte) error {
135+
s.config = Configuration{}
136+
137+
err := yaml.UnmarshalWithOptions(yamlConfig, &s.config, yaml.Strict())
138+
if err != nil {
139+
return fmt.Errorf("cannot parse wineventlog configuration: %s", yaml.FormatError(err, false, false))
140+
}
141+
142+
if s.config.EventChannel != "" && s.config.XPathQuery != "" {
143+
return errors.New("event_channel and xpath_query are mutually exclusive")
144+
}
145+
146+
if s.config.EventChannel == "" && s.config.XPathQuery == "" {
147+
return errors.New("event_channel or xpath_query must be set")
148+
}
149+
150+
s.config.Mode = configuration.TAIL_MODE
151+
152+
if s.config.XPathQuery != "" {
153+
s.query = s.config.XPathQuery
154+
} else {
155+
s.query, err = s.buildXpathQuery()
156+
if err != nil {
157+
return fmt.Errorf("buildXpathQuery failed: %v", err)
158+
}
159+
}
160+
161+
if s.config.PrettyName != "" {
162+
s.name = s.config.PrettyName
163+
} else {
164+
s.name = s.query
165+
}
166+
167+
return nil
168+
}
169+
170+
func (s *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.Entry, metricsLevel metrics.AcquisitionMetricsLevel) error {
171+
s.logger = logger
172+
s.metricsLevel = metricsLevel
173+
174+
err := s.UnmarshalConfig(yamlConfig)
175+
if err != nil {
176+
return err
177+
}
178+
179+
s.evtConfig, err = s.generateConfig(s.query, true)
180+
if err != nil {
181+
return err
182+
}
183+
184+
return nil
185+
}
186+
187+
func (s *Source) ConfigureByDSN(ctx context.Context, dsn string, labels map[string]string, logger *log.Entry, uuid string) error {
188+
if !strings.HasPrefix(dsn, "wineventlog://") {
189+
return fmt.Errorf("invalid DSN %s for wineventlog source, must start with wineventlog://", dsn)
190+
}
191+
192+
s.logger = logger
193+
s.config = Configuration{}
194+
195+
dsn = strings.TrimPrefix(dsn, "wineventlog://")
196+
197+
args := strings.Split(dsn, "?")
198+
199+
if args[0] == "" {
200+
return errors.New("empty wineventlog:// DSN")
201+
}
202+
203+
if len(args) > 2 {
204+
return errors.New("too many arguments in DSN")
205+
}
206+
207+
s.config.EventFile = args[0]
208+
209+
if len(args) == 2 && args[1] != "" {
210+
params, err := url.ParseQuery(args[1])
211+
if err != nil {
212+
return fmt.Errorf("failed to parse DSN parameters: %w", err)
213+
}
214+
215+
for key, value := range params {
216+
switch key {
217+
case "log_level":
218+
if len(value) != 1 {
219+
return errors.New("log_level must be a single value")
220+
}
221+
lvl, err := log.ParseLevel(value[0])
222+
if err != nil {
223+
return fmt.Errorf("failed to parse log_level: %s", err)
224+
}
225+
s.logger.Logger.SetLevel(lvl)
226+
case "event_id":
227+
for _, id := range value {
228+
evtid, err := strconv.Atoi(id)
229+
if err != nil {
230+
return fmt.Errorf("failed to parse event_id: %s", err)
231+
}
232+
s.config.EventIDs = append(s.config.EventIDs, evtid)
233+
}
234+
case "event_level":
235+
if len(value) != 1 {
236+
return errors.New("event_level must be a single value")
237+
}
238+
s.config.EventLevel = value[0]
239+
}
240+
}
241+
}
242+
243+
var err error
244+
245+
// FIXME: handle custom xpath query
246+
s.query, err = s.buildXpathQuery()
247+
if err != nil {
248+
return fmt.Errorf("buildXpathQuery failed: %w", err)
249+
}
250+
251+
s.logger.Debugf("query: %s\n", s.query)
252+
253+
s.evtConfig, err = s.generateConfig(s.query, false)
254+
if err != nil {
255+
return fmt.Errorf("generateConfig failed: %w", err)
256+
}
257+
258+
return nil
259+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package wineventlogacquisition
2+
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
6+
"github.com/crowdsecurity/crowdsec/pkg/metrics"
7+
)
8+
9+
func (*Source) GetMetrics() []prometheus.Collector {
10+
return []prometheus.Collector{
11+
metrics.WineventlogDataSourceLinesRead,
12+
}
13+
}
14+
15+
func (*Source) GetAggregMetrics() []prometheus.Collector {
16+
return []prometheus.Collector{
17+
metrics.WineventlogDataSourceLinesRead,
18+
}
19+
}

0 commit comments

Comments
 (0)