pkg/output/winlog/winlog_windows.go (120 lines of code) (raw):
// Package winlog implements the output of logs windows event log.
//
// Configuration file supports either writing writing passed in messages
// as events with IDs 1-1000 for simple purposes, or to pass the values
// as event templates. Take into account that `event_create_msg_file` should
// exist and contain the appropiate messages to render the required events.
// By default it will use `%SystemRoot%\System32\EventCreate.exe` which allow
// the rendering of events IDs in the 1-1000 range without further configuration.
// The channel will be cleared and removed after running, if `persist_events` is set
// to `true` the events will remain after the execution.
// If you use it with `spigot`'s `winlog` generator set `event_create_msg_file` to
// `winlog-generator` so it uses the right file.
// To manually remove the event log you can run `Remove-EventLog -LogName "WinlogbeatTest"`
// in PowerShell.
//
// output:
// type: winlog
// event_create_msg_file: "%SystemRoot%\\system32\\adtschema.dll"
// templated: true
// persist_events: true
package winlog
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"os/exec"
"strconv"
"time"
"github.com/andrewkroh/sys/windows/svc/eventlog"
"github.com/elastic/beats/v7/winlogbeat/sys/wineventlog"
"github.com/elastic/go-ucfg"
"github.com/elastic/spigot/pkg/output"
"go.uber.org/multierr"
)
// OutputName is the name of the output in the configuration file and registry
const Name = "winlog"
type Output struct {
config
log *eventlog.Log
}
func init() {
output.Register(Name, New)
}
// New is the Factory for creating a new winlog output.
func New(cfg *ucfg.Config) (output.Output, error) {
var err error
c := defaultConfig()
if err = cfg.Unpack(&c); err != nil {
return nil, err
}
if c.EventCreateMsgFile == "winlog-generator" {
c.EventCreateMsgFile = TemplateMessageFile
}
log, err := createLog(c.Provider, c.Source, c.EventCreateMsgFile)
if err != nil {
return nil, err
}
if err := setLogSize(c.Provider, c.WinlogSizeInBytes); err != nil {
return nil, err
}
return &Output{
config: c,
log: log,
}, nil
}
func (o *Output) Write(b []byte) (n int, err error) {
if o.log == nil {
return 0, errors.New("the output is closed and unusable")
}
var etype uint16
var eid uint32
var messages []string
switch o.Templated {
case true:
tpl := &EventTemplate{}
if err := json.Unmarshal(b, tpl); err != nil {
return 0, err
}
etype = tpl.EventType
eid = tpl.EventID
messages = tpl.Messages
case false:
etype = eventlog.Info
eid = uint32(rand.Int63() % 1000)
messages = []string{string(b)}
}
return len(b), safeWriteEvent(o.log, etype, eid, messages)
}
// Close the winlog. Writes after this will fail.
func (o *Output) Close() error {
if o.PersistsEvents {
return o.log.Close()
}
err := multierr.Combine(
o.log.Close(),
wineventlog.EvtClearLog(wineventlog.NilHandle, o.Provider, ""),
eventlog.RemoveSource(o.Provider, o.Source),
eventlog.RemoveProvider(o.Provider),
)
o.log = nil
return err
}
func (o *Output) NewInterval() error {
return nil
}
// createLog creates a new event log and returns a handle for writing events
// to the log.
func createLog(name, source, messageFile string) (*eventlog.Log, error) {
existed, err := eventlog.Install(name, source, messageFile, true, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
return nil, err
}
if existed {
wineventlog.EvtClearLog(wineventlog.NilHandle, name, "")
}
log, err := eventlog.Open(source)
if err != nil {
return nil, multierr.Combine(
err,
eventlog.RemoveSource(name, source),
eventlog.RemoveProvider(name),
)
}
return log, nil
}
func safeWriteEvent(log *eventlog.Log, etype uint16, eid uint32, msgs []string) error {
deadline := time.Now().Add(time.Second * 10)
for {
err := log.Report(etype, eid, msgs)
if err == nil {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("failed to write event to event log: %w", err)
}
}
}
// setLogSize set the maximum number of bytes that an event log can hold.
func setLogSize(provider string, sizeBytes int) error {
output, err := exec.Command("wevtutil.exe", "sl", "/ms:"+strconv.Itoa(sizeBytes), provider).CombinedOutput()
if err != nil {
return fmt.Errorf("failed to set log size: %w, %s", err, string(output))
}
return nil
}