Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
300
plugins/inputs/win_eventlog/README.md
Normal file
300
plugins/inputs/win_eventlog/README.md
Normal file
|
@ -0,0 +1,300 @@
|
|||
# Windows Eventlog Input Plugin
|
||||
|
||||
Telegraf's win_eventlog input plugin gathers metrics from the windows event log.
|
||||
|
||||
## Collect Windows Event Log messages
|
||||
|
||||
Supports Windows Vista and higher.
|
||||
|
||||
Telegraf should have Administrator permissions to subscribe for some of the
|
||||
Windows Events Channels, like System Log.
|
||||
|
||||
Telegraf minimum version: Telegraf 1.16.0
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Input plugin to collect Windows Event Log messages
|
||||
# This plugin ONLY supports Windows
|
||||
[[inputs.win_eventlog]]
|
||||
## Telegraf should have Administrator permissions to subscribe for some
|
||||
## Windows Events channels (e.g. System log)
|
||||
|
||||
## LCID (Locale ID) for event rendering
|
||||
## 1033 to force English language
|
||||
## 0 to use default Windows locale
|
||||
# locale = 0
|
||||
|
||||
## Name of eventlog, used only if xpath_query is empty
|
||||
## Example: "Application"
|
||||
# eventlog_name = ""
|
||||
|
||||
## xpath_query can be in defined short form like "Event/System[EventID=999]"
|
||||
## or you can form a XML Query. Refer to the Consuming Events article:
|
||||
## https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events
|
||||
## XML query is the recommended form, because it is most flexible
|
||||
## You can create or debug XML Query by creating Custom View in Windows Event Viewer
|
||||
## and then copying resulting XML here
|
||||
xpath_query = '''
|
||||
<QueryList>
|
||||
<Query Id="0" Path="Security">
|
||||
<Select Path="Security">*</Select>
|
||||
<Suppress Path="Security">*[System[( (EventID >= 5152 and EventID <= 5158) or EventID=5379 or EventID=4672)]]</Suppress>
|
||||
</Query>
|
||||
<Query Id="1" Path="Application">
|
||||
<Select Path="Application">*[System[(Level < 4)]]</Select>
|
||||
</Query>
|
||||
<Query Id="2" Path="Windows PowerShell">
|
||||
<Select Path="Windows PowerShell">*[System[(Level < 4)]]</Select>
|
||||
</Query>
|
||||
<Query Id="3" Path="System">
|
||||
<Select Path="System">*</Select>
|
||||
</Query>
|
||||
<Query Id="4" Path="Setup">
|
||||
<Select Path="Setup">*</Select>
|
||||
</Query>
|
||||
</QueryList>
|
||||
'''
|
||||
|
||||
## When true, event logs are read from the beginning; otherwise only future
|
||||
## events will be logged.
|
||||
# from_beginning = false
|
||||
|
||||
## Number of events to fetch in one batch
|
||||
# event_batch_size = 5
|
||||
|
||||
# Process UserData XML to fields, if this node exists in Event XML
|
||||
# process_userdata = true
|
||||
|
||||
# Process EventData XML to fields, if this node exists in Event XML
|
||||
# process_eventdata = true
|
||||
|
||||
## Separator character to use for unrolled XML Data field names
|
||||
# separator = "_"
|
||||
|
||||
## Get only first line of Message field. For most events first line is
|
||||
## usually more than enough
|
||||
# only_first_line_of_message = true
|
||||
|
||||
## Parse timestamp from TimeCreated.SystemTime event field.
|
||||
## Will default to current time of telegraf processing on parsing error or if
|
||||
## set to false
|
||||
# timestamp_from_event = true
|
||||
|
||||
## System field names:
|
||||
## "Source", "EventID", "Version", "Level", "Task", "Opcode", "Keywords",
|
||||
## "TimeCreated", "EventRecordID", "ActivityID", "RelatedActivityID",
|
||||
## "ProcessID", "ThreadID", "ProcessName", "Channel", "Computer", "UserID",
|
||||
## "UserName", "Message", "LevelText", "TaskText", "OpcodeText"
|
||||
##
|
||||
## In addition to System, Data fields can be unrolled from additional XML
|
||||
## nodes in event. Human-readable representation of those nodes is formatted
|
||||
## into event Message field, but XML is more machine-parsable
|
||||
|
||||
## Event fields to include as tags
|
||||
## The values below are included by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# event_tags = ["Source", "EventID", "Level", "LevelText", "Task", "TaskText", "Opcode", "OpcodeText", "Keywords", "Channel", "Computer"]
|
||||
|
||||
## Event fields to include
|
||||
## All fields are sent by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# event_fields = ["*"]
|
||||
|
||||
## Event fields to exclude
|
||||
## Note that if you exclude all fields then no metrics are produced. A valid
|
||||
## metric includes at least one field.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# exclude_fields = []
|
||||
|
||||
## Event fields to exclude if their value is empty or equals to zero
|
||||
## The values below are included by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# exclude_empty = ["Task", "Opcode", "*ActivityID", "UserID"]
|
||||
|
||||
## Maximum memory size available for an event to render
|
||||
## Events larger that that are not processed and will not create a metric.
|
||||
## NOTE: As events are encoded in UTF-16 we need two bytes per character.
|
||||
# event_size_limit = "64KB"
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
There are three types of filtering: **Event Log** name, **XPath Query** and
|
||||
**XML Query**.
|
||||
|
||||
**Event Log** name filtering is simple:
|
||||
|
||||
```toml
|
||||
eventlog_name = "Application"
|
||||
xpath_query = '''
|
||||
```
|
||||
|
||||
For **XPath Query** filtering set the `xpath_query` value, and `eventlog_name`
|
||||
will be ignored:
|
||||
|
||||
```toml
|
||||
eventlog_name = ""
|
||||
xpath_query = "Event/System[EventID=999]"
|
||||
```
|
||||
|
||||
**XML Query** is the most flexible: you can Select or Suppress any values, and
|
||||
give ranges for other values. XML query is the recommended form, because it is
|
||||
most flexible. You can create or debug XML Query by creating Custom View in
|
||||
Windows Event Viewer and then copying resulting XML in config file.
|
||||
|
||||
XML Query documentation:
|
||||
|
||||
<https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
In case you see a `Collection took longer than expected` warning, there might
|
||||
be a burst of events logged and the API is not able to deliver them fast enough
|
||||
to complete processing within the specified interval. Tweaking the
|
||||
`event_batch_size` setting might help to mitigate the issue.
|
||||
The said warning does not indicate data-loss, but you should investigate the
|
||||
amount of events you log.
|
||||
|
||||
## Metrics
|
||||
|
||||
You can send any field, *System*, *Computed* or *XML* as tag field. List of
|
||||
those fields is in the `event_tags` config array. Globbing is supported in this
|
||||
array, i.e. `Level*` for all fields beginning with `Level`, or `L?vel` for all
|
||||
fields where the name is `Level`, `L3vel`, `L@vel` and so on. Tag fields are
|
||||
converted to strings automatically.
|
||||
|
||||
By default, all other fields are sent, but you can limit that either by listing
|
||||
it in `event_fields` config array with globbing, or by adding some field name
|
||||
masks in the `exclude_fields` config array.
|
||||
|
||||
You can limit sending fields with empty values by adding masks of names of such
|
||||
fields in the `exclude_empty` config array. Value considered empty, if the
|
||||
System field of type `int` or `uint32` is equal to zero, or if any field of type
|
||||
`string` is an empty string.
|
||||
|
||||
List of System fields:
|
||||
|
||||
- Source (string)
|
||||
- EventID (int)
|
||||
- Version (int)
|
||||
- Level (int)
|
||||
- LevelText (string)
|
||||
- Opcode (int)
|
||||
- OpcodeText (string)
|
||||
- Task (int)
|
||||
- TaskText (string)
|
||||
- Keywords (string): comma-separated in case of multiple values
|
||||
- TimeCreated (string)
|
||||
- EventRecordID (string)
|
||||
- ActivityID (string)
|
||||
- RelatedActivityID (string)
|
||||
- ProcessID (int)
|
||||
- ThreadID (int)
|
||||
- ProcessName (string): derived from ProcessID
|
||||
- Channel (string)
|
||||
- Computer (string): useful if consumed from Forwarded Events
|
||||
- UserID (string): SID
|
||||
- UserName (string): derived from UserID, presented in form of DOMAIN\Username
|
||||
- Message (string)
|
||||
|
||||
### Computed fields
|
||||
|
||||
Fields `Level`, `Opcode` and `Task` are converted to text and saved as computed
|
||||
`*Text` fields.
|
||||
|
||||
`Keywords` field is converted from hex uint64 value by the `_EvtFormatMessage`
|
||||
WINAPI function. There can be more than one value, in that case they will be
|
||||
comma-separated. If keywords can't be converted (bad device driver or forwarded
|
||||
from another computer with unknown Event Channel), hex uint64 is saved as is.
|
||||
|
||||
`ProcessName` field is found by looking up ProcessID. Can be empty if telegraf
|
||||
doesn't have enough permissions.
|
||||
|
||||
`Username` field is found by looking up SID from UserID.
|
||||
|
||||
`Message` field is rendered from the event data, and can be several kilobytes of
|
||||
text with line breaks. For most events the first line of this text is more then
|
||||
enough, and additional info is more useful to be parsed as XML fields. So, for
|
||||
brevity, plugin takes only the first line. You can set
|
||||
`only_first_line_of_message` parameter to `false` to take full message text.
|
||||
|
||||
`TimeCreated` field is a string in RFC3339Nano format. By default Telegraf
|
||||
parses it as an event timestamp. If there is a field parse error or
|
||||
`timestamp_from_event` configuration parameter is set to `false`, then event
|
||||
timestamp will be set to the exact time when Telegraf has parsed this event, so
|
||||
it will be rounded to the nearest minute.
|
||||
|
||||
### Additional Fields
|
||||
|
||||
The content of **Event Data** and **User Data** XML Nodes can be added as
|
||||
additional fields, and is added by default. You can disable that by setting
|
||||
`process_userdata` or `process_eventdata` parameters to `false`.
|
||||
|
||||
For the fields from additional XML Nodes the `Name` attribute is taken as the
|
||||
name, and inner text is the value. Type of those fields is always string.
|
||||
|
||||
Name of the field is formed from XML Path by adding _ inbetween levels. For
|
||||
example, if UserData XML looks like this:
|
||||
|
||||
```xml
|
||||
<UserData>
|
||||
<CbsPackageChangeState xmlns="http://manifests.microsoft.com/win/2004/08/windows/setup_provider">
|
||||
<PackageIdentifier>KB4566782</PackageIdentifier>
|
||||
<IntendedPackageState>5112</IntendedPackageState>
|
||||
<IntendedPackageStateTextized>Installed</IntendedPackageStateTextized>
|
||||
<ErrorCode>0x0</ErrorCode>
|
||||
<Client>UpdateAgentLCU</Client>
|
||||
</CbsPackageChangeState>
|
||||
</UserData>
|
||||
```
|
||||
|
||||
It will be converted to following fields:
|
||||
|
||||
```text
|
||||
CbsPackageChangeState_PackageIdentifier = "KB4566782"
|
||||
CbsPackageChangeState_IntendedPackageState = "5112"
|
||||
CbsPackageChangeState_IntendedPackageStateTextized = "Installed"
|
||||
CbsPackageChangeState_ErrorCode = "0x0"
|
||||
CbsPackageChangeState_Client = "UpdateAgentLCU"
|
||||
```
|
||||
|
||||
If there are more than one field with the same name, all those fields are given
|
||||
suffix with number: `_1`, `_2` and so on.
|
||||
|
||||
## Localization
|
||||
|
||||
Human readable Event Description is in the Message field. But it is better to be
|
||||
skipped in favour of the Event XML values, because they are more
|
||||
machine-readable.
|
||||
|
||||
Keywords, LevelText, TaskText, OpcodeText and Message are saved with the current
|
||||
Windows locale by default. You can override this, for example, to English locale
|
||||
by setting `locale` config parameter to `1033`. Unfortunately, **Event Data**
|
||||
and **User Data** XML Nodes are in default Windows locale only.
|
||||
|
||||
Locale should be present on the computer. English locale is usually available on
|
||||
all localized versions of modern Windows. A list of all locales is available
|
||||
from Microsoft's [Open Specifications][1].
|
||||
|
||||
[1]: https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a
|
||||
|
||||
## Example Output
|
||||
|
||||
Some values are changed for anonymity.
|
||||
|
||||
```text
|
||||
win_eventlog,Channel=System,Computer=PC,EventID=105,Keywords=0x8000000000000000,Level=4,LevelText=Information,Opcode=10,OpcodeText=General,Source=WudfUsbccidDriver,Task=1,TaskText=Driver,host=PC ProcessName="WUDFHost.exe",UserName="NT AUTHORITY\\LOCAL SERVICE",Data_dwMaxCCIDMessageLength="271",Data_bPINSupport="0x0",Data_bMaxCCIDBusySlots="1",EventRecordID=1914688i,UserID="S-1-5-19",Version=0i,Data_bClassGetEnvelope="0x0",Data_wLcdLayout="0x0",Data_bClassGetResponse="0x0",TimeCreated="2020-08-21T08:43:26.7481077Z",Message="The Smartcard reader reported the following class descriptor (part 2)." 1597999410000000000
|
||||
win_eventlog,Channel=Security,Computer=PC,EventID=4798,Keywords=Audit\ Success,Level=0,LevelText=Information,Opcode=0,OpcodeText=Info,Source=Microsoft-Windows-Security-Auditing,Task=13824,TaskText=User\ Account\ Management,host=PC Data_TargetDomainName="PC",Data_SubjectUserName="User",Data_CallerProcessId="0x3d5c",Data_SubjectLogonId="0x46d14f8d",Version=0i,EventRecordID=223157i,Message="A user's local group membership was enumerated.",Data_TargetUserName="User",Data_TargetSid="S-1-5-21-.-.-.-1001",Data_SubjectUserSid="S-1-5-21-.-.-.-1001",Data_CallerProcessName="C:\\Windows\\explorer.exe",ActivityID="{0d4cc11d-7099-0002-4dc1-4c0d9970d601}",UserID="",Data_SubjectDomainName="PC",TimeCreated="2020-08-21T08:43:27.3036771Z",ProcessName="lsass.exe" 1597999410000000000
|
||||
win_eventlog,Channel=Microsoft-Windows-Dhcp-Client/Admin,Computer=PC,EventID=1002,Keywords=0x4000000000000001,Level=2,LevelText=Error,Opcode=76,OpcodeText=IpLeaseDenied,Source=Microsoft-Windows-Dhcp-Client,Task=3,TaskText=Address\ Configuration\ State\ Event,host=PC Version=0i,Message="The IP address lease 10.20.30.40 for the Network Card with network address 0xaabbccddeeff has been denied by the DHCP server 10.20.30.1 (The DHCP Server sent a DHCPNACK message).",UserID="S-1-5-19",Data_HWLength="6",Data_HWAddress="545595B7EA01",TimeCreated="2020-08-21T08:43:42.8265853Z",EventRecordID=34i,ProcessName="svchost.exe",UserName="NT AUTHORITY\\LOCAL SERVICE" 1597999430000000000
|
||||
win_eventlog,Channel=System,Computer=PC,EventID=10016,Keywords=Classic,Level=3,LevelText=Warning,Opcode=0,OpcodeText=Info,Source=Microsoft-Windows-DistributedCOM,Task=0,host=PC Data_param3="Активация",Data_param6="PC",Data_param8="S-1-5-21-2007059868-50816014-3139024325-1001",Version=0i,UserName="PC\\User",Data_param1="по умолчанию для компьютера",Data_param2="Локально",Data_param7="User",Data_param9="LocalHost (с использованием LRPC)",Data_param10="Microsoft.Windows.ShellExperienceHost_10.0.19041.423_neutral_neutral_cw5n1h2txyewy",ActivityID="{839cac9e-73a1-4559-a847-62f3a5e73e44}",ProcessName="svchost.exe",Message="The по умолчанию для компьютера permission settings do not grant Локально Активация permission for the COM Server application with CLSID ",Data_param5="{316CDED5-E4AE-4B15-9113-7055D84DCC97}",Data_param11="S-1-15-2-.-.-.-.-.-.-2861478708",TimeCreated="2020-08-21T08:43:45.5233759Z",EventRecordID=1914689i,UserID="S-1-5-21-.-.-.-1001",Data_param4="{C2F03A33-21F5-47FA-B4BB-156362A2F239}" 1597999430000000000
|
||||
```
|
82
plugins/inputs/win_eventlog/event.go
Normal file
82
plugins/inputs/win_eventlog/event.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
// event is the event entry representation
|
||||
// Only the most common elements are processed, human-readable data is rendered in Message
|
||||
// More info on schema, if there will be need to add more:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/wes/eventschema-elements
|
||||
type event struct {
|
||||
Source provider `xml:"System>Provider"`
|
||||
EventID int `xml:"System>EventID"`
|
||||
Version int `xml:"System>Version"`
|
||||
Level int `xml:"System>Level"`
|
||||
Task int `xml:"System>Task"`
|
||||
Opcode int `xml:"System>Opcode"`
|
||||
Keywords string `xml:"System>Keywords"`
|
||||
TimeCreated timeCreated `xml:"System>TimeCreated"`
|
||||
EventRecordID int `xml:"System>EventRecordID"`
|
||||
Correlation correlation `xml:"System>Correlation"`
|
||||
Execution execution `xml:"System>Execution"`
|
||||
Channel string `xml:"System>Channel"`
|
||||
Computer string `xml:"System>Computer"`
|
||||
Security security `xml:"System>Security"`
|
||||
UserData userData `xml:"UserData"`
|
||||
EventData eventData `xml:"EventData"`
|
||||
RenderingInfo *renderingInfo `xml:"RenderingInfo"`
|
||||
|
||||
Message string
|
||||
LevelText string
|
||||
TaskText string
|
||||
OpcodeText string
|
||||
}
|
||||
|
||||
// userData Application-provided XML data
|
||||
type userData struct {
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// eventData Application-provided XML data
|
||||
type eventData struct {
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// provider is the event provider information
|
||||
type provider struct {
|
||||
Name string `xml:"Name,attr"`
|
||||
}
|
||||
|
||||
// correlation is used for the event grouping
|
||||
type correlation struct {
|
||||
ActivityID string `xml:"ActivityID,attr"`
|
||||
RelatedActivityID string `xml:"RelatedActivityID,attr"`
|
||||
}
|
||||
|
||||
// execution Info for event
|
||||
type execution struct {
|
||||
ProcessID uint32 `xml:"ProcessID,attr"`
|
||||
ThreadID uint32 `xml:"ThreadID,attr"`
|
||||
ProcessName string
|
||||
}
|
||||
|
||||
// security Data for event
|
||||
type security struct {
|
||||
UserID string `xml:"UserID,attr"`
|
||||
}
|
||||
|
||||
// timeCreated field for event
|
||||
type timeCreated struct {
|
||||
SystemTime string `xml:"SystemTime,attr"`
|
||||
}
|
||||
|
||||
// renderingInfo is provided for events forwarded by Windows event Collector
|
||||
// see https://learn.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage#parameters
|
||||
type renderingInfo struct {
|
||||
Message string `xml:"Message"`
|
||||
Level string `xml:"Level"`
|
||||
Task string `xml:"Task"`
|
||||
Opcode string `xml:"Opcode"`
|
||||
Channel string `xml:"Channel"`
|
||||
Provider string `xml:"Provider"`
|
||||
Keywords []string `xml:"Keywords>Keyword"`
|
||||
}
|
102
plugins/inputs/win_eventlog/sample.conf
Normal file
102
plugins/inputs/win_eventlog/sample.conf
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Input plugin to collect Windows Event Log messages
|
||||
# This plugin ONLY supports Windows
|
||||
[[inputs.win_eventlog]]
|
||||
## Telegraf should have Administrator permissions to subscribe for some
|
||||
## Windows Events channels (e.g. System log)
|
||||
|
||||
## LCID (Locale ID) for event rendering
|
||||
## 1033 to force English language
|
||||
## 0 to use default Windows locale
|
||||
# locale = 0
|
||||
|
||||
## Name of eventlog, used only if xpath_query is empty
|
||||
## Example: "Application"
|
||||
# eventlog_name = ""
|
||||
|
||||
## xpath_query can be in defined short form like "Event/System[EventID=999]"
|
||||
## or you can form a XML Query. Refer to the Consuming Events article:
|
||||
## https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events
|
||||
## XML query is the recommended form, because it is most flexible
|
||||
## You can create or debug XML Query by creating Custom View in Windows Event Viewer
|
||||
## and then copying resulting XML here
|
||||
xpath_query = '''
|
||||
<QueryList>
|
||||
<Query Id="0" Path="Security">
|
||||
<Select Path="Security">*</Select>
|
||||
<Suppress Path="Security">*[System[( (EventID >= 5152 and EventID <= 5158) or EventID=5379 or EventID=4672)]]</Suppress>
|
||||
</Query>
|
||||
<Query Id="1" Path="Application">
|
||||
<Select Path="Application">*[System[(Level < 4)]]</Select>
|
||||
</Query>
|
||||
<Query Id="2" Path="Windows PowerShell">
|
||||
<Select Path="Windows PowerShell">*[System[(Level < 4)]]</Select>
|
||||
</Query>
|
||||
<Query Id="3" Path="System">
|
||||
<Select Path="System">*</Select>
|
||||
</Query>
|
||||
<Query Id="4" Path="Setup">
|
||||
<Select Path="Setup">*</Select>
|
||||
</Query>
|
||||
</QueryList>
|
||||
'''
|
||||
|
||||
## When true, event logs are read from the beginning; otherwise only future
|
||||
## events will be logged.
|
||||
# from_beginning = false
|
||||
|
||||
## Number of events to fetch in one batch
|
||||
# event_batch_size = 5
|
||||
|
||||
# Process UserData XML to fields, if this node exists in Event XML
|
||||
# process_userdata = true
|
||||
|
||||
# Process EventData XML to fields, if this node exists in Event XML
|
||||
# process_eventdata = true
|
||||
|
||||
## Separator character to use for unrolled XML Data field names
|
||||
# separator = "_"
|
||||
|
||||
## Get only first line of Message field. For most events first line is
|
||||
## usually more than enough
|
||||
# only_first_line_of_message = true
|
||||
|
||||
## Parse timestamp from TimeCreated.SystemTime event field.
|
||||
## Will default to current time of telegraf processing on parsing error or if
|
||||
## set to false
|
||||
# timestamp_from_event = true
|
||||
|
||||
## System field names:
|
||||
## "Source", "EventID", "Version", "Level", "Task", "Opcode", "Keywords",
|
||||
## "TimeCreated", "EventRecordID", "ActivityID", "RelatedActivityID",
|
||||
## "ProcessID", "ThreadID", "ProcessName", "Channel", "Computer", "UserID",
|
||||
## "UserName", "Message", "LevelText", "TaskText", "OpcodeText"
|
||||
##
|
||||
## In addition to System, Data fields can be unrolled from additional XML
|
||||
## nodes in event. Human-readable representation of those nodes is formatted
|
||||
## into event Message field, but XML is more machine-parsable
|
||||
|
||||
## Event fields to include as tags
|
||||
## The values below are included by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# event_tags = ["Source", "EventID", "Level", "LevelText", "Task", "TaskText", "Opcode", "OpcodeText", "Keywords", "Channel", "Computer"]
|
||||
|
||||
## Event fields to include
|
||||
## All fields are sent by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# event_fields = ["*"]
|
||||
|
||||
## Event fields to exclude
|
||||
## Note that if you exclude all fields then no metrics are produced. A valid
|
||||
## metric includes at least one field.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# exclude_fields = []
|
||||
|
||||
## Event fields to exclude if their value is empty or equals to zero
|
||||
## The values below are included by default.
|
||||
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
|
||||
# exclude_empty = ["Task", "Opcode", "*ActivityID", "UserID"]
|
||||
|
||||
## Maximum memory size available for an event to render
|
||||
## Events larger that that are not processed and will not create a metric.
|
||||
## NOTE: As events are encoded in UTF-16 we need two bytes per character.
|
||||
# event_size_limit = "64KB"
|
36
plugins/inputs/win_eventlog/syscall_windows.go
Normal file
36
plugins/inputs/win_eventlog/syscall_windows.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import "syscall"
|
||||
|
||||
// event log error codes.
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
|
||||
const (
|
||||
errInsufficientBuffer syscall.Errno = 122
|
||||
errNoMoreItems syscall.Errno = 259
|
||||
errInvalidOperation syscall.Errno = 4317
|
||||
)
|
||||
|
||||
// evtSubscribeFlag defines the possible values that specify when to start subscribing to events.
|
||||
type evtSubscribeFlag uint32
|
||||
|
||||
// EVT_SUBSCRIBE_FLAGS enumeration
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385588(v=vs.85).aspx
|
||||
const (
|
||||
evtSubscribeToFutureEvents evtSubscribeFlag = 1
|
||||
evtSubscribeStartAtOldestRecord evtSubscribeFlag = 2
|
||||
evtSubscribeStartAfterBookmark evtSubscribeFlag = 3
|
||||
)
|
||||
|
||||
// evtRenderFlag uint32
|
||||
type evtRenderFlag uint32
|
||||
|
||||
// EVT_RENDER_FLAGS enumeration
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385563(v=vs.85).aspx
|
||||
const (
|
||||
// Render the event as an XML string. For details on the contents of the XML string, see the event schema.
|
||||
evtRenderEventXML evtRenderFlag = 1
|
||||
// Render bookmark
|
||||
evtRenderBookmark evtRenderFlag = 2
|
||||
)
|
148
plugins/inputs/win_eventlog/util.go
Normal file
148
plugins/inputs/win_eventlog/util.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// decodeUTF16 to UTF8 bytes
|
||||
func decodeUTF16(b []byte) ([]byte, error) {
|
||||
if len(b)%2 != 0 {
|
||||
return nil, errors.New("must have even length byte slice")
|
||||
}
|
||||
|
||||
u16s := make([]uint16, 1)
|
||||
|
||||
ret := &bytes.Buffer{}
|
||||
|
||||
b8buf := make([]byte, 4)
|
||||
|
||||
lb := len(b)
|
||||
for i := 0; i < lb; i += 2 {
|
||||
u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8)
|
||||
r := utf16.Decode(u16s)
|
||||
n := utf8.EncodeRune(b8buf, r[0])
|
||||
ret.Write(b8buf[:n])
|
||||
}
|
||||
|
||||
return ret.Bytes(), nil
|
||||
}
|
||||
|
||||
// getFromSnapProcess finds information about process by the given pid
|
||||
// Returns process name
|
||||
func getFromSnapProcess(pid uint32) (string, error) {
|
||||
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, pid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer windows.CloseHandle(snap)
|
||||
var pe32 windows.ProcessEntry32
|
||||
pe32.Size = uint32(unsafe.Sizeof(pe32))
|
||||
if err := windows.Process32First(snap, &pe32); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
if pe32.ProcessID == pid {
|
||||
szexe := windows.UTF16ToString(pe32.ExeFile[:])
|
||||
return szexe, nil
|
||||
}
|
||||
if err = windows.Process32Next(snap, &pe32); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("couldn't find pid: %d", pid)
|
||||
}
|
||||
|
||||
type xmlnode struct {
|
||||
XMLName xml.Name
|
||||
Attrs []xml.Attr `xml:"-"`
|
||||
Content []byte `xml:",innerxml"`
|
||||
Text string `xml:",chardata"`
|
||||
Nodes []xmlnode `xml:",any"`
|
||||
}
|
||||
|
||||
// eventField for unique rendering
|
||||
type eventField struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// UnmarshalXML redefined for xml elements walk
|
||||
func (n *xmlnode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
n.Attrs = start.Attr
|
||||
type node xmlnode
|
||||
|
||||
return d.DecodeElement((*node)(n), &start)
|
||||
}
|
||||
|
||||
// unrollXMLFields extracts fields from xml data
|
||||
func unrollXMLFields(data []byte, fieldsUsage map[string]int, separator string) ([]eventField, map[string]int) {
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := xml.NewDecoder(buf)
|
||||
var fields []eventField
|
||||
for {
|
||||
var node xmlnode
|
||||
err := dec.Decode(&node)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var parents []string
|
||||
walkXML([]xmlnode{node}, parents, separator, func(node xmlnode, parents []string, separator string) bool {
|
||||
innerText := strings.TrimSpace(node.Text)
|
||||
if len(innerText) > 0 {
|
||||
valueName := strings.Join(parents, separator)
|
||||
fieldsUsage[valueName]++
|
||||
field := eventField{Name: valueName, Value: innerText}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return fields, fieldsUsage
|
||||
}
|
||||
|
||||
func walkXML(nodes []xmlnode, parents []string, separator string, f func(xmlnode, []string, string) bool) {
|
||||
for _, node := range nodes {
|
||||
parentName := node.XMLName.Local
|
||||
for _, attr := range node.Attrs {
|
||||
attrName := strings.ToLower(attr.Name.Local)
|
||||
if attrName == "name" {
|
||||
// Add Name attribute to parent name
|
||||
parentName = strings.Join([]string{parentName, attr.Value}, separator)
|
||||
}
|
||||
}
|
||||
nodeParents := append(parents, parentName)
|
||||
if f(node, nodeParents, separator) {
|
||||
walkXML(node.Nodes, nodeParents, separator, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uniqueFieldNames forms unique field names by adding _<num> if there are several of them
|
||||
func uniqueFieldNames(fields []eventField, fieldsUsage map[string]int, separator string) []eventField {
|
||||
var fieldsCounter = make(map[string]int, len(fields))
|
||||
fieldsUnique := make([]eventField, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
fieldName := field.Name
|
||||
if fieldsUsage[field.Name] > 1 {
|
||||
fieldsCounter[field.Name]++
|
||||
fieldName = fmt.Sprint(field.Name, separator, fieldsCounter[field.Name])
|
||||
}
|
||||
fieldsUnique = append(fieldsUnique, eventField{
|
||||
Name: fieldName,
|
||||
Value: field.Value,
|
||||
})
|
||||
}
|
||||
return fieldsUnique
|
||||
}
|
198
plugins/inputs/win_eventlog/util_test.go
Normal file
198
plugins/inputs/win_eventlog/util_test.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
func TestDecodeUTF16(t *testing.T) {
|
||||
testString := "Test String"
|
||||
utf16s := utf16.Encode([]rune(testString))
|
||||
var bytesUtf16 bytes.Buffer
|
||||
writer := io.Writer(&bytesUtf16)
|
||||
lb := len(utf16s)
|
||||
for i := 0; i < lb; i++ {
|
||||
word := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(word, utf16s[i])
|
||||
_, err := writer.Write(word)
|
||||
if err != nil {
|
||||
t.Errorf("error preparing UTF-16 test string")
|
||||
return
|
||||
}
|
||||
}
|
||||
type args struct {
|
||||
b []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Wrong UTF-16",
|
||||
args: args{b: append(bytesUtf16.Bytes(), byte('\x00'))},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "UTF-16",
|
||||
args: args{b: bytesUtf16.Bytes()},
|
||||
want: []byte(testString),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := decodeUTF16(tt.args.b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("decodeUTF16() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("decodeUTF16() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var xmlbroken = `
|
||||
<BrokenXML>
|
||||
<Data/>qq</Data>
|
||||
</BrokenXML>
|
||||
`
|
||||
|
||||
var xmldata = `
|
||||
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
|
||||
<UserData>
|
||||
<CbsPackageChangeState xmlns="http://manifests.microsoft.com/win/2004/08/windows/setup_provider">
|
||||
<IntendedPackageState>5111</IntendedPackageState>
|
||||
<ErrorCode><Code>0x0</Code></ErrorCode>
|
||||
</CbsPackageChangeState>
|
||||
</UserData>
|
||||
<EventData>
|
||||
<Data>2120-07-26T15:24:25Z</Data>
|
||||
<Data>RulesEngine</Data>
|
||||
<Data Name="Engine">RulesEngine</Data>
|
||||
</EventData>
|
||||
</Event>
|
||||
`
|
||||
|
||||
type testEvent struct {
|
||||
UserData struct {
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
} `xml:"UserData"`
|
||||
EventData struct {
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
} `xml:"EventData"`
|
||||
}
|
||||
|
||||
func TestUnrollXMLFields(t *testing.T) {
|
||||
container := testEvent{}
|
||||
err := xml.Unmarshal([]byte(xmldata), &container)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't unmarshal precooked xml string xmldata")
|
||||
return
|
||||
}
|
||||
|
||||
type args struct {
|
||||
data []byte
|
||||
fieldsUsage map[string]int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want1 []eventField
|
||||
want2 map[string]int
|
||||
}{
|
||||
{
|
||||
name: "Broken XML",
|
||||
args: args{
|
||||
data: []byte(xmlbroken),
|
||||
fieldsUsage: map[string]int{},
|
||||
},
|
||||
want1: nil,
|
||||
want2: map[string]int{},
|
||||
},
|
||||
{
|
||||
name: "EventData with non-unique names and one Name attr",
|
||||
args: args{
|
||||
data: container.EventData.InnerXML,
|
||||
fieldsUsage: map[string]int{},
|
||||
},
|
||||
want1: []eventField{
|
||||
{Name: "Data", Value: "2120-07-26T15:24:25Z"},
|
||||
{Name: "Data", Value: "RulesEngine"},
|
||||
{Name: "Data_Engine", Value: "RulesEngine"},
|
||||
},
|
||||
want2: map[string]int{"Data": 2, "Data_Engine": 1},
|
||||
},
|
||||
{
|
||||
name: "UserData with non-unique names and three levels of depth",
|
||||
args: args{
|
||||
data: container.UserData.InnerXML,
|
||||
fieldsUsage: map[string]int{},
|
||||
},
|
||||
want1: []eventField{
|
||||
{Name: "CbsPackageChangeState_IntendedPackageState", Value: "5111"},
|
||||
{Name: "CbsPackageChangeState_ErrorCode_Code", Value: "0x0"},
|
||||
},
|
||||
want2: map[string]int{
|
||||
"CbsPackageChangeState_ErrorCode_Code": 1,
|
||||
"CbsPackageChangeState_IntendedPackageState": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := unrollXMLFields(tt.args.data, tt.args.fieldsUsage, "_")
|
||||
if !reflect.DeepEqual(got, tt.want1) {
|
||||
t.Errorf("ExtractFields() got = %v, want %v", got, tt.want1)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want2) {
|
||||
t.Errorf("ExtractFields() got1 = %v, want %v", got1, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueFieldNames(t *testing.T) {
|
||||
type args struct {
|
||||
fields []eventField
|
||||
fieldsUsage map[string]int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []eventField
|
||||
}{
|
||||
{
|
||||
name: "Unique values",
|
||||
args: args{
|
||||
fields: []eventField{
|
||||
{Name: "Data", Value: "2120-07-26T15:24:25Z"},
|
||||
{Name: "Data", Value: "RulesEngine"},
|
||||
{Name: "Engine", Value: "RulesEngine"},
|
||||
},
|
||||
fieldsUsage: map[string]int{"Data": 2, "Engine": 1},
|
||||
},
|
||||
want: []eventField{
|
||||
{Name: "Data_1", Value: "2120-07-26T15:24:25Z"},
|
||||
{Name: "Data_2", Value: "RulesEngine"},
|
||||
{Name: "Engine", Value: "RulesEngine"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := uniqueFieldNames(tt.args.fields, tt.args.fieldsUsage, "_"); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("PrintFields() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
604
plugins/inputs/win_eventlog/win_eventlog.go
Normal file
604
plugins/inputs/win_eventlog/win_eventlog.go
Normal file
|
@ -0,0 +1,604 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
var errEventTooLarge = errors.New("event too large")
|
||||
|
||||
type WinEventLog struct {
|
||||
Locale uint32 `toml:"locale"`
|
||||
EventlogName string `toml:"eventlog_name"`
|
||||
Query string `toml:"xpath_query"`
|
||||
FromBeginning bool `toml:"from_beginning"`
|
||||
BatchSize uint32 `toml:"event_batch_size"`
|
||||
ProcessUserData bool `toml:"process_userdata"`
|
||||
ProcessEventData bool `toml:"process_eventdata"`
|
||||
Separator string `toml:"separator"`
|
||||
OnlyFirstLineOfMessage bool `toml:"only_first_line_of_message"`
|
||||
TimeStampFromEvent bool `toml:"timestamp_from_event"`
|
||||
EventTags []string `toml:"event_tags"`
|
||||
EventFields []string `toml:"event_fields"`
|
||||
ExcludeFields []string `toml:"exclude_fields"`
|
||||
ExcludeEmpty []string `toml:"exclude_empty"`
|
||||
EventSizeLimit config.Size `toml:"event_size_limit"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
subscription evtHandle
|
||||
subscriptionFlag evtSubscribeFlag
|
||||
bookmark evtHandle
|
||||
tagFilter filter.Filter
|
||||
fieldFilter filter.Filter
|
||||
fieldEmptyFilter filter.Filter
|
||||
}
|
||||
|
||||
func (*WinEventLog) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (w *WinEventLog) Init() error {
|
||||
// Set defaults
|
||||
if w.BatchSize < 1 {
|
||||
w.BatchSize = 5
|
||||
}
|
||||
|
||||
w.subscriptionFlag = evtSubscribeToFutureEvents
|
||||
if w.FromBeginning {
|
||||
w.subscriptionFlag = evtSubscribeStartAtOldestRecord
|
||||
}
|
||||
|
||||
if w.Query == "" {
|
||||
w.Query = "*"
|
||||
}
|
||||
|
||||
if w.EventSizeLimit == 0 {
|
||||
w.EventSizeLimit = config.Size(64 * 1024) // 64kb
|
||||
} else if w.EventSizeLimit > math.MaxUint32 {
|
||||
// Clip the size to not overflow
|
||||
w.EventSizeLimit = config.Size(math.MaxUint32)
|
||||
}
|
||||
|
||||
bookmark, err := evtCreateBookmark(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.bookmark = bookmark
|
||||
|
||||
if w.tagFilter, err = filter.Compile(w.EventTags); err != nil {
|
||||
return fmt.Errorf("creating tag filter failed: %w", err)
|
||||
}
|
||||
|
||||
if w.fieldFilter, err = filter.NewIncludeExcludeFilter(w.EventFields, w.ExcludeFields); err != nil {
|
||||
return fmt.Errorf("creating field filter failed: %w", err)
|
||||
}
|
||||
|
||||
if w.fieldEmptyFilter, err = filter.Compile(w.ExcludeEmpty); err != nil {
|
||||
return fmt.Errorf("creating empty fields filter failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) Start(telegraf.Accumulator) error {
|
||||
subscription, err := w.evtSubscribe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("subscription of Windows Event Log failed: %w", err)
|
||||
}
|
||||
w.subscription = subscription
|
||||
w.Log.Debug("Subscription handle id:", w.subscription)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) GetState() interface{} {
|
||||
bookmarkXML, err := w.renderBookmark()
|
||||
if err != nil {
|
||||
w.Log.Errorf("State-persistence failed, cannot render bookmark: %v", err)
|
||||
return ""
|
||||
}
|
||||
return bookmarkXML
|
||||
}
|
||||
|
||||
func (w *WinEventLog) SetState(state interface{}) error {
|
||||
bookmarkXML, ok := state.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid type %T for state", state)
|
||||
}
|
||||
|
||||
ptr, err := syscall.UTF16PtrFromString(bookmarkXML)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conversion to pointer failed: %w", err)
|
||||
}
|
||||
|
||||
bookmark, err := evtCreateBookmark(ptr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating bookmark failed: %w", err)
|
||||
}
|
||||
w.bookmark = bookmark
|
||||
w.subscriptionFlag = evtSubscribeStartAfterBookmark
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) Gather(acc telegraf.Accumulator) error {
|
||||
for {
|
||||
events, err := w.fetchEvents(w.subscription)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNoMoreItems) {
|
||||
break
|
||||
}
|
||||
w.Log.Errorf("Error getting events: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range events {
|
||||
// Prepare fields names usage counter
|
||||
fieldsUsage := make(map[string]int)
|
||||
|
||||
tags := make(map[string]string)
|
||||
fields := make(map[string]interface{})
|
||||
event := events[i]
|
||||
evt := reflect.ValueOf(&event).Elem()
|
||||
timeStamp := time.Now()
|
||||
// Walk through all fields of event struct to process System tags or fields
|
||||
for i := 0; i < evt.NumField(); i++ {
|
||||
fieldName := evt.Type().Field(i).Name
|
||||
fieldType := evt.Field(i).Type().String()
|
||||
fieldValue := evt.Field(i).Interface()
|
||||
computedValues := make(map[string]interface{})
|
||||
switch fieldName {
|
||||
case "Source":
|
||||
fieldValue = event.Source.Name
|
||||
fieldType = reflect.TypeOf(fieldValue).String()
|
||||
case "Execution":
|
||||
fieldValue := event.Execution.ProcessID
|
||||
fieldType = reflect.TypeOf(fieldValue).String()
|
||||
fieldName = "ProcessID"
|
||||
// Look up Process Name from pid
|
||||
if should, _ := w.shouldProcessField("ProcessName"); should {
|
||||
processName, err := getFromSnapProcess(fieldValue)
|
||||
if err == nil {
|
||||
computedValues["ProcessName"] = processName
|
||||
}
|
||||
}
|
||||
case "TimeCreated":
|
||||
fieldValue = event.TimeCreated.SystemTime
|
||||
fieldType = reflect.TypeOf(fieldValue).String()
|
||||
if w.TimeStampFromEvent {
|
||||
timeStamp, err = time.Parse(time.RFC3339Nano, fmt.Sprintf("%v", fieldValue))
|
||||
if err != nil {
|
||||
w.Log.Warnf("Error parsing timestamp %q: %v", fieldValue, err)
|
||||
}
|
||||
}
|
||||
case "Correlation":
|
||||
if should, _ := w.shouldProcessField("ActivityID"); should {
|
||||
activityID := event.Correlation.ActivityID
|
||||
if len(activityID) > 0 {
|
||||
computedValues["ActivityID"] = activityID
|
||||
}
|
||||
}
|
||||
if should, _ := w.shouldProcessField("RelatedActivityID"); should {
|
||||
relatedActivityID := event.Correlation.RelatedActivityID
|
||||
if len(relatedActivityID) > 0 {
|
||||
computedValues["RelatedActivityID"] = relatedActivityID
|
||||
}
|
||||
}
|
||||
case "Security":
|
||||
computedValues["UserID"] = event.Security.UserID
|
||||
// Look up UserName and Domain from SID
|
||||
if should, _ := w.shouldProcessField("UserName"); should {
|
||||
sid := event.Security.UserID
|
||||
usid, err := syscall.StringToSid(sid)
|
||||
if err == nil {
|
||||
username, domain, _, err := usid.LookupAccount("")
|
||||
if err == nil {
|
||||
computedValues["UserName"] = fmt.Sprint(domain, "\\", username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if should, where := w.shouldProcessField(fieldName); should {
|
||||
if where == "tags" {
|
||||
strValue := fmt.Sprintf("%v", fieldValue)
|
||||
if !w.shouldExcludeEmptyField(fieldName, "string", strValue) {
|
||||
tags[fieldName] = strValue
|
||||
fieldsUsage[fieldName]++
|
||||
}
|
||||
} else if where == "fields" {
|
||||
if !w.shouldExcludeEmptyField(fieldName, fieldType, fieldValue) {
|
||||
fields[fieldName] = fieldValue
|
||||
fieldsUsage[fieldName]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert computed fields
|
||||
for computedKey, computedValue := range computedValues {
|
||||
if should, where := w.shouldProcessField(computedKey); should {
|
||||
if where == "tags" {
|
||||
tags[computedKey] = fmt.Sprintf("%v", computedValue)
|
||||
fieldsUsage[computedKey]++
|
||||
} else if where == "fields" {
|
||||
fields[computedKey] = computedValue
|
||||
fieldsUsage[computedKey]++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unroll additional XML
|
||||
var xmlFields []eventField
|
||||
if w.ProcessUserData {
|
||||
fieldsUserData, xmlFieldsUsage := unrollXMLFields(event.UserData.InnerXML, fieldsUsage, w.Separator)
|
||||
xmlFields = append(xmlFields, fieldsUserData...)
|
||||
fieldsUsage = xmlFieldsUsage
|
||||
}
|
||||
if w.ProcessEventData {
|
||||
fieldsEventData, xmlFieldsUsage := unrollXMLFields(event.EventData.InnerXML, fieldsUsage, w.Separator)
|
||||
xmlFields = append(xmlFields, fieldsEventData...)
|
||||
fieldsUsage = xmlFieldsUsage
|
||||
}
|
||||
uniqueXMLFields := uniqueFieldNames(xmlFields, fieldsUsage, w.Separator)
|
||||
for _, xmlField := range uniqueXMLFields {
|
||||
should, where := w.shouldProcessField(xmlField.Name)
|
||||
if !should {
|
||||
continue
|
||||
}
|
||||
if where == "tags" {
|
||||
tags[xmlField.Name] = xmlField.Value
|
||||
} else {
|
||||
fields[xmlField.Name] = xmlField.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Pass collected metrics
|
||||
acc.AddFields("win_eventlog", fields, tags, timeStamp)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) Stop() {
|
||||
//nolint:errcheck // ending the subscription, error can be ignored
|
||||
_ = evtClose(w.subscription)
|
||||
}
|
||||
|
||||
func (w *WinEventLog) shouldProcessField(field string) (should bool, list string) {
|
||||
if w.tagFilter != nil && w.tagFilter.Match(field) {
|
||||
return true, "tags"
|
||||
}
|
||||
|
||||
if w.fieldFilter.Match(field) {
|
||||
return true, "fields"
|
||||
}
|
||||
|
||||
return false, "excluded"
|
||||
}
|
||||
|
||||
func (w *WinEventLog) shouldExcludeEmptyField(field, fieldType string, fieldValue interface{}) (should bool) {
|
||||
if w.fieldEmptyFilter == nil || !w.fieldEmptyFilter.Match(field) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch fieldType {
|
||||
case "string":
|
||||
return len(fieldValue.(string)) < 1
|
||||
case "int":
|
||||
return fieldValue.(int) == 0
|
||||
case "uint32":
|
||||
return fieldValue.(uint32) == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WinEventLog) evtSubscribe() (evtHandle, error) {
|
||||
sigEvent, err := windows.CreateEvent(nil, 0, 0, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer windows.CloseHandle(sigEvent)
|
||||
|
||||
logNamePtr, err := syscall.UTF16PtrFromString(w.EventlogName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
xqueryPtr, err := syscall.UTF16PtrFromString(w.Query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var bookmark evtHandle
|
||||
if w.subscriptionFlag == evtSubscribeStartAfterBookmark {
|
||||
bookmark = w.bookmark
|
||||
}
|
||||
subsHandle, err := evtSubscribe(0, uintptr(sigEvent), logNamePtr, xqueryPtr, bookmark, 0, 0, w.subscriptionFlag)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return subsHandle, nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) fetchEventHandles(subsHandle evtHandle) ([]evtHandle, error) {
|
||||
var evtReturned uint32
|
||||
|
||||
eventHandles := make([]evtHandle, w.BatchSize)
|
||||
if err := evtNext(subsHandle, w.BatchSize, &eventHandles[0], 0, 0, &evtReturned); err != nil {
|
||||
if errors.Is(err, errInvalidOperation) && evtReturned == 0 {
|
||||
return nil, errNoMoreItems
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eventHandles[:evtReturned], nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) fetchEvents(subsHandle evtHandle) ([]event, error) {
|
||||
var events []event
|
||||
|
||||
eventHandles, err := w.fetchEventHandles(subsHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var evterr error
|
||||
for _, eventHandle := range eventHandles {
|
||||
if eventHandle == 0 {
|
||||
continue
|
||||
}
|
||||
if event, err := w.renderEvent(eventHandle); err != nil {
|
||||
w.Log.Errorf("Rendering event failed: %v", err)
|
||||
} else {
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
if err := evtUpdateBookmark(w.bookmark, eventHandle); err != nil {
|
||||
w.Log.Errorf("Updateing bookmark failed: %v", err)
|
||||
if evterr == nil {
|
||||
evterr = err
|
||||
}
|
||||
}
|
||||
|
||||
if err := evtClose(eventHandle); err != nil {
|
||||
w.Log.Errorf("Closing event failed: %v", err)
|
||||
if evterr == nil {
|
||||
evterr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
return events, evterr
|
||||
}
|
||||
|
||||
func (w *WinEventLog) renderBookmark() (string, error) {
|
||||
// Determine the buffer size required
|
||||
var used uint32
|
||||
err := evtRender(w.bookmark, evtRenderBookmark, 0, nil, &used)
|
||||
if err != nil && !errors.Is(err, errInsufficientBuffer) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Actually retrieve the data
|
||||
buf := make([]byte, used)
|
||||
if err := evtRender(w.bookmark, evtRenderBookmark, uint32(len(buf)), &buf[0], &used); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Decocde the charset
|
||||
decoded, err := decodeUTF16(buf[:used])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Strip the trailing null character if any
|
||||
if decoded[len(decoded)-1] == 0 {
|
||||
decoded = decoded[:len(decoded)-1]
|
||||
}
|
||||
|
||||
return string(decoded), err
|
||||
}
|
||||
|
||||
func (w *WinEventLog) renderEvent(eventHandle evtHandle) (event, error) {
|
||||
// Determine the size of the buffer and grow the buffer if necessary
|
||||
var used uint32
|
||||
err := evtRender(eventHandle, evtRenderEventXML, 0, nil, &used)
|
||||
if err != nil && !errors.Is(err, errInsufficientBuffer) {
|
||||
return event{}, err
|
||||
}
|
||||
|
||||
// If the event size exceeds the limit exit early as truncating the event
|
||||
// data would destroy the XML structure.
|
||||
if used > uint32(w.EventSizeLimit) {
|
||||
return event{}, errEventTooLarge
|
||||
}
|
||||
|
||||
// Actually retrieve the event
|
||||
buf := make([]byte, used)
|
||||
if err := evtRender(eventHandle, evtRenderEventXML, uint32(len(buf)), &buf[0], &used); err != nil {
|
||||
return event{}, err
|
||||
}
|
||||
|
||||
// Decode the charset
|
||||
eventXML, err := decodeUTF16(buf[:used])
|
||||
if err != nil {
|
||||
return event{}, err
|
||||
}
|
||||
|
||||
// Unmarshal the event XML. For forwarded events, this can fail but we can
|
||||
// return the event without most text values, that way we will not lose
|
||||
// information.
|
||||
var evt event
|
||||
if err := xml.Unmarshal(eventXML, &evt); err != nil {
|
||||
//nolint:nilerr // This can happen when processing Forwarded Events
|
||||
return evt, nil
|
||||
}
|
||||
|
||||
// Do resolve local messages the usual way, while using built-in information for events forwarded by WEC.
|
||||
// This is a safety measure as the underlying Windows-internal EvtFormatMessage might segfault in cases
|
||||
// where the publisher (i.e. the remote machine which forwarded the event) is unavailable e.g. due to
|
||||
// a reboot. See https://github.com/influxdata/telegraf/issues/12328 for the full story.
|
||||
if evt.RenderingInfo == nil {
|
||||
return w.renderLocalMessage(evt, eventHandle)
|
||||
}
|
||||
|
||||
// We got 'RenderInfo' elements, so try to apply them in the following function
|
||||
return w.renderRemoteMessage(evt)
|
||||
}
|
||||
|
||||
func (w *WinEventLog) renderLocalMessage(event event, eventHandle evtHandle) (event, error) {
|
||||
publisherHandle, err := openPublisherMetadata(0, event.Source.Name, w.Locale)
|
||||
if err != nil {
|
||||
return event, nil
|
||||
}
|
||||
defer evtClose(publisherHandle) //nolint:errcheck // Ignore error returned during Close
|
||||
|
||||
// Populating text values
|
||||
keywords, err := formatEventString(evtFormatMessageKeyword, eventHandle, publisherHandle)
|
||||
if err == nil {
|
||||
event.Keywords = keywords
|
||||
}
|
||||
message, err := formatEventString(evtFormatMessageEvent, eventHandle, publisherHandle)
|
||||
if err == nil {
|
||||
if w.OnlyFirstLineOfMessage {
|
||||
scanner := bufio.NewScanner(strings.NewReader(message))
|
||||
scanner.Scan()
|
||||
message = scanner.Text()
|
||||
}
|
||||
event.Message = message
|
||||
}
|
||||
level, err := formatEventString(evtFormatMessageLevel, eventHandle, publisherHandle)
|
||||
if err == nil {
|
||||
event.LevelText = level
|
||||
}
|
||||
task, err := formatEventString(evtFormatMessageTask, eventHandle, publisherHandle)
|
||||
if err == nil {
|
||||
event.TaskText = task
|
||||
}
|
||||
opcode, err := formatEventString(evtFormatMessageOpcode, eventHandle, publisherHandle)
|
||||
if err == nil {
|
||||
event.OpcodeText = opcode
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func (w *WinEventLog) renderRemoteMessage(event event) (event, error) {
|
||||
// Populating text values from RenderingInfo part of the XML
|
||||
if len(event.RenderingInfo.Keywords) > 0 {
|
||||
event.Keywords = strings.Join(event.RenderingInfo.Keywords, ",")
|
||||
}
|
||||
if event.RenderingInfo.Message != "" {
|
||||
message := event.RenderingInfo.Message
|
||||
if w.OnlyFirstLineOfMessage {
|
||||
scanner := bufio.NewScanner(strings.NewReader(message))
|
||||
scanner.Scan()
|
||||
message = scanner.Text()
|
||||
}
|
||||
event.Message = message
|
||||
}
|
||||
if event.RenderingInfo.Level != "" {
|
||||
event.LevelText = event.RenderingInfo.Level
|
||||
}
|
||||
if event.RenderingInfo.Task != "" {
|
||||
event.TaskText = event.RenderingInfo.Task
|
||||
}
|
||||
if event.RenderingInfo.Opcode != "" {
|
||||
event.OpcodeText = event.RenderingInfo.Opcode
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func formatEventString(messageFlag evtFormatMessageFlag, eventHandle, publisherHandle evtHandle) (string, error) {
|
||||
var bufferUsed uint32
|
||||
err := evtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag, 0, nil, &bufferUsed)
|
||||
if err != nil && !errors.Is(err, errInsufficientBuffer) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Handle empty elements
|
||||
if bufferUsed < 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
bufferUsed *= 2
|
||||
buffer := make([]byte, bufferUsed)
|
||||
bufferUsed = 0
|
||||
|
||||
err = evtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag,
|
||||
uint32(len(buffer)/2), &buffer[0], &bufferUsed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bufferUsed *= 2
|
||||
|
||||
result, err := decodeUTF16(buffer[:bufferUsed])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var out string
|
||||
if messageFlag == evtFormatMessageKeyword {
|
||||
// Keywords are returned as array of a zero-terminated strings
|
||||
splitZero := func(c rune) bool { return c == '\x00' }
|
||||
eventKeywords := strings.FieldsFunc(string(result), splitZero)
|
||||
// So convert them to comma-separated string
|
||||
out = strings.Join(eventKeywords, ",")
|
||||
} else {
|
||||
result := bytes.Trim(result, "\x00")
|
||||
out = string(result)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// openPublisherMetadata opens a handle to the publisher's metadata. Close must
|
||||
// be called on returned evtHandle when finished with the handle.
|
||||
func openPublisherMetadata(session evtHandle, publisherName string, lang uint32) (evtHandle, error) {
|
||||
p, err := syscall.UTF16PtrFromString(publisherName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
h, err := evtOpenPublisherMetadata(session, p, nil, lang, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("win_eventlog", func() telegraf.Input {
|
||||
return &WinEventLog{
|
||||
ProcessUserData: true,
|
||||
ProcessEventData: true,
|
||||
Separator: "_",
|
||||
OnlyFirstLineOfMessage: true,
|
||||
TimeStampFromEvent: true,
|
||||
EventTags: []string{"Source", "EventID", "Level", "LevelText", "Keywords", "Channel", "Computer"},
|
||||
ExcludeEmpty: []string{"Task", "Opcode", "*ActivityID", "UserID"},
|
||||
}
|
||||
})
|
||||
}
|
33
plugins/inputs/win_eventlog/win_eventlog_notwindows.go
Normal file
33
plugins/inputs/win_eventlog/win_eventlog_notwindows.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
//go:build !windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
type WinEventLog struct {
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
}
|
||||
|
||||
func (*WinEventLog) SampleConfig() string { return sampleConfig }
|
||||
|
||||
func (w *WinEventLog) Init() error {
|
||||
w.Log.Warn("Current platform is not supported")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*WinEventLog) Gather(telegraf.Accumulator) error { return nil }
|
||||
|
||||
func init() {
|
||||
inputs.Add("win_eventlog", func() telegraf.Input {
|
||||
return &WinEventLog{}
|
||||
})
|
||||
}
|
133
plugins/inputs/win_eventlog/win_eventlog_test.go
Normal file
133
plugins/inputs/win_eventlog/win_eventlog_test.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWinEventLog_shouldExcludeEmptyField(t *testing.T) {
|
||||
type args struct {
|
||||
field string
|
||||
fieldType string
|
||||
fieldValue interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
w *WinEventLog
|
||||
args args
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Not in list",
|
||||
args: args{field: "qq", fieldType: "string", fieldValue: ""},
|
||||
expected: false,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
args: args{field: "test", fieldType: "string", fieldValue: ""},
|
||||
expected: true,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Non-empty string",
|
||||
args: args{field: "test", fieldType: "string", fieldValue: "qq"},
|
||||
expected: false,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Zero int",
|
||||
args: args{field: "test", fieldType: "int", fieldValue: int(0)},
|
||||
expected: true,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Non-zero int",
|
||||
args: args{field: "test", fieldType: "int", fieldValue: int(-1)},
|
||||
expected: false,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Zero uint32",
|
||||
args: args{field: "test", fieldType: "uint32", fieldValue: uint32(0)},
|
||||
expected: true,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
{
|
||||
name: "Non-zero uint32",
|
||||
args: args{field: "test", fieldType: "uint32", fieldValue: uint32(0xc0fefeed)},
|
||||
expected: false,
|
||||
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.NoError(t, tt.w.Init())
|
||||
actual := tt.w.shouldExcludeEmptyField(tt.args.field, tt.args.fieldType, tt.args.fieldValue)
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWinEventLog_shouldProcessField(t *testing.T) {
|
||||
tags := []string{"Source", "Level*"}
|
||||
fields := []string{"EventID", "Message*"}
|
||||
excluded := []string{"Message*"}
|
||||
type args struct {
|
||||
field string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
w *WinEventLog
|
||||
args args
|
||||
wantShould bool
|
||||
wantList string
|
||||
}{
|
||||
{
|
||||
name: "Not in tags",
|
||||
args: args{field: "test"},
|
||||
wantShould: false,
|
||||
wantList: "excluded",
|
||||
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
|
||||
},
|
||||
{
|
||||
name: "In Tags",
|
||||
args: args{field: "LevelText"},
|
||||
wantShould: true,
|
||||
wantList: "tags",
|
||||
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
|
||||
},
|
||||
{
|
||||
name: "Not in Fields",
|
||||
args: args{field: "EventId"},
|
||||
wantShould: false,
|
||||
wantList: "excluded",
|
||||
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
|
||||
},
|
||||
{
|
||||
name: "In Fields",
|
||||
args: args{field: "EventID"},
|
||||
wantShould: true,
|
||||
wantList: "fields",
|
||||
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
|
||||
},
|
||||
{
|
||||
name: "In Fields and Excluded",
|
||||
args: args{field: "Messages"},
|
||||
wantShould: false,
|
||||
wantList: "excluded",
|
||||
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.NoError(t, tt.w.Init())
|
||||
should, list := tt.w.shouldProcessField(tt.args.field)
|
||||
require.Equal(t, tt.wantShould, should)
|
||||
require.Equal(t, tt.wantList, list)
|
||||
})
|
||||
}
|
||||
}
|
250
plugins/inputs/win_eventlog/zsyscall_windows.go
Normal file
250
plugins/inputs/win_eventlog/zsyscall_windows.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
//go:build windows
|
||||
|
||||
package win_eventlog
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// evtHandle uintptr
|
||||
type evtHandle uintptr
|
||||
|
||||
// Do the interface allocations only once for common errno values.
|
||||
const (
|
||||
errnoErrorIOPending = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errErrorIOPending error = syscall.Errno(errnoErrorIOPending)
|
||||
)
|
||||
|
||||
// evtFormatMessageFlag defines the values that specify the message string from the event to format.
|
||||
type evtFormatMessageFlag uint32
|
||||
|
||||
// EVT_FORMAT_MESSAGE_FLAGS enumeration
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385525(v=vs.85).aspx
|
||||
const (
|
||||
// evtFormatMessageEvent - Format the event's message string.
|
||||
evtFormatMessageEvent evtFormatMessageFlag = iota + 1
|
||||
// evtFormatMessageLevel - Format the message string of the level specified in the event.
|
||||
evtFormatMessageLevel
|
||||
// evtFormatMessageTask - Format the message string of the task specified in the event.
|
||||
evtFormatMessageTask
|
||||
// evtFormatMessageOpcode - Format the message string of the task specified in the event.
|
||||
evtFormatMessageOpcode
|
||||
// evtFormatMessageKeyword - Format the message string of the keywords specified in the event. If the
|
||||
// event specifies multiple keywords, the formatted string is a list of null-terminated strings.
|
||||
// Increment through the strings until your pointer points past the end of the used buffer.
|
||||
evtFormatMessageKeyword
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoErrorIOPending:
|
||||
return errErrorIOPending
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modwevtapi = windows.NewLazySystemDLL("wevtapi.dll")
|
||||
|
||||
procEvtSubscribe = modwevtapi.NewProc("EvtSubscribe")
|
||||
procEvtRender = modwevtapi.NewProc("EvtRender")
|
||||
procEvtClose = modwevtapi.NewProc("EvtClose")
|
||||
procEvtNext = modwevtapi.NewProc("EvtNext")
|
||||
procEvtFormatMessage = modwevtapi.NewProc("EvtFormatMessage")
|
||||
procEvtOpenPublisherMetadata = modwevtapi.NewProc("EvtOpenPublisherMetadata")
|
||||
procEvtCreateBookmark = modwevtapi.NewProc("EvtCreateBookmark")
|
||||
procEvtUpdateBookmark = modwevtapi.NewProc("EvtUpdateBookmark")
|
||||
)
|
||||
|
||||
//nolint:revive //argument-limit conditionally more arguments allowed
|
||||
func evtSubscribe(
|
||||
session evtHandle,
|
||||
signalEvent uintptr,
|
||||
channelPath *uint16,
|
||||
query *uint16,
|
||||
bookmark evtHandle,
|
||||
context uintptr,
|
||||
callback syscall.Handle,
|
||||
flags evtSubscribeFlag,
|
||||
) (evtHandle, error) {
|
||||
r0, _, e1 := syscall.SyscallN(
|
||||
procEvtSubscribe.Addr(),
|
||||
uintptr(session),
|
||||
signalEvent,
|
||||
uintptr(unsafe.Pointer(channelPath)), //nolint:gosec // G103: Valid use of unsafe call to pass channelPath
|
||||
uintptr(unsafe.Pointer(query)), //nolint:gosec // G103: Valid use of unsafe call to pass query
|
||||
uintptr(bookmark),
|
||||
context,
|
||||
uintptr(callback),
|
||||
uintptr(flags),
|
||||
)
|
||||
|
||||
var err error
|
||||
handle := evtHandle(r0)
|
||||
if handle == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return handle, err
|
||||
}
|
||||
|
||||
func evtRender(
|
||||
fragment evtHandle,
|
||||
flags evtRenderFlag,
|
||||
bufferSize uint32,
|
||||
buffer *byte,
|
||||
bufferUsed *uint32,
|
||||
) error {
|
||||
r1, _, e1 := syscall.SyscallN(
|
||||
procEvtRender.Addr(),
|
||||
uintptr(0), // context
|
||||
uintptr(fragment),
|
||||
uintptr(flags),
|
||||
uintptr(bufferSize),
|
||||
uintptr(unsafe.Pointer(buffer)), //nolint:gosec // G103: Valid use of unsafe call to pass buffer
|
||||
uintptr(unsafe.Pointer(bufferUsed)), //nolint:gosec // G103: Valid use of unsafe call to pass bufferUsed
|
||||
uintptr(unsafe.Pointer(nil)), //nolint:gosec // G103: Valid use of unsafe call to pass propertyCount
|
||||
)
|
||||
|
||||
var err error
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func evtClose(object evtHandle) error {
|
||||
r1, _, e1 := syscall.SyscallN(procEvtClose.Addr(), uintptr(object))
|
||||
var err error
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func evtNext(resultSet evtHandle, eventArraySize uint32, eventArray *evtHandle, timeout, flags uint32, numReturned *uint32) error {
|
||||
r1, _, e1 := syscall.SyscallN(
|
||||
procEvtNext.Addr(),
|
||||
uintptr(resultSet),
|
||||
uintptr(eventArraySize),
|
||||
uintptr(unsafe.Pointer(eventArray)), //nolint:gosec // G103: Valid use of unsafe call to pass eventArray
|
||||
uintptr(timeout),
|
||||
uintptr(flags),
|
||||
uintptr(unsafe.Pointer(numReturned)), //nolint:gosec // G103: Valid use of unsafe call to pass numReturned
|
||||
)
|
||||
|
||||
var err error
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:revive //argument-limit conditionally more arguments allowed
|
||||
func evtFormatMessage(
|
||||
publisherMetadata evtHandle,
|
||||
event evtHandle,
|
||||
messageID uint32,
|
||||
valueCount uint32,
|
||||
values uintptr,
|
||||
flags evtFormatMessageFlag,
|
||||
bufferSize uint32,
|
||||
buffer *byte,
|
||||
bufferUsed *uint32,
|
||||
) error {
|
||||
r1, _, e1 := syscall.SyscallN(
|
||||
procEvtFormatMessage.Addr(),
|
||||
uintptr(publisherMetadata),
|
||||
uintptr(event),
|
||||
uintptr(messageID),
|
||||
uintptr(valueCount),
|
||||
values,
|
||||
uintptr(flags),
|
||||
uintptr(bufferSize),
|
||||
uintptr(unsafe.Pointer(buffer)), //nolint:gosec // G103: Valid use of unsafe call to pass buffer
|
||||
uintptr(unsafe.Pointer(bufferUsed)), //nolint:gosec // G103: Valid use of unsafe call to pass bufferUsed
|
||||
)
|
||||
|
||||
var err error
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func evtOpenPublisherMetadata(session evtHandle, publisherIdentity, logFilePath *uint16, locale, flags uint32) (evtHandle, error) {
|
||||
r0, _, e1 := syscall.SyscallN(
|
||||
procEvtOpenPublisherMetadata.Addr(),
|
||||
uintptr(session),
|
||||
uintptr(unsafe.Pointer(publisherIdentity)), //nolint:gosec // G103: Valid use of unsafe call to pass publisherIdentity
|
||||
uintptr(unsafe.Pointer(logFilePath)), //nolint:gosec // G103: Valid use of unsafe call to pass logFilePath
|
||||
uintptr(locale),
|
||||
uintptr(flags),
|
||||
)
|
||||
|
||||
var err error
|
||||
handle := evtHandle(r0)
|
||||
if handle == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return handle, err
|
||||
}
|
||||
|
||||
func evtCreateBookmark(bookmarkXML *uint16) (evtHandle, error) {
|
||||
//nolint:gosec // G103: Valid use of unsafe call to pass bookmarkXML
|
||||
r0, _, e1 := syscall.SyscallN(procEvtCreateBookmark.Addr(), uintptr(unsafe.Pointer(bookmarkXML)))
|
||||
handle := evtHandle(r0)
|
||||
if handle != 0 {
|
||||
return handle, nil
|
||||
}
|
||||
if e1 != 0 {
|
||||
return handle, errnoErr(e1)
|
||||
}
|
||||
return handle, syscall.EINVAL
|
||||
}
|
||||
|
||||
func evtUpdateBookmark(bookmark, event evtHandle) error {
|
||||
r0, _, e1 := syscall.SyscallN(procEvtUpdateBookmark.Addr(), uintptr(bookmark), uintptr(event))
|
||||
if r0 != 0 {
|
||||
return nil
|
||||
}
|
||||
if e1 != 0 {
|
||||
return errnoErr(e1)
|
||||
}
|
||||
return syscall.EINVAL
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue