221 lines
6.5 KiB
Go
221 lines
6.5 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package eventlog
|
|
|
|
import (
|
|
"expvar"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/elastic/beats/libbeat/beat"
|
|
"github.com/elastic/beats/libbeat/common"
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
|
|
"github.com/elastic/beats/winlogbeat/checkpoint"
|
|
"github.com/elastic/beats/winlogbeat/sys"
|
|
)
|
|
|
|
// Debug selectors used in this package.
|
|
const (
|
|
debugSelector = "eventlog"
|
|
detailSelector = "eventlog_detail"
|
|
)
|
|
|
|
// Debug logging functions for this package.
|
|
var (
|
|
debugf = logp.MakeDebug(debugSelector)
|
|
detailf = logp.MakeDebug(detailSelector)
|
|
)
|
|
|
|
var (
|
|
// dropReasons contains counters for the number of dropped events for each
|
|
// reason.
|
|
dropReasons = expvar.NewMap("drop_reasons")
|
|
|
|
// readErrors contains counters for the read error types that occur.
|
|
readErrors = expvar.NewMap("read_errors")
|
|
)
|
|
|
|
// EventLog is an interface to a Windows Event Log.
|
|
type EventLog interface {
|
|
// Open the event log. state points to the last successfully read event
|
|
// in this event log. Read will resume from the next record. To start reading
|
|
// from the first event specify a zero-valued EventLogState.
|
|
Open(state checkpoint.EventLogState) error
|
|
|
|
// Read records from the event log.
|
|
Read() ([]Record, error)
|
|
|
|
// Close the event log. It should not be re-opened after closing.
|
|
Close() error
|
|
|
|
// Name returns the event log's name.
|
|
Name() string
|
|
}
|
|
|
|
// Record represents a single event from the log.
|
|
type Record struct {
|
|
sys.Event
|
|
API string // The event log API type used to read the record.
|
|
XML string // XML representation of the event.
|
|
Offset checkpoint.EventLogState // Position of the record within its source stream.
|
|
}
|
|
|
|
// ToMapStr returns a new MapStr containing the data from this Record.
|
|
func (e Record) ToEvent() beat.Event {
|
|
m := common.MapStr{
|
|
"type": e.API,
|
|
"log_name": e.Channel,
|
|
"source_name": e.Provider.Name,
|
|
"computer_name": e.Computer,
|
|
"record_number": strconv.FormatUint(e.RecordID, 10),
|
|
"event_id": e.EventIdentifier.ID,
|
|
}
|
|
|
|
addOptional(m, "xml", e.XML)
|
|
addOptional(m, "provider_guid", e.Provider.GUID)
|
|
addOptional(m, "version", e.Version)
|
|
addOptional(m, "level", e.Level)
|
|
addOptional(m, "task", e.Task)
|
|
addOptional(m, "opcode", e.Opcode)
|
|
addOptional(m, "keywords", e.Keywords)
|
|
addOptional(m, "message", sys.RemoveWindowsLineEndings(e.Message))
|
|
addOptional(m, "message_error", e.RenderErr)
|
|
|
|
// Correlation
|
|
addOptional(m, "activity_id", e.Correlation.ActivityID)
|
|
addOptional(m, "related_activity_id", e.Correlation.RelatedActivityID)
|
|
|
|
// Execution
|
|
addOptional(m, "process_id", e.Execution.ProcessID)
|
|
addOptional(m, "thread_id", e.Execution.ThreadID)
|
|
addOptional(m, "processor_id", e.Execution.ProcessorID)
|
|
addOptional(m, "session_id", e.Execution.SessionID)
|
|
addOptional(m, "kernel_time", e.Execution.KernelTime)
|
|
addOptional(m, "user_time", e.Execution.UserTime)
|
|
addOptional(m, "processor_time", e.Execution.ProcessorTime)
|
|
|
|
if e.User.Identifier != "" {
|
|
user := common.MapStr{
|
|
"identifier": e.User.Identifier,
|
|
}
|
|
m["user"] = user
|
|
|
|
addOptional(user, "name", e.User.Name)
|
|
addOptional(user, "domain", e.User.Domain)
|
|
addOptional(user, "type", e.User.Type.String())
|
|
}
|
|
|
|
addPairs(m, "event_data", e.EventData.Pairs)
|
|
userData := addPairs(m, "user_data", e.UserData.Pairs)
|
|
addOptional(userData, "xml_name", e.UserData.Name.Local)
|
|
|
|
return beat.Event{
|
|
Timestamp: e.TimeCreated.SystemTime,
|
|
Fields: m,
|
|
Private: e.Offset,
|
|
}
|
|
}
|
|
|
|
// addOptional adds a key and value to the given MapStr if the value is not the
|
|
// zero value for the type of v. It is safe to call the function with a nil
|
|
// MapStr.
|
|
func addOptional(m common.MapStr, key string, v interface{}) {
|
|
if m != nil && !isZero(v) {
|
|
m[key] = v
|
|
}
|
|
}
|
|
|
|
// addPairs adds a new dictionary to the given MapStr. The key/value pairs are
|
|
// added to the new dictionary. If any keys are duplicates, the first key/value
|
|
// pair is added and the remaining duplicates are dropped.
|
|
//
|
|
// The new dictionary is added to the given MapStr and it is also returned for
|
|
// convenience purposes.
|
|
func addPairs(m common.MapStr, key string, pairs []sys.KeyValue) common.MapStr {
|
|
if len(pairs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
h := make(common.MapStr, len(pairs))
|
|
for i, kv := range pairs {
|
|
// Ignore empty values.
|
|
if kv.Value == "" {
|
|
continue
|
|
}
|
|
|
|
// If the key name is empty or if it the default of "Data" then
|
|
// assign a generic name of paramN.
|
|
k := kv.Key
|
|
if k == "" || k == "Data" {
|
|
k = fmt.Sprintf("param%d", i+1)
|
|
}
|
|
|
|
// Do not overwrite.
|
|
_, exists := h[k]
|
|
if !exists {
|
|
h[k] = sys.RemoveWindowsLineEndings(kv.Value)
|
|
} else {
|
|
debugf("Dropping key/value (k=%s, v=%s) pair because key already "+
|
|
"exists. event=%+v", k, kv.Value, m)
|
|
}
|
|
}
|
|
|
|
if len(h) == 0 {
|
|
return nil
|
|
}
|
|
|
|
m[key] = h
|
|
return h
|
|
}
|
|
|
|
// isZero return true if the given value is the zero value for its type.
|
|
func isZero(i interface{}) bool {
|
|
v := reflect.ValueOf(i)
|
|
switch v.Kind() {
|
|
case reflect.Array, reflect.String:
|
|
return v.Len() == 0
|
|
case reflect.Bool:
|
|
return !v.Bool()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return v.Int() == 0
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return v.Uint() == 0
|
|
case reflect.Float32, reflect.Float64:
|
|
return v.Float() == 0
|
|
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
return v.IsNil()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// incrementMetric increments a value in the specified expvar.Map. The key
|
|
// should be a windows syscall.Errno or a string. Any other types will be
|
|
// reported under the "other" key.
|
|
func incrementMetric(v *expvar.Map, key interface{}) {
|
|
switch t := key.(type) {
|
|
default:
|
|
v.Add("other", 1)
|
|
case string:
|
|
v.Add(t, 1)
|
|
case syscall.Errno:
|
|
v.Add(strconv.Itoa(int(t)), 1)
|
|
}
|
|
}
|