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  }