Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x-pack/libbeat/reader/etw: New reader to collect ETW logs #36914

Merged
merged 33 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
aa7cdb4
First version of ETW reader
chemamartinez Oct 19, 2023
35b164d
Minor fixes for ETW reader
chemamartinez Oct 26, 2023
5619eaf
Update Windows package and add build tags
chemamartinez Oct 27, 2023
03a4c03
Merge branch 'main' into dev-etw-reader
chemamartinez Nov 20, 2023
03b6767
More fixes and requested changes for ETW reader
chemamartinez Nov 20, 2023
75b8489
Add unit tests for ETW reader code
chemamartinez Nov 22, 2023
0e1b645
Enable Windows testing for Libbeat
chemamartinez Nov 22, 2023
e0d0384
Fix TestGUIDFromProviderName_GUIDNotFound test
chemamartinez Nov 22, 2023
20af031
Fix GUIDFromProviderName and lint errors
chemamartinez Nov 23, 2023
9a6b06c
Fix new linting errors
chemamartinez Nov 23, 2023
7d102a2
Fix latest lint errors and provider test
chemamartinez Nov 23, 2023
c6465b8
Fix TestGUIDFromProviderName_GUIDNotFound test
chemamartinez Nov 23, 2023
5ed4d9c
Fix several unit tests
chemamartinez Nov 24, 2023
88d5190
Merge branch 'main' into dev-etw-reader
rdner Nov 27, 2023
5373cdd
Merge branch 'main' into dev-etw-reader
chemamartinez Nov 29, 2023
00442f0
Fix timestamp resolution
chemamartinez Nov 29, 2023
4c98bc2
Update changelog
chemamartinez Nov 29, 2023
e3122c1
Run Windows unit tests in CI only for ETW
chemamartinez Nov 30, 2023
d25e575
Functions to convert GUID to string
chemamartinez Nov 30, 2023
1fd1311
Fix requested changes for ETW reader
chemamartinez Nov 30, 2023
202d473
Execute only Go unit tests for Libbeat
chemamartinez Dec 1, 2023
e2b9f1d
Add requested changes in ETW reader
chemamartinez Dec 21, 2023
f58b9eb
Add pointers to API wrappers to the Session struct
chemamartinez Dec 22, 2023
706ffdb
Merge branch 'main' into dev-etw-reader
chemamartinez Jan 16, 2024
378a712
Fix requested changes - part 1
chemamartinez Jan 25, 2024
fc5769d
Fix requested changes - Part 2
chemamartinez Jan 26, 2024
4234f64
Merge branch 'main' into dev-etw-reader
chemamartinez Jan 26, 2024
0c08943
Fix codeowners of libbeat readers
chemamartinez Jan 26, 2024
9dd5a3b
Fix linting and unit test errors
chemamartinez Jan 26, 2024
ee8b1e4
Fix requested changes - Part 3
chemamartinez Feb 3, 2024
e71475a
Avoid to export Session variables and rename GetHandler
chemamartinez Feb 6, 2024
6dd1e8e
Merge branch 'main' into dev-etw-reader
chemamartinez Feb 6, 2024
873f4d6
Merge branch 'main' into dev-etw-reader
chemamartinez Feb 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ CHANGELOG*
/x-pack/filebeat/modules.d/zoom.yml.disabled @elastic/security-service-integrations
/x-pack/filebeat/processors/decode_cef/ @elastic/sec-deployment-and-devices
/x-pack/heartbeat/ @elastic/obs-ds-hosted-services
/x-pack/libbeat/reader/parquet/ @elastic/security-service-integrations
/x-pack/libbeat/reader/etw/ @elastic/sec-windows-platform
/x-pack/metricbeat/ @elastic/elastic-agent-data-plane
/x-pack/metricbeat/docs/ # Listed without an owner to avoid maintaining doc ownership for each input and module.
/x-pack/metricbeat/module/activemq @elastic/obs-infraobs-integrations
Expand Down Expand Up @@ -219,4 +221,3 @@ CHANGELOG*
/x-pack/osquerybeat/ @elastic/sec-deployment-and-devices
/x-pack/packetbeat/ @elastic/sec-linux-platform
/x-pack/winlogbeat/ @elastic/sec-windows-platform
/x-pack/libbeat/reader/parquet/ @elastic/security-service-integrations
2 changes: 2 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ Setting environmental variable ELASTIC_NETINFO:false in Elastic Agent pod will d

*Libbeat*

- Added support for ETW reader. {pull}36914[36914]

*Heartbeat*
- Added status to monitor run log report.
- Upgrade github.com/elastic/go-elasticsearch/v8 to v8.12.0. {pull}37673[37673]
Expand Down
37 changes: 37 additions & 0 deletions x-pack/libbeat/Jenkinsfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,43 @@ stages:
branches: true ## for all the branches
tags: true ## for all the tags
stage: extended
## For now Windows CI tests for Libbeat are only enabled for ETW
## It only contains Go tests
windows-2022:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-2022"
stage: mandatory
windows-2019:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-2019"
stage: extended_win
windows-2016:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-2016"
stage: mandatory
windows-2012:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-2012-r2"
stage: extended_win
windows-11:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-11"
stage: extended_win
windows-10:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-10"
stage: extended_win
windows-8:
mage: "mage -w reader/etw build goUnitTest"
platforms: ## override default labels in this specific stage.
- "windows-8"
stage: extended_win
unitTest:
mage: "mage build unitTest"
stage: mandatory
Expand Down
16 changes: 16 additions & 0 deletions x-pack/libbeat/reader/etw/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package etw

