SIEM / SOC forwarding

July 18, 2026 · View on GitHub

macrdp emits its security-relevant events (connection accept / reject / auth / fingerprint / disconnect, with source IP+port, reason, and outcome) on a dedicated macrdp::audit tracing target. Point --audit-file at a file and those events are additionally written as one JSON object per line — a stable, versioned contract a log collector can tail and forward to a SIEM.

For what each event and field means and how to read them (patterns for a normal session, a wrong password, a brute-force lockout, etc.), see the companion audit-log.md. This page is about getting the stream off-box.

Want a runnable end-to-end example? siem-tutorial.md stands up a real open-source SIEM (OpenSearch) on your Mac and detects an RDP brute-force against macrdp, copy-paste, in ~15 minutes.

macrdp deliberately does not speak network syslog itself. Getting logs off-box reliably (TLS, buffering, reconnect, backpressure) is a solved problem owned by collector agents; a software RDP server should not re-implement it on its hot path. And on macOS there is no first-class network-syslog daemon anyway — unified logging replaced syslogd — so the supported integration is: macrdp → JSON file → collector agent → SIEM.

Enabling it

Off by default. Enable either way (both opt-in; the normal runtime path is byte-identical when off):

# Explicit path (CLI, config.env AUDIT_FILE=, or LaunchAgent EnvironmentVariables):
macrdp --audit-file ~/Library/Logs/macrdp-audit.log

# Or flip the switch to use the default <log-dir>/macrdp-audit.log:
MACRDP_AUDIT_JSON=1 macrdp

Knobs:

knobdefaultmeaning
--audit-file PATH / AUDIT_FILE(off)write JSON audit lines to PATH
MACRDP_AUDIT_JSON=1offenable at the default <log-dir-or-~/Library/Logs>/macrdp-audit.log
MACRDP_AUDIT_LOG / AUDIT_LOGongates whether audit events emit at all (both sinks)
MACRDP_AUDIT_LOG_MAX_BYTES10 MiBaudit-file rotation size
MACRDP_AUDIT_LOG_MAX_FILES5audit-file archive count

Notes:

  • The human-readable audit lines still appear in macrdp.log (logfmt) unchanged — the JSON file is an additional sink, not a move.
  • The JSON stream is emitted independent of RUST_LOG (a per-layer filter pins the macrdp::audit target at INFO), so raising operational verbosity to warn never suppresses security events.
  • The file self-rotates (stable live name macrdp-audit.log, archives .1, .2, …), so the collector always tails the same path.
  • Loopback is exempt from the auth guard, and audit events fire for real (non-loopback) peers — i.e. the stream is meaningful only when --bind exposes the server off-loopback.

Schema (v1)

One JSON object per line. schema_version pins the contract — it bumps only on a breaking field change (rename/removal/semantic shift), never for additive fields, so detection/parse rules can key off it safely.

Fields common to the events (all carry these except where the src_port row notes otherwise):

fieldtypenotes
timestampstringRFC3339 UTC
levelstringINFO (accept/disconnect, auth success) or WARN (reject, auth failure)
targetstringalways macrdp::audit
schema_versionint1
macrdp_versionstringe.g. 0.8.32
hoststringserver hostname (a collector usually adds its own too)
eventstringaccept | reject | auth | fingerprint | disconnect
src_ipstringpeer IP — correlation key
src_portintpeer source port — correlation key (present on accept/auth/disconnect; absent on reject, which is a per-IP decision)

Event-specific:

eventextra fields
accept
rejectreason (rate_limit | lockout), and window_attempts or retry_after_secs
authoutcome (success | did_not_complete), and reason (only on did_not_complete)
fingerprintclient_name, rdp_version, client_build, platform (which RDP client connected — informational, not a trust signal)
disconnectduration_ms, outcome (success | failure)

The auth event is the explicit CredSSP/NLA login verdict, emitted once when the exchange resolves: success = the client's credentials validated; did_not_complete = auth did not finish (dominated by a wrong password, but also a client abort or a rare mid-exchange transport error — the reason, a short sspi error description, disambiguates and never contains credential material; it is control-char-stripped and length-bounded, so it is always a safe single-line token — no log-injection risk in either sink). It's strictly better than inferring the verdict from disconnect's duration heuristic. v1 caveats: it carries no client-attempted username (macrdp authenticates a static credential — surfacing the attempted user is a possible future additive field).

