From fd3bfdd8e2e3d7b3cb69243ccb004497b5151c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Fri, 26 Jul 2024 12:43:45 +0200 Subject: [PATCH 1/2] syslog-ng-ctl: add test for incorrect escaping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to the following patch, syslog-ng/AxoSyslog escaped control characters and '\' incorrectly. https://github.com/axoflow/axosyslog/pull/224 Signed-off-by: László Várady --- pkg/syslog-ng-ctl/stats_prometheus_test.go | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/syslog-ng-ctl/stats_prometheus_test.go b/pkg/syslog-ng-ctl/stats_prometheus_test.go index ee8e686..7b261c1 100644 --- a/pkg/syslog-ng-ctl/stats_prometheus_test.go +++ b/pkg/syslog-ng-ctl/stats_prometheus_test.go @@ -297,6 +297,34 @@ func TestStatsPrometheus(t *testing.T) { } sortMetricFamilies(expectedDelayMetrics) + expectedEscapeMetrics := []*io_prometheus_client.MetricFamily{ + { + Name: amp("syslogng_classified_output_events_total"), + Type: io_prometheus_client.MetricType_COUNTER.Enum(), + Metric: []*io_prometheus_client.Metric{ + { + Label: []*io_prometheus_client.LabelPair{ + newLabel("app", "MSWinEventLog\\t1\\tSecurity\\t921448325\\tFri"), + newLabel("source", "s_critical_hosts_515"), + }, + Counter: &io_prometheus_client.Counter{ + Value: amp(1.0), + }, + }, + { + Label: []*io_prometheus_client.LabelPair{ + newLabel("app", "\\a\\t\n\"\\xfa\\"), + newLabel("source", "s_unescaped_bug"), + }, + Counter: &io_prometheus_client.Counter{ + Value: amp(1.0), + }, + }, + }, + }, + } + sortMetricFamilies(expectedEscapeMetrics) + testCases := map[string]struct { cc ControlChannel expected []*io_prometheus_client.MetricFamily @@ -322,6 +350,13 @@ func TestStatsPrometheus(t *testing.T) { }), expected: expectedDelayMetrics, }, + "syslog-ng stats prometheus label escaping": { + cc: ControlChannelFunc(func(_ context.Context, cmd string) (rsp string, err error) { + require.Equal(t, "STATS PROMETHEUS", cmd) + return PROMETHEUS_ESCAPE_METRICS_OUTPUT, nil + }), + expected: expectedEscapeMetrics, + }, } for name, testCase := range testCases { @@ -460,6 +495,10 @@ syslogng_output_event_delay_sample_age_seconds{driver="http",url="http://localho syslogng_output_event_delay_sample_age_seconds{transport="tcp",address="localhost:5555",driver="afsocket",id="#anon-destination0#0"} 31 ` +const PROMETHEUS_ESCAPE_METRICS_OUTPUT = `syslogng_classified_output_events_total{app="MSWinEventLog\\t1\\tSecurity\\t921448325\\tFri",source="s_critical_hosts_515"} 1 +syslogng_classified_output_events_total{app="\a\t\n\"\xfa\\",source="s_unescaped_bug"} 1 +` + func metricFamiliesToText(mfs []*io_prometheus_client.MetricFamily) string { var buf strings.Builder for _, mf := range mfs { From 10108445b3fe78ab201491f518e824a38f30e299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20V=C3=A1rady?= Date: Fri, 26 Jul 2024 16:48:26 +0200 Subject: [PATCH 2/2] syslog-ng-ctl: workaround invalid escaping in syslog-ng/AxoSyslog metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: László Várady --- pkg/syslog-ng-ctl/stats_prometheus.go | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/syslog-ng-ctl/stats_prometheus.go b/pkg/syslog-ng-ctl/stats_prometheus.go index e46bac1..67d9a67 100644 --- a/pkg/syslog-ng-ctl/stats_prometheus.go +++ b/pkg/syslog-ng-ctl/stats_prometheus.go @@ -154,6 +154,38 @@ func transformEventDelayMetric(delayMetric *io_prometheus_client.MetricFamily, d delayMetric.Type = io_prometheus_client.MetricType_GAUGE.Enum() } +// Workaround for a bug in older syslog-ng/AxoSyslog versions where the output of STATS PROMETHEUS was overescaped. +// Escapes \ as \\ everywhere except for the allowed sequences: \\, \n, \" +func sanitizeBuggyFormat(output string) string { + var fixedOutput strings.Builder + + length := len(output) + for i := 0; i < length; i++ { + c := output[i] + + if c != '\\' { + fixedOutput.WriteByte(c) + continue + } + + if i+1 >= length { + fixedOutput.WriteString(`\\`) + break + } + + if next := output[i+1]; next == '\\' || next == 'n' || next == '"' { + fixedOutput.WriteByte(c) + fixedOutput.WriteByte(next) + i++ + continue + } + + fixedOutput.WriteString(`\\`) + } + + return fixedOutput.String() +} + func StatsPrometheus(ctx context.Context, cc ControlChannel, lastMetricQueryTime *time.Time) ([]*io_prometheus_client.MetricFamily, error) { rsp, err := cc.SendCommand(ctx, "STATS PROMETHEUS") if err != nil { @@ -169,6 +201,7 @@ func StatsPrometheus(ctx context.Context, cc ControlChannel, lastMetricQueryTime return maps.Values(mfs), err } + rsp = sanitizeBuggyFormat(rsp) mfs, err = new(expfmt.TextParser).TextToMetricFamilies(strings.NewReader(rsp)) var delayMetric *io_prometheus_client.MetricFamily