type Config struct {
Logfile string // Path to the logfile
ProviderGUID string // GUID of the ETW provider
ProviderName string // Name of the ETW provider
SessionName string // Name for new ETW session
TraceLevel string // Level of tracing (e.g., "verbose")
MatchAnyKeyword uint64 // Filter for any matching keywords (bitmask)
MatchAllKeyword uint64 // Filter for all matching keywords (bitmask)
Session string // Existing session to attach
}
120 changes: 120 additions & 0 deletions x-pack/libbeat/reader/etw/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

//go:build windows

package etw

import (
"errors"
"fmt"
"syscall"
)

// GetHandler queries the status of an existing ETW session to get its handler and properties.
func (s *Session) GetHandler() error {
chemamartinez marked this conversation as resolved.
Show resolved Hide resolved
// Convert the session name to UTF16 for Windows API compatibility.
sessionNamePtr, err := syscall.UTF16PtrFromString(s.Name)
if err != nil {
return fmt.Errorf("failed to convert session name: %w", err)
}

// Query the current state of the ETW session.
err = s.controlTrace(0, sessionNamePtr, s.Properties, EVENT_TRACE_CONTROL_QUERY)
switch {
case err == nil:
// Get the session handler from the properties struct.
s.Handler = uintptr(s.Properties.Wnode.Union1)

return nil

// Handle specific errors related to the query operation.
case errors.Is(err, ERROR_BAD_LENGTH):
return fmt.Errorf("bad length when querying handler: %w", err)
case errors.Is(err, ERROR_INVALID_PARAMETER):
return fmt.Errorf("invalid parameters when querying handler: %w", err)
case errors.Is(err, ERROR_WMI_INSTANCE_NOT_FOUND):
return fmt.Errorf("session is not running: %w", err)
default:
return fmt.Errorf("failed to get handler: %w", err)
}
}

// CreateRealtimeSession initializes and starts a new real-time ETW session.
func (s *Session) CreateRealtimeSession() error {
// Convert the session name to UTF16 format for Windows API compatibility.
sessionPtr, err := syscall.UTF16PtrFromString(s.Name)
if err != nil {
return fmt.Errorf("failed to convert session name: %w", err)
}

// Start the ETW trace session.
chemamartinez marked this conversation as resolved.
Show resolved Hide resolved
err = s.startTrace(&s.Handler, sessionPtr, s.Properties)
switch {
case err == nil:

// Handle specific errors related to starting the trace session.
case errors.Is(err, ERROR_ALREADY_EXISTS):
return fmt.Errorf("session already exists: %w", err)
case errors.Is(err, ERROR_INVALID_PARAMETER):
return fmt.Errorf("invalid parameters when starting session trace: %w", err)
default:
return fmt.Errorf("failed to start trace: %w", err)
}

// Set additional parameters for trace enabling.
// See https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-enable_trace_parameters#members
params := EnableTraceParameters{
Version: 2, // ENABLE_TRACE_PARAMETERS_VERSION_2
}

// Zero timeout means asynchronous enablement
const timeout = 0

// Enable the trace session with extended options.
err = s.enableTrace(s.Handler, &s.GUID, EVENT_CONTROL_CODE_ENABLE_PROVIDER, s.TraceLevel, s.MatchAnyKeyword, s.MatchAllKeyword, timeout, &params)
switch {
case err == nil:
return nil
// Handle specific errors related to enabling the trace session.
case errors.Is(err, ERROR_INVALID_PARAMETER):
return fmt.Errorf("invalid parameters when enabling session trace: %w", err)
case errors.Is(err, ERROR_TIMEOUT):
return fmt.Errorf("timeout value expired before the enable callback completed: %w", err)
case errors.Is(err, ERROR_NO_SYSTEM_RESOURCES):
return fmt.Errorf("exceeded the number of trace sessions that can enable the provider: %w", err)
default:
return fmt.Errorf("failed to enable trace: %w", err)
}
}

// StopSession closes the ETW session and associated handles if they were created.
func (s *Session) StopSession() error {
if !s.Realtime {
return nil
}

if isValidHandler(s.TraceHandler) {
// Attempt to close the trace and handle potential errors.
if err := s.closeTrace(s.TraceHandler); err != nil && !errors.Is(err, ERROR_CTX_CLOSE_PENDING) {
return fmt.Errorf("failed to close trace: %w", err)
}
}

if s.NewSession {
// If we created the session, send a control command to stop it.
return s.controlTrace(
s.Handler,
nil,
s.Properties,
EVENT_TRACE_CONTROL_STOP,
)
}

return nil
}

