Skip to content

Commit fe93fdb

Browse files
duhminickjefchien
andauthored
Restore and send gaps for Windows events (#1747)
Co-authored-by: Jeffrey Chien <[email protected]>
1 parent df626d1 commit fe93fdb

File tree

5 files changed

+1137
-38
lines changed

5 files changed

+1137
-38
lines changed

plugins/inputs/windows_event_log/wineventlog/utils.go

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@ import (
1515
"strings"
1616
"syscall"
1717
"time"
18+
"unsafe"
1819

1920
"golang.org/x/text/encoding/unicode"
2021
"golang.org/x/text/transform"
22+
23+
"github.com/aws/amazon-cloudwatch-agent/internal/state"
2124
)
2225

2326
const (
2427
bookmarkTemplate = `<BookmarkList><Bookmark Channel="%s" RecordId="%d" IsCurrent="True"/></BookmarkList>`
25-
eventLogQueryTemplate = `<QueryList><Query Id="0"><Select Path="%s">%s</Select></Query></QueryList>`
28+
eventLogQueryTemplate = `<QueryList><Query Id="0"><Select Path="%s">*[System[%s]]</Select></Query></QueryList>`
2629
eventLogLevelFilter = "Level='%s'"
2730
eventIgnoreOldFilter = "TimeCreated[timediff(@SystemTime) &lt;= %d]"
31+
eventRangeFilter = "EventRecordID &gt; %d and EventRecordID &lt;= %d"
2832
emptySpaceScanLength = 100
2933
UnknownBytesPerCharacter = 0
3034

@@ -38,10 +42,10 @@ const (
3842

3943
var NumberOfBytesPerCharacter = UnknownBytesPerCharacter
4044

41-
func RenderEventXML(eventHandle EvtHandle, renderBuf []byte) ([]byte, error) {
45+
func RenderEventXML(w WindowsEventAPI, eventHandle EvtHandle, renderBuf []byte) ([]byte, error) {
4246
var bufferUsed, propertyCount uint32
4347

44-
if err := EvtRender(0, eventHandle, EvtRenderEventXml, uint32(len(renderBuf)), &renderBuf[0], &bufferUsed, &propertyCount); err != nil {
48+
if err := w.EvtRender(0, eventHandle, EvtRenderEventXml, uint32(len(renderBuf)), &renderBuf[0], &bufferUsed, &propertyCount); err != nil {
4549
return nil, fmt.Errorf("error when rendering events. Details: %v", err)
4650
}
4751

@@ -50,39 +54,54 @@ func RenderEventXML(eventHandle EvtHandle, renderBuf []byte) ([]byte, error) {
5054
return utf16ToUTF8Bytes(renderBuf, bufferUsed)
5155
}
5256

53-
func CreateBookmark(channel string, recordID uint64) (h EvtHandle, err error) {
57+
func CreateBookmark(w WindowsEventAPI, channel string, recordID uint64) (h EvtHandle, err error) {
5458
xml := fmt.Sprintf(bookmarkTemplate, channel, recordID)
5559
p, err := syscall.UTF16PtrFromString(xml)
5660
if err != nil {
5761
return 0, err
5862
}
59-
h, err = EvtCreateBookmark(p)
63+
h, err = w.EvtCreateBookmark(p)
6064
if err != nil {
6165
return 0, fmt.Errorf("error when creating a bookmark. Details: %v", err)
6266
}
6367
return h, nil
6468
}
6569

6670
func CreateQuery(path string, levels []string) (*uint16, error) {
67-
var filterLevels string
68-
for _, level := range levels {
69-
if filterLevels == "" {
70-
filterLevels = fmt.Sprintf(eventLogLevelFilter, level)
71-
} else {
72-
filterLevels = filterLevels + " or " + fmt.Sprintf(eventLogLevelFilter, level)
73-
}
71+
return createWindowsEventFilter(path, levels)
72+
}
73+
74+
func CreateRangeQuery(path string, levels []string, r state.Range) (*uint16, error) {
75+
rangeFilter := fmt.Sprintf(eventRangeFilter, r.StartOffset(), r.EndOffset())
76+
return createWindowsEventFilter(path, levels, rangeFilter)
77+
}
78+
79+
func createWindowsEventFilter(path string, levels []string, additionalFilters ...string) (*uint16, error) {
80+
// Add log levels
81+
var levelsFilter string
82+
formattedLevels := make([]string, len(levels))
83+
for i, level := range levels {
84+
formattedLevels[i] = fmt.Sprintf(eventLogLevelFilter, level)
85+
}
86+
if len(formattedLevels) > 0 {
87+
levelsFilter = fmt.Sprintf("(%s)", strings.Join(formattedLevels, " or "))
7488
}
7589

76-
//Ignore events older than 2 weeks
90+
// Ignore events older than 2 weeks
7791
cutOffPeriod := (time.Hour * 24 * 14).Nanoseconds()
7892
ignoreOlderThanTwoWeeksFilter := fmt.Sprintf(eventIgnoreOldFilter, cutOffPeriod/int64(time.Millisecond))
79-
if filterLevels != "" {
80-
filterLevels = "*[System[(" + filterLevels + ") and " + ignoreOlderThanTwoWeeksFilter + "]]"
81-
} else {
82-
filterLevels = "*[System[" + ignoreOlderThanTwoWeeksFilter + "]]"
93+
94+
var filters []string
95+
if levelsFilter != "" {
96+
filters = append(filters, levelsFilter)
97+
}
98+
filters = append(filters, ignoreOlderThanTwoWeeksFilter)
99+
// Add any additional filters (e.g. record IDs)
100+
if len(additionalFilters) > 0 {
101+
filters = append(filters, strings.Join(additionalFilters, " and "))
83102
}
84103

85-
xml := fmt.Sprintf(eventLogQueryTemplate, path, filterLevels)
104+
xml := fmt.Sprintf(eventLogQueryTemplate, path, strings.Join(filters, " and "))
86105
return syscall.UTF16PtrFromString(xml)
87106
}
88107

@@ -219,3 +238,17 @@ func insertPlaceholderValues(rawMessage string, evtDataValues []Datum) string {
219238
}
220239
return sb.String()
221240
}
241+
242+
func utf16PtrToString(ptr *uint16) string {
243+
utf16Slice := make([]uint16, 0, 1024)
244+
for i := 0; ; i++ {
245+
// Get the value at memory address ptr + (i * sizeof(uint16))
246+
element := *(*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(i)*unsafe.Sizeof(uint16(0))))
247+
248+
if element == 0 {
249+
break // Null terminator found
250+
}
251+
utf16Slice = append(utf16Slice, element)
252+
}
253+
return syscall.UTF16ToString(utf16Slice)
254+
}

plugins/inputs/windows_event_log/wineventlog/utils_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"testing"
1212

1313
"github.com/stretchr/testify/assert"
14+
15+
"github.com/aws/amazon-cloudwatch-agent/internal/state"
1416
)
1517

1618
func TestPayload_Value(t *testing.T) {
@@ -129,6 +131,124 @@ func TestInsertPlaceholderValues(t *testing.T) {
129131
}
130132
}
131133

134+
func TestCreateQuery(t *testing.T) {
135+
tests := []struct {
136+
name string
137+
path string
138+
levels []string
139+
expected string
140+
}{
141+
{
142+
name: "Single level filter",
143+
path: "Application",
144+
levels: []string{"2"},
145+
expected: `<QueryList><Query Id="0"><Select Path="Application">*[System[(Level='2') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>`,
146+
},
147+
{
148+
name: "Multiple level filters",
149+
path: "System",
150+
levels: []string{"2", "3", "4"},
151+
expected: `<QueryList><Query Id="0"><Select Path="System">*[System[(Level='2' or Level='3' or Level='4') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>`,
152+
},
153+
{
154+
name: "No level filters",
155+
path: "Security",
156+
levels: []string{},
157+
expected: `<QueryList><Query Id="0"><Select Path="Security">*[System[TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>`,
158+
},
159+
{
160+
name: "Empty level filters",
161+
path: "Application",
162+
levels: nil,
163+
expected: `<QueryList><Query Id="0"><Select Path="Application">*[System[TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>`,
164+
},
165+
{
166+
name: "Path with special characters",
167+
path: "Microsoft-Windows-Security-Auditing",
168+
levels: []string{"2"},
169+
expected: `<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Security-Auditing">*[System[(Level='2') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>`,
170+
},
171+
}
172+
173+
for _, tc := range tests {
174+
t.Run(tc.name, func(t *testing.T) {
175+
ptr, err := CreateQuery(tc.path, tc.levels)
176+
assert.NoError(t, err)
177+
assert.NotNil(t, ptr)
178+
assert.Equal(t, tc.expected, utf16PtrToString(ptr))
179+
})
180+
}
181+
}
182+
183+
func TestCreateRangeQuery(t *testing.T) {
184+
tests := []struct {
185+
name string
186+
path string
187+
levels []string
188+
r state.Range
189+
expected string
190+
}{
191+
{
192+
name: "Single level with range",
193+
path: "Application",
194+
levels: []string{"2"},
195+
r: state.NewRange(100, 200),
196+
expected: `<QueryList><Query Id="0"><Select Path="Application">*[System[(Level='2') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 100 and EventRecordID &lt;= 200]]</Select></Query></QueryList>`,
197+
},
198+
{
199+
name: "Multiple levels with range",
200+
path: "System",
201+
levels: []string{"2", "3"},
202+
r: state.NewRange(1000, 2000),
203+
expected: `<QueryList><Query Id="0"><Select Path="System">*[System[(Level='2' or Level='3') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 1000 and EventRecordID &lt;= 2000]]</Select></Query></QueryList>`,
204+
},
205+
{
206+
name: "No levels with range",
207+
path: "Security",
208+
levels: []string{},
209+
r: state.NewRange(50, 150),
210+
expected: `<QueryList><Query Id="0"><Select Path="Security">*[System[TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 50 and EventRecordID &lt;= 150]]</Select></Query></QueryList>`,
211+
},
212+
{
213+
name: "Empty levels with range",
214+
path: "Application",
215+
levels: nil,
216+
r: state.NewRange(0, 100),
217+
expected: `<QueryList><Query Id="0"><Select Path="Application">*[System[TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 0 and EventRecordID &lt;= 100]]</Select></Query></QueryList>`,
218+
},
219+
{
220+
name: "Large range values",
221+
path: "System",
222+
levels: []string{"2", "3", "4"},
223+
r: state.NewRange(999999, 1000000),
224+
expected: `<QueryList><Query Id="0"><Select Path="System">*[System[(Level='2' or Level='3' or Level='4') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 999999 and EventRecordID &lt;= 1000000]]</Select></Query></QueryList>`,
225+
},
226+
{
227+
name: "Zero start range",
228+
path: "Test",
229+
levels: []string{"2"},
230+
r: state.NewRange(0, 1),
231+
expected: `<QueryList><Query Id="0"><Select Path="Test">*[System[(Level='2') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 0 and EventRecordID &lt;= 1]]</Select></Query></QueryList>`,
232+
},
233+
{
234+
name: "Path with special characters and range",
235+
path: "Microsoft-Windows-Kernel-General",
236+
levels: []string{"2"},
237+
r: state.NewRange(12345, 67890),
238+
expected: `<QueryList><Query Id="0"><Select Path="Microsoft-Windows-Kernel-General">*[System[(Level='2') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000] and EventRecordID &gt; 12345 and EventRecordID &lt;= 67890]]</Select></Query></QueryList>`,
239+
},
240+
}
241+
242+
for _, tc := range tests {
243+
t.Run(tc.name, func(t *testing.T) {
244+
ptr, err := CreateRangeQuery(tc.path, tc.levels, tc.r)
245+
assert.NoError(t, err)
246+
assert.NotNil(t, ptr)
247+
assert.Equal(t, tc.expected, utf16PtrToString(ptr))
248+
})
249+
}
250+
}
251+
132252
func resetState() {
133253
NumberOfBytesPerCharacter = 0
134254
}

plugins/inputs/windows_event_log/wineventlog/sys_call.go renamed to plugins/inputs/windows_event_log/wineventlog/windows_event_api.go

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ import (
1010
"unsafe"
1111
)
1212

13+
// WindowsEventAPI defines the interface for Windows Event Log API operations
14+
// This allows us to mock the Windows API calls for testing
15+
type WindowsEventAPI interface {
16+
EvtSubscribe(session EvtHandle, signalEvent uintptr, channelPath *uint16, query *uint16, bookmark EvtHandle, context uintptr, callback syscall.Handle, flags EvtSubscribeFlag) (handle EvtHandle, err error)
17+
EvtQuery(session EvtHandle, path *uint16, query *uint16, flags EvtQueryFlag) (EvtHandle, error)
18+
EvtNext(resultSet EvtHandle, eventArraySize uint32, eventArray *EvtHandle, timeout uint32, flags uint32, numReturned *uint32) error
19+
EvtClose(handle EvtHandle) error
20+
EvtRender(context EvtHandle, fragment EvtHandle, flags EvtRenderFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32, propertyCount *uint32) error
21+
EvtCreateBookmark(bookmarkXML *uint16) (handle EvtHandle, err error)
22+
EvtFormatMessage(publisherMetadata EvtHandle, event EvtHandle, messageID uint32, valueCount uint32, values uintptr, flags EvtFormatMessageFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32) error
23+
EvtOpenPublisherMetadata(session EvtHandle, publisherId *uint16, logFilePath *uint16, locale uint32, flags uint32) (EvtHandle, error)
24+
}
25+
1326
// EvtHandle is a handle to the event log.
1427
type EvtHandle uintptr
1528

@@ -20,6 +33,14 @@ const (
2033
EvtSubscribeStartAfterBookmark EvtSubscribeFlag = 3
2134
)
2235

36+
// EvtQueryFlag defines the values that specify how to return the query results and whether you are query against a channel or log file.
37+
// https://learn.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtquery
38+
type EvtQueryFlag uint32
39+
40+
const (
41+
EvtQueryChannelPath EvtQueryFlag = 1
42+
)
43+
2344
// EvtRenderFlag defines the values that specify what to render.
2445
type EvtRenderFlag uint32
2546

@@ -45,6 +66,7 @@ var (
4566
// For Windows versions newer than 2003
4667
modwevtapi = syscall.NewLazyDLL("wevtapi.dll")
4768
procEvtSubscribe = modwevtapi.NewProc("EvtSubscribe")
69+
procEvtQuery = modwevtapi.NewProc("EvtQuery")
4870
procEvtCreateBookmark = modwevtapi.NewProc("EvtCreateBookmark")
4971
procEvtCreateRenderContext = modwevtapi.NewProc("EvtCreateRenderContext")
5072
procEvtRender = modwevtapi.NewProc("EvtRender")
@@ -54,7 +76,14 @@ var (
5476
procEvtOpenPublisherMetadata = modwevtapi.NewProc("EvtOpenPublisherMetadata")
5577
)
5678

57-
func EvtSubscribe(session EvtHandle, signalEvent uintptr, channelPath *uint16, query *uint16, bookmark EvtHandle, context uintptr, callback syscall.Handle, flags EvtSubscribeFlag) (handle EvtHandle, err error) {
79+
// windowsEventAPI implements the interface using actual Windows API calls
80+
type windowsEventAPI struct{}
81+
82+
func NewWindowsEventAPI() WindowsEventAPI {
83+
return &windowsEventAPI{}
84+
}
85+
86+
func (w *windowsEventAPI) EvtSubscribe(session EvtHandle, signalEvent uintptr, channelPath *uint16, query *uint16, bookmark EvtHandle, context uintptr, callback syscall.Handle, flags EvtSubscribeFlag) (handle EvtHandle, err error) {
5887
r0, _, e1 := syscall.Syscall9(procEvtSubscribe.Addr(), 8, uintptr(session), uintptr(signalEvent), uintptr(unsafe.Pointer(channelPath)), uintptr(unsafe.Pointer(query)), uintptr(bookmark), uintptr(context), uintptr(callback), uintptr(flags), 0)
5988
handle = EvtHandle(r0)
6089
if handle == 0 {
@@ -67,7 +96,20 @@ func EvtSubscribe(session EvtHandle, signalEvent uintptr, channelPath *uint16, q
6796
return
6897
}
6998

70-
func EvtCreateBookmark(bookmarkXML *uint16) (handle EvtHandle, err error) {
99+
func (w *windowsEventAPI) EvtQuery(session EvtHandle, path *uint16, query *uint16, flags EvtQueryFlag) (handle EvtHandle, err error) {
100+
r0, _, e1 := syscall.Syscall6(procEvtQuery.Addr(), 4, uintptr(session), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(query)), uintptr(flags), 0, 0)
101+
handle = EvtHandle(r0)
102+
if handle == 0 {
103+
if e1 != 0 {
104+
err = error(e1)
105+
} else {
106+
err = syscall.EINVAL
107+
}
108+
}
109+
return
110+
}
111+
112+
func (w *windowsEventAPI) EvtCreateBookmark(bookmarkXML *uint16) (handle EvtHandle, err error) {
71113
r0, _, e1 := syscall.Syscall(procEvtCreateBookmark.Addr(), 1, uintptr(unsafe.Pointer(bookmarkXML)), 0, 0)
72114
handle = EvtHandle(r0)
73115
if handle == 0 {
@@ -80,7 +122,7 @@ func EvtCreateBookmark(bookmarkXML *uint16) (handle EvtHandle, err error) {
80122
return
81123
}
82124

83-
func EvtCreateRenderContext(ValuePathsCount uint32, valuePaths uintptr, flags EvtRenderContextFlag) (handle EvtHandle, err error) {
125+
func (w *windowsEventAPI) EvtCreateRenderContext(ValuePathsCount uint32, valuePaths uintptr, flags EvtRenderContextFlag) (handle EvtHandle, err error) {
84126
r0, _, e1 := syscall.Syscall(procEvtCreateRenderContext.Addr(), 3, uintptr(ValuePathsCount), uintptr(valuePaths), uintptr(flags))
85127
handle = EvtHandle(r0)
86128
if handle == 0 {
@@ -93,7 +135,7 @@ func EvtCreateRenderContext(ValuePathsCount uint32, valuePaths uintptr, flags Ev
93135
return
94136
}
95137

96-
func EvtRender(context EvtHandle, fragment EvtHandle, flags EvtRenderFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32, propertyCount *uint32) (err error) {
138+
func (w *windowsEventAPI) EvtRender(context EvtHandle, fragment EvtHandle, flags EvtRenderFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32, propertyCount *uint32) (err error) {
97139
r1, _, e1 := syscall.Syscall9(procEvtRender.Addr(), 7, uintptr(context), uintptr(fragment), uintptr(flags), uintptr(bufferSize), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferUsed)), uintptr(unsafe.Pointer(propertyCount)), 0, 0)
98140
if r1 == 0 {
99141
if e1 != 0 {
@@ -105,7 +147,7 @@ func EvtRender(context EvtHandle, fragment EvtHandle, flags EvtRenderFlag, buffe
105147
return
106148
}
107149

108-
func EvtClose(object EvtHandle) (err error) {
150+
func (w *windowsEventAPI) EvtClose(object EvtHandle) (err error) {
109151
r1, _, e1 := syscall.Syscall(procEvtClose.Addr(), 1, uintptr(object), 0, 0)
110152
if r1 == 0 {
111153
if e1 != 0 {
@@ -117,7 +159,7 @@ func EvtClose(object EvtHandle) (err error) {
117159
return
118160
}
119161

120-
func EvtNext(resultSet EvtHandle, eventArraySize uint32, eventArray *EvtHandle, timeout uint32, flags uint32, numReturned *uint32) (err error) {
162+
func (w *windowsEventAPI) EvtNext(resultSet EvtHandle, eventArraySize uint32, eventArray *EvtHandle, timeout uint32, flags uint32, numReturned *uint32) (err error) {
121163
r1, _, e1 := syscall.Syscall6(procEvtNext.Addr(), 6, uintptr(resultSet), uintptr(eventArraySize), uintptr(unsafe.Pointer(eventArray)), uintptr(timeout), uintptr(flags), uintptr(unsafe.Pointer(numReturned)))
122164
if r1 == 0 {
123165
if e1 != 0 {
@@ -129,7 +171,7 @@ func EvtNext(resultSet EvtHandle, eventArraySize uint32, eventArray *EvtHandle,
129171
return
130172
}
131173

132-
func EvtFormatMessage(publisherMetadata EvtHandle, event EvtHandle, messageID uint32, valueCount uint32, values uintptr, flags EvtFormatMessageFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32) (err error) {
174+
func (w *windowsEventAPI) EvtFormatMessage(publisherMetadata EvtHandle, event EvtHandle, messageID uint32, valueCount uint32, values uintptr, flags EvtFormatMessageFlag, bufferSize uint32, buffer *byte, bufferUsed *uint32) (err error) {
133175
r1, _, e1 := syscall.Syscall9(procEvtFormatMessage.Addr(), 9, uintptr(publisherMetadata), uintptr(event), uintptr(messageID), uintptr(valueCount), uintptr(values), uintptr(flags), uintptr(bufferSize), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferUsed)))
134176
if r1 == 0 {
135177
if e1 != 0 {
@@ -141,7 +183,7 @@ func EvtFormatMessage(publisherMetadata EvtHandle, event EvtHandle, messageID ui
141183
return
142184
}
143185

144-
func EvtOpenPublisherMetadata(session EvtHandle, publisherIdentity *uint16, logFilePath *uint16, locale uint32, flags uint32) (handle EvtHandle, err error) {
186+
func (w *windowsEventAPI) EvtOpenPublisherMetadata(session EvtHandle, publisherIdentity *uint16, logFilePath *uint16, locale uint32, flags uint32) (handle EvtHandle, err error) {
145187
r0, _, e1 := syscall.Syscall6(procEvtOpenPublisherMetadata.Addr(), 5, uintptr(session), uintptr(unsafe.Pointer(publisherIdentity)), uintptr(unsafe.Pointer(logFilePath)), uintptr(locale), uintptr(flags), 0)
146188
handle = EvtHandle(r0)
147189
if handle == 0 {

0 commit comments

Comments
 (0)