Correlation: an accept, its auth, and its matching disconnect share the (src_ip, src_port) tuple. (Ephemeral ports can be reused over time; a monotonic per-connection id is a possible future additive field.)

Example lines

{"timestamp":"2026-07-10T18:22:04.117Z","level":"INFO","target":"macrdp::audit","schema_version":1,"macrdp_version":"0.8.32","host":"mac-studio","event":"accept","src_ip":"203.0.113.5","src_port":54132}
{"timestamp":"2026-07-10T18:22:04.402Z","level":"INFO","target":"macrdp::audit","schema_version":1,"macrdp_version":"0.8.32","host":"mac-studio","event":"auth","src_ip":"203.0.113.5","src_port":54132,"outcome":"success"}
{"timestamp":"2026-07-10T18:22:04.451Z","level":"INFO","target":"macrdp::audit","schema_version":1,"macrdp_version":"0.8.32","host":"mac-studio","event":"fingerprint","src_ip":"203.0.113.5","src_port":54132,"client_name":"GENMACWIN","rdp_version":"0x80011","client_build":26100,"platform":"WINDOWS/WINDOWS_NT"}
{"timestamp":"2026-07-10T18:22:09.882Z","level":"WARN","target":"macrdp::audit","schema_version":1,"macrdp_version":"0.8.32","host":"mac-studio","event":"reject","reason":"lockout","src_ip":"203.0.113.9","retry_after_secs":240}
{"timestamp":"2026-07-10T18:25:41.030Z","level":"INFO","target":"macrdp::audit","schema_version":1,"macrdp_version":"0.8.32","host":"mac-studio","event":"disconnect","src_ip":"203.0.113.5","src_port":54132,"duration_ms":216913,"outcome":"success"}

Forwarding with a collector

Any file-tailing agent works. Examples (fill in your SIEM's sink + TLS):

Vector

[sources.macrdp_audit]
type = "file"
include = ["/Users/<you>/Library/Logs/macrdp-audit.log"]
read_from = "beginning"

[transforms.macrdp_parse]
type = "remap"
inputs = ["macrdp_audit"]
source = '. = parse_json!(.message)'

# Example sink: Splunk HEC (swap for elasticsearch / datadog_logs / loki / syslog).
[sinks.siem]
type = "splunk_hec_logs"
inputs = ["macrdp_parse"]
endpoint = "https://splunk.example.com:8088"
default_token = "${SPLUNK_HEC_TOKEN}"
[sinks.siem.tls]
verify_certificate = true

Fluent Bit

[INPUT]
    Name        tail
    Path        /Users/<you>/Library/Logs/macrdp-audit.log
    Tag         macrdp.audit
    Parser      json
    Refresh_Interval 5

[OUTPUT]
    Name        splunk
    Match       macrdp.audit
    Host        splunk.example.com
    Port        8088
    Splunk_Token ${SPLUNK_HEC_TOKEN}
    TLS         On

(parsers.conf: a [PARSER] Name json / Format json / Time_Key timestamp entry.)

rsyslog (for shops that specifically want syslog transport)

module(load="imfile")
input(type="imfile"
      File="/Users/<you>/Library/Logs/macrdp-audit.log"
      Tag="macrdp-audit"
      ruleset="macrdp_fwd")

ruleset(name="macrdp_fwd") {
    # $msg already IS the JSON object; forward as RFC5424 to the SIEM over TLS.
    action(type="omfwd" target="siem.example.com" port="6514" protocol="tcp"
           StreamDriver="gtls" StreamDriverMode="1" StreamDriverAuthMode="x509/name")
}

macOS note: rsyslog isn't shipped by Apple; install via Homebrew if you go this route. Vector/Fluent Bit are the lower-friction options on a Mac.

What a SOC gets today, and what could be added

Emitted now: connection accept, reject (rate-limit / lockout, with retry-after), the explicit CredSSP/NLA auth verdict (success / did-not-complete + reason), and disconnect (duration + success/failure outcome). That's the core RDP-server auth telemetry.

Natural follow-ons (each a one-line tracing::info!(target: "macrdp::audit", …) once the schema+sink exist, additive — no schema_version bump): TLS cert load / expiry warning, the health-watchdog bounce, which redirection features are active at startup (drive / smart-card / USB — they widen the trust surface), and an explicit session-start event. A native RFC5424/CEF-over-TLS emitter inside macrdp is a deliberately deferred option for deployments that truly can't run a collector.