Skip to content

Commit b28a715

Browse files
postnatirite7sh
authored andcommitted
[receiver/windowseventlog] Add SID resolution feature (open-telemetry#45878)
#### Description Adds automatic Security Identifier (SID) resolution to the Windows Event Log Receiver. This enhancement resolves Windows SIDs to human-readable user and group names using the Windows LSA API, making Windows event logs significantly more usable for security operations. #### Link to tracking issue Fixes open-telemetry#45875 #### Testing Comprehensive unit tests added covering: - Cache operations (Get, Set, Close) - Well-known SID resolution (45+ built-in mappings) - Invalid SID handling - SID field detection logic - End-to-end enrichment scenarios - Platform-specific builds (Windows/non-Windows) #### Documentation Added comprehensive documentation to README.md, including: - Complete configuration options and defaults - Before/after examples with real SID resolution - Performance characteristics and benchmarks - Troubleshooting guide - Well-known SIDs reference - Configuration example files in testdata/ **Documentation Files:** - `receiver/windowseventlogreceiver/README.md` - Updated with SID information - `receiver/windowseventlogreceiver/testdata/README.md` - Complete setup guide - `receiver/windowseventlogreceiver/testdata/collector-config-example.yaml` - Example config
1 parent beecda0 commit b28a715

20 files changed

Lines changed: 2221 additions & 1 deletion
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: receiver/windowseventlog
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add SID resolution feature to automatically resolve Windows Security Identifiers to user and group names
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [45875]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
Added new `resolve_sids` configuration option with configurable cache size and TTL.
20+
When enabled, Windows Security Identifiers (SIDs) in event logs are automatically resolved to human-readable names using the Windows LSA API.
21+
Includes support for well-known SIDs, domain users and groups, and high-performance LRU caching for improved throughput.
22+
23+
# If your change doesn't affect end users or the exported elements of any package,
24+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
25+
# Optional: The change log or logs in which this entry should be included.
26+
# e.g. '[user]' or '[user, api]'
27+
# Include 'user' if the change is relevant to end users.
28+
# Include 'api' if there is a change to a library API.
29+
# Default: '[user]'
30+
change_logs: [user]

receiver/windowseventlogreceiver/README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Tails and parses logs from windows event log API using the [opentelemetry-log-co
4040
| `retry_on_failure.max_elapsed_time` | `5 minutes` | Maximum amount of time (including retries) spent trying to send a logs batch to a downstream consumer. Once this value is reached, the data is discarded. Retrying never stops if set to `0`. |
4141
| `remote` | object | Remote configuration for connecting to a remote machine to collect logs. Includes server (the address of the remote server), with username, password, and optional domain. |
4242
| `query` | none | XML query used for filtering events. See [Query Schema](https://learn.microsoft.com/en-us/windows/win32/wes/queryschema-schema) |
43+
| `resolve_sids` | object | Configuration for resolving Windows Security Identifiers (SIDs) to user/group names. See [SID Resolution](#sid-resolution) section below. |
44+
| `resolve_sids.enabled` | `false` | If `true`, automatically resolves SIDs to user and group names in Windows event logs. |
45+
| `resolve_sids.cache_size` | `10000` | Maximum number of SID-to-name mappings to cache in memory. Older entries are evicted using LRU policy. |
46+
| `resolve_sids.cache_ttl` | `15m` | Time-to-live for cached SID mappings. After this duration, SIDs will be re-resolved from the Windows LSA API. |
4347

4448
### Operators
4549

@@ -134,3 +138,109 @@ receivers:
134138
</Query>
135139
</QueryList>
136140
```
141+
142+
#### SID Resolution
143+
144+
Windows Event Logs often contain Security Identifiers (SIDs) instead of readable user or group names. The SID resolution feature automatically resolves these SIDs to human-readable names using the Windows Local Security Authority (LSA) API.
145+
146+
**Key Features:**
147+
- Automatically enriches Windows events with resolved user and group names
148+
- High-performance LRU cache with configurable size and TTL
149+
- Resolves well-known SIDs (SYSTEM, LOCAL_SERVICE, etc.) instantly from static map
150+
- Works with domain-joined machines to resolve domain users and groups
151+
- Non-breaking: original SID values are preserved alongside resolved names
152+
153+
**Configuration:**
154+
```yaml
155+
receivers:
156+
windowseventlog:
157+
channel: Security
158+
resolve_sids:
159+
enabled: true # Enable SID resolution
160+
cache_size: 10000 # Cache up to 10,000 SID-to-name mappings
161+
cache_ttl: 15m # Re-resolve SIDs after 15 minutes
162+
```
163+
164+
**Without SID Resolution:**
165+
```json
166+
{
167+
"security": {
168+
"user_id": "S-1-5-21-3623811015-3361044348-30300820-1013"
169+
},
170+
"event_data": {
171+
"SubjectUserSid": "S-1-5-18",
172+
"TargetUserSid": "S-1-5-21-3623811015-3361044348-30300820-1013"
173+
}
174+
}
175+
```
176+
177+
**With SID Resolution:**
178+
```json
179+
{
180+
"security": {
181+
"user_id": "S-1-5-21-3623811015-3361044348-30300820-1013",
182+
"user_name": "ACME\\jsmith",
183+
"domain": "ACME",
184+
"account": "jsmith",
185+
"account_type": "User"
186+
},
187+
"event_data": {
188+
"SubjectUserSid": "S-1-5-18",
189+
"SubjectUserSid_Resolved": "NT AUTHORITY\\SYSTEM",
190+
"SubjectUserSid_Domain": "NT AUTHORITY",
191+
"SubjectUserSid_Account": "SYSTEM",
192+
"SubjectUserSid_Type": "WellKnownGroup",
193+
"TargetUserSid": "S-1-5-21-3623811015-3361044348-30300820-1013",
194+
"TargetUserSid_Resolved": "ACME\\jsmith",
195+
"TargetUserSid_Domain": "ACME",
196+
"TargetUserSid_Account": "jsmith",
197+
"TargetUserSid_Type": "User"
198+
}
199+
}
200+
```
201+
202+
**Performance Characteristics:**
203+
204+
- Cache hit latency: < 1 microsecond
205+
- Cache miss latency: < 5 milliseconds (Windows LSA API call)
206+
- Expected cache hit rate: > 99% in steady state
207+
- Memory usage: ~100 bytes per cached entry
208+
- Throughput impact: < 5% with cache enabled
209+
210+
**Limitations:**
211+
212+
- Only resolves SIDs for the local system or the domain the Windows machine is joined to
213+
- Cannot resolve SIDs from trusted domains (requires LDAP extension)
214+
- Remote collection: SID resolution only works when the collector runs on Windows
215+
- Cache lifecycle: Cache is created at receiver start and closed at shutdown
216+
217+
**Troubleshooting:**
218+
219+
If SID resolution is not working as expected:
220+
221+
1. **Check logs for initialization message:**
222+
```
223+
INFO SID resolution enabled {"cache_size": 10000, "cache_ttl": "15m0s"}
224+
```
225+
226+
2. **Verify the collector is running on Windows:**
227+
SID resolution only works on Windows operating systems.
228+
229+
3. **Check for resolution errors:**
230+
Failed SID lookups are logged at DEBUG level:
231+
```
232+
DEBUG Failed to resolve SID {"sid": "S-1-5-21-...", "error": "..."}
233+
```
234+
235+
4. **Verify SID format:**
236+
Only fields ending with "Sid" or named "UserID" are automatically resolved.
237+
Custom SID fields may not be detected.
238+
239+
**Well-Known SIDs:**
240+
The following SIDs are resolved instantly from a static map (no API call required):
241+
- `S-1-5-18` - NT AUTHORITY\SYSTEM
242+
- `S-1-5-19` - NT AUTHORITY\LOCAL SERVICE
243+
- `S-1-5-20` - NT AUTHORITY\NETWORK SERVICE
244+
- `S-1-5-32-544` - BUILTIN\Administrators
245+
- `S-1-1-0` - Everyone
246+
- And 40+ more common Windows SIDs
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
$defs:
2+
resolve_si_ds_config:
3+
description: ResolveSIDsConfig contains configuration for SID resolution
4+
type: object
5+
properties:
6+
cache_size:
7+
description: 'CacheSize is the maximum number of SIDs to cache (LRU eviction) Default: 10000'
8+
type: integer
9+
x-customType: uint
10+
cache_ttl:
11+
description: 'CacheTTL is how long cache entries remain valid Default: 15m'
12+
type: string
13+
format: duration
14+
enabled:
15+
description: Enabled controls whether SID resolution is active
16+
type: boolean
117
description: WindowsLogConfig defines configuration for the windowseventlog receiver
218
type: object
19+
properties:
20+
resolve_sids:
21+
description: ResolveSIDs contains configuration for SID-to-username resolution
22+
$ref: resolve_si_ds_config
323
allOf:
424
- $ref: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/operator/input/windows.config
525
- $ref: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/adapter.base_config

receiver/windowseventlogreceiver/factory_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package windowseventlogreceiver // import "github.com/open-telemetry/opentelemet
66
import (
77
"runtime"
88
"testing"
9+
"time"
910

1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
13+
"go.opentelemetry.io/collector/confmap"
1214
"go.opentelemetry.io/collector/consumer/consumertest"
1315
"go.opentelemetry.io/collector/receiver/receivertest"
1416

@@ -28,6 +30,38 @@ func TestCreateDefaultConfig(t *testing.T) {
2830
require.NotNil(t, cfg, "failed to create default config")
2931
}
3032

33+
func TestNegativeCacheSizeRejected(t *testing.T) {
34+
factory := NewFactory()
35+
cfg := factory.CreateDefaultConfig()
36+
37+
cm := confmap.NewFromStringMap(map[string]any{
38+
"resolve_sids": map[string]any{
39+
"cache_size": -1,
40+
},
41+
})
42+
err := cm.Unmarshal(cfg)
43+
require.Error(t, err)
44+
require.ErrorContains(t, err, "cache_size")
45+
}
46+
47+
func TestNegativeCacheTTLRejected(t *testing.T) {
48+
cfg := &ResolveSIDsConfig{
49+
Enabled: true,
50+
CacheTTL: -1 * time.Second,
51+
}
52+
err := cfg.Validate()
53+
require.Error(t, err)
54+
require.ErrorContains(t, err, "cache_ttl must not be negative")
55+
}
56+
57+
func TestZeroCacheTTLAccepted(t *testing.T) {
58+
cfg := &ResolveSIDsConfig{
59+
Enabled: true,
60+
CacheTTL: 0,
61+
}
62+
require.NoError(t, cfg.Validate())
63+
}
64+
3165
func TestCreateAndShutdown(t *testing.T) {
3266
factory := NewFactory()
3367
defaultConfig := factory.CreateDefaultConfig()

receiver/windowseventlogreceiver/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
)
1818

1919
require (
20+
github.com/hashicorp/golang-lru/v2 v2.0.7
2021
go.opentelemetry.io/collector/component/componenttest v0.147.1-0.20260306010043-a44ab254898b
2122
go.opentelemetry.io/collector/consumer/consumertest v0.147.1-0.20260306010043-a44ab254898b
2223
go.opentelemetry.io/collector/pdata v1.53.1-0.20260306010043-a44ab254898b

receiver/windowseventlogreceiver/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package sidcache provides a high-performance LRU cache for Windows SID-to-name resolution.
5+
// It uses the Windows Local Security Authority (LSA) API to resolve Security Identifiers (SIDs)
6+
// to human-readable user and group names, with support for well-known SIDs and TTL-based expiration.
7+
package sidcache // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowseventlogreceiver/internal/sidcache"
8+
9+
import (
10+
"regexp"
11+
"time"
12+
)
13+
14+
// Default configuration values
15+
const (
16+
DefaultCacheSize = 10000
17+
DefaultCacheTTL = 15 * time.Minute
18+
)
19+
20+
// sidPattern validates SID format: S-1-<revision>-<authority>-<sub-authorities>
21+
var sidPattern = regexp.MustCompile(`^S-1-\d+(-\d+)+$`)
22+
23+
// isSIDFormat checks if a string matches the SID format
24+
func isSIDFormat(sid string) bool {
25+
return sidPattern.MatchString(sid)
26+
}
27+
28+
// IsSIDField checks if a field name likely contains a SID
29+
// Used by the receiver to identify which fields need resolution
30+
func IsSIDField(fieldName string) bool {
31+
// Common SID field patterns in Windows events
32+
return len(fieldName) >= 3 && (fieldName[len(fieldName)-3:] == "Sid" || // ends with "Sid"
33+
fieldName == "UserID") // exact match for security.user_id
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:build !windows
5+
6+
package sidcache // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/windowseventlogreceiver/internal/sidcache"
7+
8+
import "errors"
9+
10+
// New returns an error on non-Windows platforms since SID resolution
11+
// requires the Windows Local Security Authority API.
12+
func New(_ Config) (Cache, error) {
13+
return nil, errors.New("SID cache is only supported on Windows")
14+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package sidcache
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestIsSIDField(t *testing.T) {
13+
tests := []struct {
14+
fieldName string
15+
expect bool
16+
}{
17+
{"SubjectUserSid", true},
18+
{"TargetUserSid", true},
19+
{"UserSid", true},
20+
{"Sid", true},
21+
{"UserID", true},
22+
{"UserName", false},
23+
{"EventID", false},
24+
{"Si", false},
25+
{"", false},
26+
{"AccountName", false},
27+
}
28+
29+
for _, tt := range tests {
30+
t.Run(tt.fieldName, func(t *testing.T) {
31+
result := IsSIDField(tt.fieldName)
32+
require.Equal(t, tt.expect, result)
33+
})
34+
}
35+
}
36+
37+
func TestIsSIDFormat(t *testing.T) {
38+
tests := []struct {
39+
sid string
40+
expect bool
41+
}{
42+
{"S-1-5-18", true},
43+
{"S-1-5-32-544", true},
44+
{"S-1-5-21-3623811015-3361044348-30300820-1013", true},
45+
{"S-1-0-0", true},
46+
{"", false},
47+
{"S-1", false},
48+
{"S-1-5", false},
49+
{"X-1-5-18", false},
50+
{"S-X-5-18", false},
51+
{"not-a-sid", false},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.sid, func(t *testing.T) {
56+
result := isSIDFormat(tt.sid)
57+
require.Equal(t, tt.expect, result)
58+
})
59+
}
60+
}

0 commit comments

Comments
 (0)