354 lines
11 KiB
Go
354 lines
11 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.
|
||
|
|
||
|
// +build windows
|
||
|
|
||
|
package eventlog
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
|
||
|
"github.com/joeshaw/multierror"
|
||
|
"github.com/pkg/errors"
|
||
|
"golang.org/x/sys/windows"
|
||
|
|
||
|
"github.com/elastic/beats/libbeat/common"
|
||
|
"github.com/elastic/beats/libbeat/logp"
|
||
|
"github.com/elastic/beats/winlogbeat/checkpoint"
|
||
|
"github.com/elastic/beats/winlogbeat/sys"
|
||
|
win "github.com/elastic/beats/winlogbeat/sys/wineventlog"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// renderBufferSize is the size in bytes of the buffer used to render events.
|
||
|
renderBufferSize = 1 << 14
|
||
|
|
||
|
// winEventLogApiName is the name used to identify the Windows Event Log API
|
||
|
// as both an event type and an API.
|
||
|
winEventLogAPIName = "wineventlog"
|
||
|
)
|
||
|
|
||
|
var winEventLogConfigKeys = append(commonConfigKeys, "batch_read_size",
|
||
|
"ignore_older", "include_xml", "event_id", "forwarded", "level", "provider")
|
||
|
|
||
|
type winEventLogConfig struct {
|
||
|
ConfigCommon `config:",inline"`
|
||
|
BatchReadSize int `config:"batch_read_size"` // Maximum number of events that Read will return.
|
||
|
IncludeXML bool `config:"include_xml"`
|
||
|
Forwarded *bool `config:"forwarded"`
|
||
|
SimpleQuery query `config:",inline"`
|
||
|
}
|
||
|
|
||
|
// defaultWinEventLogConfig is the default configuration for new wineventlog readers.
|
||
|
var defaultWinEventLogConfig = winEventLogConfig{
|
||
|
BatchReadSize: 100,
|
||
|
}
|
||
|
|
||
|
// query contains parameters used to customize the event log data that is
|
||
|
// queried from the log.
|
||
|
type query struct {
|
||
|
IgnoreOlder time.Duration `config:"ignore_older"` // Ignore records older than this period of time.
|
||
|
EventID string `config:"event_id"` // White-list and black-list of events.
|
||
|
Level string `config:"level"` // Severity level.
|
||
|
Provider []string `config:"provider"` // Provider (source name).
|
||
|
}
|
||
|
|
||
|
// Validate validates the winEventLogConfig data and returns an error describing
|
||
|
// any problems or nil.
|
||
|
func (c *winEventLogConfig) Validate() error {
|
||
|
var errs multierror.Errors
|
||
|
if c.Name == "" {
|
||
|
errs = append(errs, fmt.Errorf("event log is missing a 'name'"))
|
||
|
}
|
||
|
|
||
|
return errs.Err()
|
||
|
}
|
||
|
|
||
|
// Validate that winEventLog implements the EventLog interface.
|
||
|
var _ EventLog = &winEventLog{}
|
||
|
|
||
|
// winEventLog implements the EventLog interface for reading from the Windows
|
||
|
// Event Log API.
|
||
|
type winEventLog struct {
|
||
|
config winEventLogConfig
|
||
|
query string
|
||
|
channelName string // Name of the channel from which to read.
|
||
|
subscription win.EvtHandle // Handle to the subscription.
|
||
|
maxRead int // Maximum number returned in one Read.
|
||
|
lastRead checkpoint.EventLogState // Record number of the last read event.
|
||
|
|
||
|
render func(event win.EvtHandle, out io.Writer) error // Function for rendering the event to XML.
|
||
|
renderBuf []byte // Buffer used for rendering event.
|
||
|
outputBuf *sys.ByteBuffer // Buffer for receiving XML
|
||
|
cache *messageFilesCache // Cached mapping of source name to event message file handles.
|
||
|
|
||
|
logPrefix string // String to prefix on log messages.
|
||
|
}
|
||
|
|
||
|
// Name returns the name of the event log (i.e. Application, Security, etc.).
|
||
|
func (l *winEventLog) Name() string {
|
||
|
return l.channelName
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) Open(state checkpoint.EventLogState) error {
|
||
|
var bookmark win.EvtHandle
|
||
|
var err error
|
||
|
if len(state.Bookmark) > 0 {
|
||
|
bookmark, err = win.CreateBookmarkFromXML(state.Bookmark)
|
||
|
} else {
|
||
|
bookmark, err = win.CreateBookmarkFromRecordID(l.channelName, state.RecordNumber)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer win.Close(bookmark)
|
||
|
|
||
|
// Using a pull subscription to receive events. See:
|
||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385771(v=vs.85).aspx#pull
|
||
|
signalEvent, err := windows.CreateEvent(nil, 0, 0, nil)
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
debugf("%s using subscription query=%s", l.logPrefix, l.query)
|
||
|
subscriptionHandle, err := win.Subscribe(
|
||
|
0, // Session - nil for localhost
|
||
|
signalEvent,
|
||
|
"", // Channel - empty b/c channel is in the query
|
||
|
l.query, // Query - nil means all events
|
||
|
bookmark, // Bookmark - for resuming from a specific event
|
||
|
win.EvtSubscribeStartAfterBookmark)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
l.subscription = subscriptionHandle
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) Read() ([]Record, error) {
|
||
|
handles, _, err := l.eventHandles(l.maxRead)
|
||
|
if err != nil || len(handles) == 0 {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer func() {
|
||
|
for _, h := range handles {
|
||
|
win.Close(h)
|
||
|
}
|
||
|
}()
|
||
|
detailf("%s EventHandles returned %d handles", l.logPrefix, len(handles))
|
||
|
|
||
|
var records []Record
|
||
|
for _, h := range handles {
|
||
|
l.outputBuf.Reset()
|
||
|
err := l.render(h, l.outputBuf)
|
||
|
if bufErr, ok := err.(sys.InsufficientBufferError); ok {
|
||
|
detailf("%s Increasing render buffer size to %d", l.logPrefix,
|
||
|
bufErr.RequiredSize)
|
||
|
l.renderBuf = make([]byte, bufErr.RequiredSize)
|
||
|
l.outputBuf.Reset()
|
||
|
err = l.render(h, l.outputBuf)
|
||
|
}
|
||
|
if err != nil && l.outputBuf.Len() == 0 {
|
||
|
logp.Err("%s Dropping event with rendering error. %v", l.logPrefix, err)
|
||
|
incrementMetric(dropReasons, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
r, err := l.buildRecordFromXML(l.outputBuf.Bytes(), err)
|
||
|
if err != nil {
|
||
|
logp.Err("%s Dropping event. %v", l.logPrefix, err)
|
||
|
incrementMetric(dropReasons, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
r.Offset = checkpoint.EventLogState{
|
||
|
Name: l.channelName,
|
||
|
RecordNumber: r.RecordID,
|
||
|
Timestamp: r.TimeCreated.SystemTime,
|
||
|
}
|
||
|
if r.Offset.Bookmark, err = l.createBookmarkFromEvent(h); err != nil {
|
||
|
logp.Warn("%s failed creating bookmark: %v", l.logPrefix, err)
|
||
|
}
|
||
|
records = append(records, r)
|
||
|
l.lastRead = r.Offset
|
||
|
}
|
||
|
|
||
|
debugf("%s Read() is returning %d records", l.logPrefix, len(records))
|
||
|
return records, nil
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) Close() error {
|
||
|
debugf("%s Closing handle", l.logPrefix)
|
||
|
return win.Close(l.subscription)
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) eventHandles(maxRead int) ([]win.EvtHandle, int, error) {
|
||
|
handles, err := win.EventHandles(l.subscription, maxRead)
|
||
|
switch err {
|
||
|
case nil:
|
||
|
if l.maxRead > maxRead {
|
||
|
debugf("%s Recovered from RPC_S_INVALID_BOUND error (errno 1734) "+
|
||
|
"by decreasing batch_read_size to %v", l.logPrefix, maxRead)
|
||
|
}
|
||
|
return handles, maxRead, nil
|
||
|
case win.ERROR_NO_MORE_ITEMS:
|
||
|
detailf("%s No more events", l.logPrefix)
|
||
|
return nil, maxRead, nil
|
||
|
case win.RPC_S_INVALID_BOUND:
|
||
|
incrementMetric(readErrors, err)
|
||
|
if err := l.Close(); err != nil {
|
||
|
return nil, 0, errors.Wrap(err, "failed to recover from RPC_S_INVALID_BOUND")
|
||
|
}
|
||
|
if err := l.Open(l.lastRead); err != nil {
|
||
|
return nil, 0, errors.Wrap(err, "failed to recover from RPC_S_INVALID_BOUND")
|
||
|
}
|
||
|
return l.eventHandles(maxRead / 2)
|
||
|
default:
|
||
|
incrementMetric(readErrors, err)
|
||
|
logp.Warn("%s EventHandles returned error %v", l.logPrefix, err)
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) buildRecordFromXML(x []byte, recoveredErr error) (Record, error) {
|
||
|
e, err := sys.UnmarshalEventXML(x)
|
||
|
if err != nil {
|
||
|
return Record{}, fmt.Errorf("Failed to unmarshal XML='%s'. %v", x, err)
|
||
|
}
|
||
|
|
||
|
err = sys.PopulateAccount(&e.User)
|
||
|
if err != nil {
|
||
|
debugf("%s SID %s account lookup failed. %v", l.logPrefix,
|
||
|
e.User.Identifier, err)
|
||
|
}
|
||
|
|
||
|
if e.RenderErrorCode != 0 {
|
||
|
// Convert the render error code to an error message that can be
|
||
|
// included in the "message_error" field.
|
||
|
e.RenderErr = syscall.Errno(e.RenderErrorCode).Error()
|
||
|
} else if recoveredErr != nil {
|
||
|
e.RenderErr = recoveredErr.Error()
|
||
|
}
|
||
|
|
||
|
if e.Level == "" {
|
||
|
// Fallback on LevelRaw if the Level is not set in the RenderingInfo.
|
||
|
e.Level = win.EventLevel(e.LevelRaw).String()
|
||
|
}
|
||
|
|
||
|
if logp.IsDebug(detailSelector) {
|
||
|
detailf("%s XML=%s Event=%+v", l.logPrefix, string(x), e)
|
||
|
}
|
||
|
|
||
|
r := Record{
|
||
|
API: winEventLogAPIName,
|
||
|
Event: e,
|
||
|
}
|
||
|
|
||
|
if l.config.IncludeXML {
|
||
|
r.XML = string(x)
|
||
|
}
|
||
|
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
// newWinEventLog creates and returns a new EventLog for reading event logs
|
||
|
// using the Windows Event Log.
|
||
|
func newWinEventLog(options *common.Config) (EventLog, error) {
|
||
|
c := defaultWinEventLogConfig
|
||
|
if err := readConfig(options, &c, winEventLogConfigKeys); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
query, err := win.Query{
|
||
|
Log: c.Name,
|
||
|
IgnoreOlder: c.SimpleQuery.IgnoreOlder,
|
||
|
Level: c.SimpleQuery.Level,
|
||
|
EventID: c.SimpleQuery.EventID,
|
||
|
Provider: c.SimpleQuery.Provider,
|
||
|
}.Build()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
eventMetadataHandle := func(providerName, sourceName string) sys.MessageFiles {
|
||
|
mf := sys.MessageFiles{SourceName: sourceName}
|
||
|
h, err := win.OpenPublisherMetadata(0, sourceName, 0)
|
||
|
if err != nil {
|
||
|
mf.Err = err
|
||
|
return mf
|
||
|
}
|
||
|
|
||
|
mf.Handles = []sys.FileHandle{{Handle: uintptr(h)}}
|
||
|
return mf
|
||
|
}
|
||
|
|
||
|
freeHandle := func(handle uintptr) error {
|
||
|
return win.Close(win.EvtHandle(handle))
|
||
|
}
|
||
|
|
||
|
l := &winEventLog{
|
||
|
config: c,
|
||
|
query: query,
|
||
|
channelName: c.Name,
|
||
|
maxRead: c.BatchReadSize,
|
||
|
renderBuf: make([]byte, renderBufferSize),
|
||
|
outputBuf: sys.NewByteBuffer(renderBufferSize),
|
||
|
cache: newMessageFilesCache(c.Name, eventMetadataHandle, freeHandle),
|
||
|
logPrefix: fmt.Sprintf("WinEventLog[%s]", c.Name),
|
||
|
}
|
||
|
|
||
|
// Forwarded events should be rendered using RenderEventXML. It is more
|
||
|
// efficient and does not attempt to use local message files for rendering
|
||
|
// the event's message.
|
||
|
switch {
|
||
|
case c.Forwarded == nil && c.Name == "ForwardedEvents",
|
||
|
c.Forwarded != nil && *c.Forwarded == true:
|
||
|
l.render = func(event win.EvtHandle, out io.Writer) error {
|
||
|
return win.RenderEventXML(event, l.renderBuf, out)
|
||
|
}
|
||
|
default:
|
||
|
l.render = func(event win.EvtHandle, out io.Writer) error {
|
||
|
return win.RenderEvent(event, 0, l.renderBuf, l.cache.get, out)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return l, nil
|
||
|
}
|
||
|
|
||
|
func (l *winEventLog) createBookmarkFromEvent(evtHandle win.EvtHandle) (string, error) {
|
||
|
bmHandle, err := win.CreateBookmarkFromEvent(evtHandle)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
l.outputBuf.Reset()
|
||
|
err = win.RenderBookmarkXML(bmHandle, l.renderBuf, l.outputBuf)
|
||
|
win.Close(bmHandle)
|
||
|
return string(l.outputBuf.Bytes()), err
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
// Register wineventlog API if it is available.
|
||
|
available, _ := win.IsAvailable()
|
||
|
if available {
|
||
|
Register(winEventLogAPIName, 0, newWinEventLog, win.Channels)
|
||
|
}
|
||
|
}
|