func isValidHandler(handler uint64) bool {
return handler != 0 && handler != INVALID_PROCESSTRACE_HANDLE
}
190 changes: 190 additions & 0 deletions x-pack/libbeat/reader/etw/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

//go:build windows

package etw

import (
"testing"

"github.com/stretchr/testify/assert"
"golang.org/x/sys/windows"
)

func TestGetHandler_Error(t *testing.T) {
// Mock implementation of controlTrace
controlTrace := func(traceHandle uintptr,
instanceName *uint16,
properties *EventTraceProperties,
controlCode uint32) error {
return ERROR_WMI_INSTANCE_NOT_FOUND
}

// Create a Session instance
session := &Session{
Name: "TestSession",
Properties: &EventTraceProperties{},
controlTrace: controlTrace,
}

err := session.GetHandler()
assert.EqualError(t, err, "session is not running: The instance name passed was not recognized as valid by a WMI data provider.")
}

func TestGetHandler_Success(t *testing.T) {
// Mock implementation of controlTrace
controlTrace := func(traceHandle uintptr,
instanceName *uint16,
properties *EventTraceProperties,
controlCode uint32) error {
// Set a mock handler value
properties.Wnode.Union1 = 12345
return nil
}

// Create a Session instance with initialized Properties
session := &Session{
Name: "TestSession",
Properties: &EventTraceProperties{},
controlTrace: controlTrace,
}

err := session.GetHandler()

assert.NoError(t, err)
assert.Equal(t, uintptr(12345), session.Handler, "Handler should be set to the mock value")
}

func TestCreateRealtimeSession_StartTraceError(t *testing.T) {
// Mock implementation of startTrace
startTrace := func(traceHandle *uintptr,
instanceName *uint16,
properties *EventTraceProperties) error {
return ERROR_ALREADY_EXISTS
}

// Create a Session instance
session := &Session{
Name: "TestSession",
Properties: &EventTraceProperties{},
startTrace: startTrace,
}

err := session.CreateRealtimeSession()
assert.EqualError(t, err, "session already exists: Cannot create a file when that file already exists.")
}

func TestCreateRealtimeSession_EnableTraceError(t *testing.T) {
// Mock implementations
startTrace := func(traceHandle *uintptr,
instanceName *uint16,
properties *EventTraceProperties) error {
*traceHandle = 12345 // Mock handler value
return nil
}

enableTrace := func(traceHandle uintptr,
providerId *windows.GUID,
isEnabled uint32,
level uint8,
matchAnyKeyword uint64,
matchAllKeyword uint64,
enableProperty uint32,
enableParameters *EnableTraceParameters) error {
return ERROR_INVALID_PARAMETER
}

// Create a Session instance
session := &Session{
Name: "TestSession",
Properties: &EventTraceProperties{},
startTrace: startTrace,
enableTrace: enableTrace,
}

err := session.CreateRealtimeSession()
assert.EqualError(t, err, "invalid parameters when enabling session trace: The parameter is incorrect.")
}

func TestCreateRealtimeSession_Success(t *testing.T) {
// Mock implementations
startTrace := func(traceHandle *uintptr,
instanceName *uint16,
properties *EventTraceProperties) error {
*traceHandle = 12345 // Mock handler value
return nil
}

enableTrace := func(traceHandle uintptr,
providerId *windows.GUID,
isEnabled uint32,
level uint8,
matchAnyKeyword uint64,
matchAllKeyword uint64,
enableProperty uint32,
enableParameters *EnableTraceParameters) error {
return nil
}

// Create a Session instance
session := &Session{
Name: "TestSession",
Properties: &EventTraceProperties{},
startTrace: startTrace,
enableTrace: enableTrace,
}

err := session.CreateRealtimeSession()

assert.NoError(t, err)
assert.Equal(t, uintptr(12345), session.Handler, "Handler should be set to the mock value")
}

func TestStopSession_Error(t *testing.T) {
// Mock implementation of closeTrace
closeTrace := func(traceHandle uint64) error {
return ERROR_INVALID_PARAMETER
}

// Create a Session instance
session := &Session{
Realtime: true,
NewSession: true,
TraceHandler: 12345, // Example handler value
Properties: &EventTraceProperties{},
closeTrace: closeTrace,
}

err := session.StopSession()
assert.EqualError(t, err, "failed to close trace: The parameter is incorrect.")
}

func TestStopSession_Success(t *testing.T) {
// Mock implementations
closeTrace := func(traceHandle uint64) error {
return nil
}

controlTrace := func(traceHandle uintptr,
instanceName *uint16,
properties *EventTraceProperties,
controlCode uint32) error {
// Set a mock handler value
return nil
}

// Create a Session instance
session := &Session{
Realtime: true,
NewSession: true,
TraceHandler: 12345, // Example handler value
Properties: &EventTraceProperties{},
closeTrace: closeTrace,
controlTrace: controlTrace,
}

err := session.StopSession()
assert.NoError(t, err)
}
Loading
Loading