github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/wineventlog/wineventlog_windows.go (about) 1 package wineventlogacquisition 2 3 import ( 4 "encoding/xml" 5 "errors" 6 "fmt" 7 "runtime" 8 "strings" 9 "syscall" 10 "time" 11 12 "github.com/google/winops/winlog" 13 "github.com/google/winops/winlog/wevtapi" 14 "github.com/prometheus/client_golang/prometheus" 15 log "github.com/sirupsen/logrus" 16 "golang.org/x/sys/windows" 17 "gopkg.in/tomb.v2" 18 "gopkg.in/yaml.v2" 19 20 "github.com/crowdsecurity/go-cs-lib/trace" 21 22 "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" 23 "github.com/crowdsecurity/crowdsec/pkg/types" 24 ) 25 26 type WinEventLogConfiguration struct { 27 configuration.DataSourceCommonCfg `yaml:",inline"` 28 EventChannel string `yaml:"event_channel"` 29 EventLevel string `yaml:"event_level"` 30 EventIDs []int `yaml:"event_ids"` 31 XPathQuery string `yaml:"xpath_query"` 32 EventFile string `yaml:"event_file"` 33 PrettyName string `yaml:"pretty_name"` 34 } 35 36 type WinEventLogSource struct { 37 metricsLevel int 38 config WinEventLogConfiguration 39 logger *log.Entry 40 evtConfig *winlog.SubscribeConfig 41 query string 42 name string 43 } 44 45 type QueryList struct { 46 Select Select `xml:"Query>Select"` 47 } 48 49 type Select struct { 50 Path string `xml:"Path,attr"` 51 Query string `xml:",chardata"` 52 } 53 54 var linesRead = prometheus.NewCounterVec( 55 prometheus.CounterOpts{ 56 Name: "cs_winevtlogsource_hits_total", 57 Help: "Total event that were read.", 58 }, 59 []string{"source"}) 60 61 func logLevelToInt(logLevel string) ([]string, error) { 62 switch strings.ToUpper(logLevel) { 63 case "CRITICAL": 64 return []string{"1"}, nil 65 case "ERROR": 66 return []string{"2"}, nil 67 case "WARNING": 68 return []string{"3"}, nil 69 case "INFORMATION": 70 return []string{"0", "4"}, nil 71 case "VERBOSE": 72 return []string{"5"}, nil 73 default: 74 return nil, errors.New("invalid log level") 75 } 76 } 77 78 // This is lifted from winops/winlog, but we only want to render the basic XML string, we don't need the extra fluff 79 func (w *WinEventLogSource) getXMLEvents(config *winlog.SubscribeConfig, publisherCache map[string]windows.Handle, resultSet windows.Handle, maxEvents int) ([]string, error) { 80 var events = make([]windows.Handle, maxEvents) 81 var returned uint32 82 83 // Get handles to events from the result set. 84 err := wevtapi.EvtNext( 85 resultSet, // Handle to query or subscription result set. 86 uint32(len(events)), // The number of events to attempt to retrieve. 87 &events[0], // Pointer to the array of event handles. 88 2000, // Timeout in milliseconds to wait. 89 0, // Reserved. Must be zero. 90 &returned) // The number of handles in the array that are set by the API. 91 if err == windows.ERROR_NO_MORE_ITEMS { 92 return nil, err 93 } else if err != nil { 94 return nil, fmt.Errorf("wevtapi.EvtNext failed: %v", err) 95 } 96 97 // Event handles must be closed after they are returned by EvtNext whether or not we use them. 98 defer func() { 99 for _, event := range events[:returned] { 100 winlog.Close(event) 101 } 102 }() 103 104 // Render events. 105 var renderedEvents []string 106 for _, event := range events[:returned] { 107 // Render the basic XML representation of the event. 108 fragment, err := winlog.RenderFragment(event, wevtapi.EvtRenderEventXml) 109 if err != nil { 110 w.logger.Errorf("Failed to render event with RenderFragment, skipping: %v", err) 111 continue 112 } 113 w.logger.Tracef("Rendered event: %s", fragment) 114 renderedEvents = append(renderedEvents, fragment) 115 } 116 return renderedEvents, err 117 } 118 119 func (w *WinEventLogSource) buildXpathQuery() (string, error) { 120 var query string 121 queryComponents := [][]string{} 122 if w.config.EventIDs != nil { 123 eventIds := []string{} 124 for _, id := range w.config.EventIDs { 125 eventIds = append(eventIds, fmt.Sprintf("EventID=%d", id)) 126 } 127 queryComponents = append(queryComponents, eventIds) 128 } 129 if w.config.EventLevel != "" { 130 levels, err := logLevelToInt(w.config.EventLevel) 131 logLevels := []string{} 132 if err != nil { 133 return "", err 134 } 135 for _, level := range levels { 136 logLevels = append(logLevels, fmt.Sprintf("Level=%s", level)) 137 } 138 queryComponents = append(queryComponents, logLevels) 139 } 140 if len(queryComponents) > 0 { 141 andList := []string{} 142 for _, component := range queryComponents { 143 andList = append(andList, fmt.Sprintf("(%s)", strings.Join(component, " or "))) 144 } 145 query = fmt.Sprintf("*[System[%s]]", strings.Join(andList, " and ")) 146 } else { 147 query = "*" 148 } 149 queryList := QueryList{Select: Select{Path: w.config.EventChannel, Query: query}} 150 xpathQuery, err := xml.Marshal(queryList) 151 if err != nil { 152 w.logger.Errorf("Marshal failed: %v", err) 153 return "", err 154 } 155 w.logger.Debugf("xpathQuery: %s", xpathQuery) 156 return string(xpathQuery), nil 157 } 158 159 func (w *WinEventLogSource) getEvents(out chan types.Event, t *tomb.Tomb) error { 160 subscription, err := winlog.Subscribe(w.evtConfig) 161 if err != nil { 162 w.logger.Errorf("Failed to subscribe to event log: %s", err) 163 return err 164 } 165 defer winlog.Close(subscription) 166 publisherCache := make(map[string]windows.Handle) 167 defer func() { 168 for _, h := range publisherCache { 169 winlog.Close(h) 170 } 171 }() 172 for { 173 select { 174 case <-t.Dying(): 175 w.logger.Infof("wineventlog is dying") 176 return nil 177 default: 178 status, err := windows.WaitForSingleObject(w.evtConfig.SignalEvent, 1000) 179 if err != nil { 180 w.logger.Errorf("WaitForSingleObject failed: %s", err) 181 return err 182 } 183 if status == syscall.WAIT_OBJECT_0 { 184 renderedEvents, err := w.getXMLEvents(w.evtConfig, publisherCache, subscription, 500) 185 if err == windows.ERROR_NO_MORE_ITEMS { 186 windows.ResetEvent(w.evtConfig.SignalEvent) 187 } else if err != nil { 188 w.logger.Errorf("getXMLEvents failed: %v", err) 189 continue 190 } 191 for _, event := range renderedEvents { 192 if w.metricsLevel != configuration.METRICS_NONE { 193 linesRead.With(prometheus.Labels{"source": w.name}).Inc() 194 } 195 l := types.Line{} 196 l.Raw = event 197 l.Module = w.GetName() 198 l.Labels = w.config.Labels 199 l.Time = time.Now() 200 l.Src = w.name 201 l.Process = true 202 if !w.config.UseTimeMachine { 203 out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.LIVE} 204 } else { 205 out <- types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.TIMEMACHINE} 206 } 207 } 208 } 209 210 } 211 } 212 } 213 214 func (w *WinEventLogSource) generateConfig(query string) (*winlog.SubscribeConfig, error) { 215 var config winlog.SubscribeConfig 216 var err error 217 218 // Create a subscription signaler. 219 config.SignalEvent, err = windows.CreateEvent( 220 nil, // Default security descriptor. 221 1, // Manual reset. 222 1, // Initial state is signaled. 223 nil) // Optional name. 224 if err != nil { 225 return &config, fmt.Errorf("windows.CreateEvent failed: %v", err) 226 } 227 config.Flags = wevtapi.EvtSubscribeToFutureEvents 228 config.Query, err = syscall.UTF16PtrFromString(query) 229 if err != nil { 230 return &config, fmt.Errorf("syscall.UTF16PtrFromString failed: %v", err) 231 } 232 233 return &config, nil 234 } 235 236 func (w *WinEventLogSource) GetUuid() string { 237 return w.config.UniqueId 238 } 239 240 func (w *WinEventLogSource) UnmarshalConfig(yamlConfig []byte) error { 241 w.config = WinEventLogConfiguration{} 242 243 err := yaml.UnmarshalStrict(yamlConfig, &w.config) 244 if err != nil { 245 return fmt.Errorf("unable to parse configuration: %v", err) 246 } 247 248 if w.config.EventChannel != "" && w.config.XPathQuery != "" { 249 return fmt.Errorf("event_channel and xpath_query are mutually exclusive") 250 } 251 252 if w.config.EventChannel == "" && w.config.XPathQuery == "" { 253 return fmt.Errorf("event_channel or xpath_query must be set") 254 } 255 256 w.config.Mode = configuration.TAIL_MODE 257 258 if w.config.XPathQuery != "" { 259 w.query = w.config.XPathQuery 260 } else { 261 w.query, err = w.buildXpathQuery() 262 if err != nil { 263 return fmt.Errorf("buildXpathQuery failed: %v", err) 264 } 265 } 266 267 if w.config.PrettyName != "" { 268 w.name = w.config.PrettyName 269 } else { 270 w.name = w.query 271 } 272 273 return nil 274 } 275 276 func (w *WinEventLogSource) Configure(yamlConfig []byte, logger *log.Entry, MetricsLevel int) error { 277 w.logger = logger 278 w.metricsLevel = MetricsLevel 279 280 err := w.UnmarshalConfig(yamlConfig) 281 if err != nil { 282 return err 283 } 284 285 w.evtConfig, err = w.generateConfig(w.query) 286 if err != nil { 287 return err 288 } 289 290 return nil 291 } 292 293 func (w *WinEventLogSource) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error { 294 return nil 295 } 296 297 func (w *WinEventLogSource) GetMode() string { 298 return w.config.Mode 299 } 300 301 func (w *WinEventLogSource) SupportedModes() []string { 302 return []string{configuration.TAIL_MODE} 303 } 304 305 func (w *WinEventLogSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error { 306 return nil 307 } 308 309 func (w *WinEventLogSource) GetMetrics() []prometheus.Collector { 310 return []prometheus.Collector{linesRead} 311 } 312 313 func (w *WinEventLogSource) GetAggregMetrics() []prometheus.Collector { 314 return []prometheus.Collector{linesRead} 315 } 316 317 func (w *WinEventLogSource) GetName() string { 318 return "wineventlog" 319 } 320 321 func (w *WinEventLogSource) CanRun() error { 322 if runtime.GOOS != "windows" { 323 return errors.New("windows event log acquisition is only supported on Windows") 324 } 325 return nil 326 } 327 328 func (w *WinEventLogSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error { 329 t.Go(func() error { 330 defer trace.CatchPanic("crowdsec/acquis/wineventlog/streaming") 331 return w.getEvents(out, t) 332 }) 333 return nil 334 } 335 336 func (w *WinEventLogSource) Dump() interface{} { 337 return w 338 }