[Discussion] Configurable traffic-log sinks; write traffic logs to a file or push them to a log platform #3202
DinithHerath
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Problem
The traffic log is the only thing the gateway emits that deliberately carries request and
response bodies, and today it has exactly one destination, hard-coded:
Everything unwelcome follows from that single field:
/var/log/pods/…on the node.kubectl logson the namespace can read unredacted PII.Forwarder on the host — picks them up and ships them onward automatically, with no
configuration. On a cluster that already forwards container logs to a SIEM, enabling
[traffic_logging]silently starts sending request bodies to a long-retention,org-wide-searchable index.
hostPath).Several customers are not permitted to run either.
[pol]stdout prefix, which forces a fragilestrip-the-prefix parser downstream. A JSON parse failure there previously let an
unredacted line through to a log store.
This is structural to the stdout path — no downstream configuration fixes it, because the
unredacted copy already exists on disk by the time anything else sees it.
Architecure
Proposed solution
Introduce a sink abstraction behind the existing
Logpublisher and add two sinks. Both areconfig-level, and neither changes default behaviour — absent new config the gateway writes
to stdout exactly as before.
filekubectl logsand from every node-level collectorhttpThey compose:
outputs = ["file", "http"]writes both.Configuration
The
headerauth type is not cosmetic: Splunk HEC expectsAuthorization: Splunk <token>,which
bearercannot express.The NDJSON body is accepted as-is by Splunk HEC
/raw, Fluent Bit'shttpinput, theOpenTelemetry Collector, Datadog's log intake, and (via a collector) Elasticsearch/OpenSearch.
Design guarantees
These are the properties the tests exist to defend:
outputskey ⇒["stdout"]⇒ byte-identical output.stdout, which would put bodies back into the container log — the exact disclosure a
file/http sink is configured to prevent.
Writenever blocks. It runs on the ALS ingest path; blocking backpressures Envoy.0600inside a0700directory, both established at creation.shutdown_timeout.Note (3): unlike Fluent Bit or the OTel Collector, the HTTP sink's queue is memory-only, so a
receiver outage costs events rather than delaying them. That is the deliberate trade for never
touching disk, and it is why
dropped_totalexists.Metrics
policy_engine_traffic_log_written_totalsinkpolicy_engine_traffic_log_dropped_totalsink,reasonpolicy_engine_traffic_log_queue_depthsinkpolicy_engine_traffic_log_flush_duration_secondssinkpolicy_engine_traffic_log_write_errors_totalsink,codedropped_totalis the series to alert on; the rest exist to diagnose it.Related change: publisher lifecycle
Moesif.Close()existed but was never called from anywhere —Analyticswas constructedinside
newAccessLogServiceServerand never surfaced tomain.go, so shutdown stopped thegRPC server and exited without flushing any publisher. Harmless for unbuffered stdout; it would
lose the HTTP sink's in-flight batch on every restart, rolling update and scale-down.
Added a
Closerinterface,Analytics.Close(ctx), and wired it intomain.goafteralsServer.GracefulStop()so the flush cannot race arriving events.Scope of change
New
Modified
No new Go dependencies. Rotation is ~40 lines of stdlib rather than a logging library.
Helm
traffic_logging.file/.httpsub-tables rendered intoconfig.toml.emptyDirfor the file sink, auto-enabled whenoutputscontainsfileso apath cannot be configured with nowhere to write it. Mounted at the parent of the log
directory, because the kubelet creates a mount point
0777and a volume mount cannot set adirectory mode — mounting one level up lets the sink create the leaf at
0700.gateway.config_tomlnow renders before the first[section]instead of being appended.TOML cannot return to the root document once a table is open, so a root-level key written
after the generated sections silently became a member of
[mcp]. This also makes policysystemParameters(${config.<key>}) settable for the first time.Known gaps / follow-ups
ErrorUnusedon the config decoder. Unknown keys are silently discarded today, so atypo'd
pth = …is a no-op. The sink validation catches the cases that matter here(unknown sink name, unusable sink), but this remains a trap for every other config key.
Deserves its own PR — turning it on will break any deployment carrying a stale key.
spill_to_filewhen the HTTP receiver is down. The right answer to the durabilitytrade, deferred because it reintroduces PII at rest and deserves its own decision.
Sinkinterface; HTTP covers everyreceiver we have needed so far.
[collector].ignore_path_prefixesdoes not match the real health-probe path(
/_gateway-health/ready), so probes dominate traffic-log volume. Small, unrelated fix.Out of scope
redaction step composes cleanly with both.
[rtr],[pol],[pye]) — still stdout. Atlogging.level = "debug"those can also contain payload fragments; moving the traffic log does not move those.
Related Issues
#3201
All reactions