telegraf/plugins/inputs/win_eventlog/win_eventlog.go

570 lines
16 KiB
Go
Raw Normal View History

//go:generate ../../../tools/readme_config_includer/generator
//go:build windows
// Package win_eventlog Input plugin to collect Windows Event Log messages
2022-09-09 02:49:36 +08:00
//
//revive:disable-next-line:var-naming
package win_eventlog
import (
"bufio"
"bytes"
_ "embed"
"encoding/xml"
"errors"
"fmt"
"path/filepath"
"reflect"
"strings"
"syscall"
"time"
"golang.org/x/sys/windows"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
// WinEventLog config
type WinEventLog struct {
Locale uint32 `toml:"locale"`
EventlogName string `toml:"eventlog_name"`
Query string `toml:"xpath_query"`
FromBeginning bool `toml:"from_beginning"`
ProcessUserData bool `toml:"process_userdata"`
ProcessEventData bool `toml:"process_eventdata"`
Separator string `toml:"separator"`
OnlyFirstLineOfMessage bool `toml:"only_first_line_of_message"`
TimeStampFromEvent bool `toml:"timestamp_from_event"`
EventTags []string `toml:"event_tags"`
EventFields []string `toml:"event_fields"`
ExcludeFields []string `toml:"exclude_fields"`
ExcludeEmpty []string `toml:"exclude_empty"`
Log telegraf.Logger `toml:"-"`
subscription EvtHandle
subscriptionFlag EvtSubscribeFlag
bookmark EvtHandle
}
const bufferSize = 1 << 14
func (*WinEventLog) SampleConfig() string {
return sampleConfig
}
func (w *WinEventLog) Init() error {
w.subscriptionFlag = EvtSubscribeToFutureEvents
if w.FromBeginning {
w.subscriptionFlag = EvtSubscribeStartAtOldestRecord
}
bookmark, err := _EvtCreateBookmark(nil)
if err != nil {
return err
}
w.bookmark = bookmark
return nil
}
func (w *WinEventLog) Start(_ telegraf.Accumulator) error {
subscription, err := w.evtSubscribe()
if err != nil {
return fmt.Errorf("subscription of Windows Event Log failed: %w", err)
}
w.subscription = subscription
w.Log.Debug("Subscription handle id:", w.subscription)
return nil
}
func (w *WinEventLog) Stop() {
_ = _EvtClose(w.subscription)
}
func (w *WinEventLog) GetState() interface{} {
bookmarkXML, err := w.renderBookmark(w.bookmark)
if err != nil {
w.Log.Errorf("State-persistence failed, cannot render bookmark: %w", err)
return ""
}
return bookmarkXML
}
func (w *WinEventLog) SetState(state interface{}) error {
bookmarkXML, ok := state.(string)
if !ok {
return fmt.Errorf("invalid type %T for state", state)
}
ptr, err := syscall.UTF16PtrFromString(bookmarkXML)
if err != nil {
return fmt.Errorf("convertion to pointer failed: %w", err)
}
bookmark, err := _EvtCreateBookmark(ptr)
if err != nil {
return fmt.Errorf("creating bookmark failed: %w", err)
}
w.bookmark = bookmark
w.subscriptionFlag = EvtSubscribeStartAfterBookmark
return nil
}
// Gather Windows Event Log entries
func (w *WinEventLog) Gather(acc telegraf.Accumulator) error {
for {
events, err := w.fetchEvents(w.subscription)
if err != nil {
if errors.Is(err, ERROR_NO_MORE_ITEMS) {
break
}
w.Log.Errorf("Error getting events: %v", err)
return err
}
for i := range events {
// Prepare fields names usage counter
var fieldsUsage = map[string]int{}
tags := map[string]string{}
fields := map[string]interface{}{}
event := events[i]
evt := reflect.ValueOf(&event).Elem()
timeStamp := time.Now()
// Walk through all fields of Event struct to process System tags or fields
for i := 0; i < evt.NumField(); i++ {
fieldName := evt.Type().Field(i).Name
fieldType := evt.Field(i).Type().String()
fieldValue := evt.Field(i).Interface()
computedValues := map[string]interface{}{}
switch fieldName {
case "Source":
fieldValue = event.Source.Name
fieldType = reflect.TypeOf(fieldValue).String()
case "Execution":
fieldValue := event.Execution.ProcessID
fieldType = reflect.TypeOf(fieldValue).String()
fieldName = "ProcessID"
// Look up Process Name from pid
if should, _ := w.shouldProcessField("ProcessName"); should {
processName, err := GetFromSnapProcess(fieldValue)
if err == nil {
computedValues["ProcessName"] = processName
}
}
case "TimeCreated":
fieldValue = event.TimeCreated.SystemTime
fieldType = reflect.TypeOf(fieldValue).String()
if w.TimeStampFromEvent {
timeStamp, err = time.Parse(time.RFC3339Nano, fmt.Sprintf("%v", fieldValue))
if err != nil {
w.Log.Warnf("Error parsing timestamp %q: %v", fieldValue, err)
}
}
case "Correlation":
if should, _ := w.shouldProcessField("ActivityID"); should {
activityID := event.Correlation.ActivityID
if len(activityID) > 0 {
computedValues["ActivityID"] = activityID
}
}
if should, _ := w.shouldProcessField("RelatedActivityID"); should {
relatedActivityID := event.Correlation.RelatedActivityID
if len(relatedActivityID) > 0 {
computedValues["RelatedActivityID"] = relatedActivityID
}
}
case "Security":
computedValues["UserID"] = event.Security.UserID
// Look up UserName and Domain from SID
if should, _ := w.shouldProcessField("UserName"); should {
sid := event.Security.UserID
usid, err := syscall.StringToSid(sid)
if err == nil {
username, domain, _, err := usid.LookupAccount("")
if err == nil {
computedValues["UserName"] = fmt.Sprint(domain, "\\", username)
}
}
}
default:
}
if should, where := w.shouldProcessField(fieldName); should {
if where == "tags" {
strValue := fmt.Sprintf("%v", fieldValue)
if !w.shouldExcludeEmptyField(fieldName, "string", strValue) {
tags[fieldName] = strValue
fieldsUsage[fieldName]++
}
} else if where == "fields" {
if !w.shouldExcludeEmptyField(fieldName, fieldType, fieldValue) {
fields[fieldName] = fieldValue
fieldsUsage[fieldName]++
}
}
}
// Insert computed fields
for computedKey, computedValue := range computedValues {
if should, where := w.shouldProcessField(computedKey); should {
if where == "tags" {
tags[computedKey] = fmt.Sprintf("%v", computedValue)
fieldsUsage[computedKey]++
} else if where == "fields" {
fields[computedKey] = computedValue
fieldsUsage[computedKey]++
}
}
}
}
// Unroll additional XML
var xmlFields []EventField
if w.ProcessUserData {
fieldsUserData, xmlFieldsUsage := UnrollXMLFields(event.UserData.InnerXML, fieldsUsage, w.Separator)
xmlFields = append(xmlFields, fieldsUserData...)
fieldsUsage = xmlFieldsUsage
}
if w.ProcessEventData {
fieldsEventData, xmlFieldsUsage := UnrollXMLFields(event.EventData.InnerXML, fieldsUsage, w.Separator)
xmlFields = append(xmlFields, fieldsEventData...)
fieldsUsage = xmlFieldsUsage
}
uniqueXMLFields := UniqueFieldNames(xmlFields, fieldsUsage, w.Separator)
for _, xmlField := range uniqueXMLFields {
if !w.shouldExclude(xmlField.Name) {
fields[xmlField.Name] = xmlField.Value
}
}
// Pass collected metrics
acc.AddFields("win_eventlog", fields, tags, timeStamp)
}
}
return nil
}
func (w *WinEventLog) shouldExclude(field string) (should bool) {
for _, excludePattern := range w.ExcludeFields {
// Check if field name matches excluded list
if matched, _ := filepath.Match(excludePattern, field); matched {
return true
}
}
return false
}
func (w *WinEventLog) shouldProcessField(field string) (should bool, list string) {
for _, pattern := range w.EventTags {
if matched, _ := filepath.Match(pattern, field); matched {
// Tags are not excluded
return true, "tags"
}
}
for _, pattern := range w.EventFields {
if matched, _ := filepath.Match(pattern, field); matched {
if w.shouldExclude(field) {
return false, "excluded"
}
return true, "fields"
}
}
return false, "excluded"
}
func (w *WinEventLog) shouldExcludeEmptyField(field string, fieldType string, fieldValue interface{}) (should bool) {
for _, pattern := range w.ExcludeEmpty {
if matched, _ := filepath.Match(pattern, field); matched {
switch fieldType {
case "string":
return len(fieldValue.(string)) < 1
case "int":
return fieldValue.(int) == 0
case "uint32":
return fieldValue.(uint32) == 0
}
}
}
return false
}
func (w *WinEventLog) evtSubscribe() (EvtHandle, error) {
sigEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return 0, err
}
defer windows.CloseHandle(sigEvent)
logNamePtr, err := syscall.UTF16PtrFromString(w.EventlogName)
if err != nil {
return 0, err
}
xqueryPtr, err := syscall.UTF16PtrFromString(w.Query)
if err != nil {
return 0, err
}
var bookmark EvtHandle
if w.subscriptionFlag == EvtSubscribeStartAfterBookmark {
bookmark = w.bookmark
}
subsHandle, err := _EvtSubscribe(0, uintptr(sigEvent), logNamePtr, xqueryPtr, bookmark, 0, 0, w.subscriptionFlag)
if err != nil {
return 0, err
}
return subsHandle, nil
}
func (w *WinEventLog) fetchEventHandles(subsHandle EvtHandle) ([]EvtHandle, error) {
var eventsNumber uint32
var evtReturned uint32
eventsNumber = 5
eventHandles := make([]EvtHandle, eventsNumber)
err := _EvtNext(subsHandle, eventsNumber, &eventHandles[0], 0, 0, &evtReturned)
if err != nil {
if errors.Is(err, ERROR_INVALID_OPERATION) && evtReturned == 0 {
return nil, ERROR_NO_MORE_ITEMS
}
return nil, err
}
return eventHandles[:evtReturned], nil
}
func (w *WinEventLog) fetchEvents(subsHandle EvtHandle) ([]Event, error) {
var events []Event
eventHandles, err := w.fetchEventHandles(subsHandle)
if err != nil {
return nil, err
}
var evterr error
for _, eventHandle := range eventHandles {
if eventHandle == 0 {
continue
}
if event, err := w.renderEvent(eventHandle); err == nil {
events = append(events, event)
}
if err := _EvtUpdateBookmark(w.bookmark, eventHandle); err != nil && evterr == nil {
evterr = err
}
if err := _EvtClose(eventHandle); err != nil && evterr == nil {
evterr = err
}
}
return events, evterr
}
func (w *WinEventLog) renderBookmark(bookmark EvtHandle) (string, error) {
var bufferUsed, propertyCount uint32
buf := make([]byte, bufferSize)
err := _EvtRender(0, bookmark, EvtRenderBookmark, uint32(len(buf)), &buf[0], &bufferUsed, &propertyCount)
if err != nil {
return "", err
}
x, err := DecodeUTF16(buf[:bufferUsed])
if err != nil {
return "", err
}
if x[len(x)-1] == 0 {
x = x[:len(x)-1]
}
return string(x), err
}
func (w *WinEventLog) renderEvent(eventHandle EvtHandle) (Event, error) {
var bufferUsed, propertyCount uint32
buf := make([]byte, bufferSize)
event := Event{}
err := _EvtRender(0, eventHandle, EvtRenderEventXML, uint32(len(buf)), &buf[0], &bufferUsed, &propertyCount)
if err != nil {
return event, err
}
eventXML, err := DecodeUTF16(buf[:bufferUsed])
if err != nil {
return event, err
}
err = xml.Unmarshal(eventXML, &event)
if err != nil {
//nolint:nilerr // We can return event without most text values, that way we will not lose information
// This can happen when processing Forwarded Events
return event, nil
}
// Do resolve local messages the usual way, while using built-in information for events forwarded by WEC.
// This is a safety measure as the underlying Windows-internal EvtFormatMessage might segfault in cases
// where the publisher (i.e. the remote machine which forwarded the event) is unavailable e.g. due to
// a reboot. See https://github.com/influxdata/telegraf/issues/12328 for the full story.
if event.RenderingInfo == nil {
return w.renderLocalMessage(event, eventHandle)
}
// We got 'RenderInfo' elements, so try to apply them in the following function
return w.renderRemoteMessage(event)
}
func (w *WinEventLog) renderLocalMessage(event Event, eventHandle EvtHandle) (Event, error) {
publisherHandle, err := openPublisherMetadata(0, event.Source.Name, w.Locale)
if err != nil {
return event, nil //nolint:nilerr // We can return event without most values
}
defer _EvtClose(publisherHandle) //nolint:errcheck // Ignore error returned during Close
// Populating text values
keywords, err := formatEventString(EvtFormatMessageKeyword, eventHandle, publisherHandle)
if err == nil {
event.Keywords = keywords
}
message, err := formatEventString(EvtFormatMessageEvent, eventHandle, publisherHandle)
if err == nil {
if w.OnlyFirstLineOfMessage {
scanner := bufio.NewScanner(strings.NewReader(message))
scanner.Scan()
message = scanner.Text()
}
event.Message = message
}
level, err := formatEventString(EvtFormatMessageLevel, eventHandle, publisherHandle)
if err == nil {
event.LevelText = level
}
task, err := formatEventString(EvtFormatMessageTask, eventHandle, publisherHandle)
if err == nil {
event.TaskText = task
}
opcode, err := formatEventString(EvtFormatMessageOpcode, eventHandle, publisherHandle)
if err == nil {
event.OpcodeText = opcode
}
return event, nil
}
func (w *WinEventLog) renderRemoteMessage(event Event) (Event, error) {
// Populating text values from RenderingInfo part of the XML
if len(event.RenderingInfo.Keywords) > 0 {
event.Keywords = strings.Join(event.RenderingInfo.Keywords, ",")
}
if event.RenderingInfo.Message != "" {
message := event.RenderingInfo.Message
if w.OnlyFirstLineOfMessage {
scanner := bufio.NewScanner(strings.NewReader(message))
scanner.Scan()
message = scanner.Text()
}
event.Message = message
}
if event.RenderingInfo.Level != "" {
event.LevelText = event.RenderingInfo.Level
}
if event.RenderingInfo.Task != "" {
event.TaskText = event.RenderingInfo.Task
}
if event.RenderingInfo.Opcode != "" {
event.OpcodeText = event.RenderingInfo.Opcode
}
return event, nil
}
func formatEventString(
messageFlag EvtFormatMessageFlag,
eventHandle EvtHandle,
publisherHandle EvtHandle,
) (string, error) {
var bufferUsed uint32
err := _EvtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag,
0, nil, &bufferUsed)
if err != nil && !errors.Is(err, ERROR_INSUFFICIENT_BUFFER) {
return "", err
}
// Handle empty elements
if bufferUsed < 1 {
return "", nil
}
bufferUsed *= 2
buffer := make([]byte, bufferUsed)
bufferUsed = 0
err = _EvtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag,
uint32(len(buffer)/2), &buffer[0], &bufferUsed)
bufferUsed *= 2
if err != nil {
return "", err
}
result, err := DecodeUTF16(buffer[:bufferUsed])
if err != nil {
return "", err
}
var out string
if messageFlag == EvtFormatMessageKeyword {
// Keywords are returned as array of a zero-terminated strings
splitZero := func(c rune) bool { return c == '\x00' }
eventKeywords := strings.FieldsFunc(string(result), splitZero)
// So convert them to comma-separated string
out = strings.Join(eventKeywords, ",")
} else {
result := bytes.Trim(result, "\x00")
out = string(result)
}
return out, nil
}
// openPublisherMetadata opens a handle to the publisher's metadata. Close must
// be called on returned EvtHandle when finished with the handle.
func openPublisherMetadata(
session EvtHandle,
publisherName string,
lang uint32,
) (EvtHandle, error) {
p, err := syscall.UTF16PtrFromString(publisherName)
if err != nil {
return 0, err
}
h, err := _EvtOpenPublisherMetadata(session, p, nil, lang, 0)
if err != nil {
return 0, err
}
return h, nil
}
func init() {
inputs.Add("win_eventlog", func() telegraf.Input {
return &WinEventLog{
ProcessUserData: true,
ProcessEventData: true,
Separator: "_",
OnlyFirstLineOfMessage: true,
TimeStampFromEvent: true,
EventTags: []string{"Source", "EventID", "Level", "LevelText", "Keywords", "Channel", "Computer"},
EventFields: []string{"*"},
ExcludeEmpty: []string{"Task", "Opcode", "*ActivityID", "UserID"},
}
})
}