Phase 4: syslog UDP ingestion source (D-0023) #4

Merged
hannah-vernon merged 1 commit from feature/syslog-source into dev 2026-08-19 12:30:29 -05:00

What does this PR do?

Implements the general syslog UDP ingestion source (Phase 4, D-0023), serving SWAG/nginx first and future senders (MikroTik RouterOS remote logging, other LAN hosts).

  • Viegard.Sources.Syslog:
    • SyslogUdpSource: thin UDP receive loop; per-source health including drop counters.
    • SyslogDatagramHandler (testable guardrail core): fail-closed source-IP allowlist (no allowlist, no listener), datagram size cap, per-source token-bucket rate cap, replacement-character UTF-8 decoding so hostile bytes can never throw.
    • SyslogEnvelopeParser: RFC 3164 (pid tags, year inference with December/January wrap) and RFC 5424; unparseable input degrades to a message-only envelope, never an exception.
    • NginxAccessLogParser: standard combined format plus the Viegard host=/rt= extension; malformed or hostile request lines yield partial fields.
    • SyslogEventNormalizer: nginx_access-tagged lines become HttpRequestEvent with IP/host/URI/User-Agent entity refs; everything else becomes a generic SyslogEvent whose origin identity is the peer IP, with the claimed hostname carried as untrusted data.
  • Domain: HttpRequestEvent and SyslogEvent payloads.
  • PipelineHost: syslog registration (disabled by default; startup validation refuses an enabled listener without an allowlist); debug log on successful ingestion.
  • docs/swag-syslog-setup.md: sanitized SWAG-side nginx configuration guide (log_format viegard, syslog access_log/error_log targets, firewall note, keep-file-logs guidance).

Fixes #

How was this tested?

  • dotnet build Viegard.slnx - 0 errors, 0 warnings
  • dotnet test Viegard.slnx - 130/130 pass (31 new: envelope parsing incl. hostile content verbatim, nginx parsing incl. injection-text-in-UA as data and IPv6, allowlist/size/rate guardrails with FakeTimeProvider, spoofed-hostname handling, fail-closed DTO validation)
  • End-to-end smoke test: live UDP datagrams sent to the running host produced a normalized HttpRequestEvent (nginx access line) and a generic SyslogEvent (hostile "ignore previous instructions" message, treated purely as data), both persisted and queued for correlation
  • Live SWAG configuration not yet applied (deployment-time step; see docs/swag-syslog-setup.md and TODO.md)

Checklist

  • I have read the Contributing Guide
  • Changes are focused - one logical change per PR
  • Documentation updated (AGENT-README.md, TODO.md, THIRD-PARTY-NOTICES.md, docs/swag-syslog-setup.md)
  • New dependencies are MIT/Apache-2.0 and security-vetted (Microsoft.Extensions.TimeProvider.Testing 10.9.0, MIT, test-only; recorded)
  • No secrets, credentials, or real infrastructure identifiers in the diff (doc examples use RFC 5737 addresses)
  • No commented-out code or debug leftovers
## What does this PR do? Implements the general syslog UDP ingestion source (Phase 4, D-0023), serving SWAG/nginx first and future senders (MikroTik RouterOS remote logging, other LAN hosts). - **`Viegard.Sources.Syslog`:** - `SyslogUdpSource`: thin UDP receive loop; per-source health including drop counters. - `SyslogDatagramHandler` (testable guardrail core): **fail-closed source-IP allowlist** (no allowlist, no listener), datagram size cap, per-source token-bucket rate cap, replacement-character UTF-8 decoding so hostile bytes can never throw. - `SyslogEnvelopeParser`: RFC 3164 (pid tags, year inference with December/January wrap) and RFC 5424; unparseable input degrades to a message-only envelope, never an exception. - `NginxAccessLogParser`: standard combined format plus the Viegard `host=`/`rt=` extension; malformed or hostile request lines yield partial fields. - `SyslogEventNormalizer`: `nginx_access`-tagged lines become `HttpRequestEvent` with IP/host/URI/User-Agent entity refs; everything else becomes a generic `SyslogEvent` whose origin identity is the **peer IP**, with the claimed hostname carried as untrusted data. - **Domain:** `HttpRequestEvent` and `SyslogEvent` payloads. - **PipelineHost:** syslog registration (disabled by default; startup validation refuses an enabled listener without an allowlist); debug log on successful ingestion. - **`docs/swag-syslog-setup.md`:** sanitized SWAG-side nginx configuration guide (`log_format viegard`, syslog `access_log`/`error_log` targets, firewall note, keep-file-logs guidance). Fixes # ## How was this tested? - [x] `dotnet build Viegard.slnx` - 0 errors, 0 warnings - [x] `dotnet test Viegard.slnx` - 130/130 pass (31 new: envelope parsing incl. hostile content verbatim, nginx parsing incl. injection-text-in-UA as data and IPv6, allowlist/size/rate guardrails with FakeTimeProvider, spoofed-hostname handling, fail-closed DTO validation) - [x] **End-to-end smoke test:** live UDP datagrams sent to the running host produced a normalized `HttpRequestEvent` (nginx access line) and a generic `SyslogEvent` (hostile "ignore previous instructions" message, treated purely as data), both persisted and queued for correlation - [ ] Live SWAG configuration not yet applied (deployment-time step; see docs/swag-syslog-setup.md and TODO.md) ## Checklist - [x] I have read the [Contributing Guide](../CONTRIBUTING.md) - [x] Changes are focused - one logical change per PR - [x] Documentation updated (AGENT-README.md, TODO.md, THIRD-PARTY-NOTICES.md, docs/swag-syslog-setup.md) - [x] New dependencies are MIT/Apache-2.0 and security-vetted (Microsoft.Extensions.TimeProvider.Testing 10.9.0, MIT, test-only; recorded) - [x] No secrets, credentials, or real infrastructure identifiers in the diff (doc examples use RFC 5737 addresses) - [x] No commented-out code or debug leftovers
- Viegard.Sources.Syslog:
  - SyslogUdpSource: thin UDP loop; per-source health incl. drop counters
  - SyslogDatagramHandler (testable core): fail-closed source-IP
    allowlist, datagram size cap, per-source token-bucket rate cap,
    replacement-character decoding so hostile bytes never throw
  - SyslogEnvelopeParser: RFC 3164 (incl. pid tags, year inference with
    Dec/Jan wrap) and RFC 5424; garbage degrades to message-only
  - NginxAccessLogParser: combined format + Viegard host=/rt= extension;
    malformed/hostile request lines yield partial fields, never throw
  - SyslogEventNormalizer: nginx_access tags -> HttpRequestEvent with
    IP/host/URI/UA entity refs; everything else -> generic SyslogEvent
    keyed to peer IP (claimed hostname untrusted)
  - Options validation: enabled listener requires non-empty allowlist
- Domain: HttpRequestEvent + SyslogEvent payloads
- PipelineHost: syslog registration (off by default); debug log on
  successful ingestion
- docs/swag-syslog-setup.md: sanitized SWAG nginx configuration guide
- Tests: 130 passing (31 new incl. spoofed-hostname handling, injection
  text as data, rate-limit recovery with FakeTimeProvider)

Verified end-to-end: live UDP datagrams to the running host produced a
normalized HttpRequestEvent (nginx line) and generic SyslogEvent
(hostile message) queued for correlation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
hannah-vernon/viegard-sentinel!4
No description provided.