Skip to content

Commit f30716e

Browse files
PierreFdanielnelson
authored andcommitted
Whitelist allowed char classes for graphite output (#3473)
1 parent 3405dee commit f30716e

File tree

3 files changed

+118
-4
lines changed

3 files changed

+118
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
- [#3351](https://github.com/influxdata/telegraf/issues/3351): Fix prometheus passthrough for existing value types.
6868
- [#3430](https://github.com/influxdata/telegraf/issues/3430): Always ignore autofs filesystems in disk input.
6969
- [#3326](https://github.com/influxdata/telegraf/issues/3326): Fail metrics parsing on unescaped quotes.
70+
- [#3473](https://github.com/influxdata/telegraf/pull/3473): Whitelist allowed char classes for graphite output.
7071

7172
## v1.4.4 [2017-11-08]
7273

plugins/serializers/graphite/graphite.go

+24-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package graphite
22

33
import (
44
"fmt"
5+
"regexp"
56
"sort"
67
"strings"
78

@@ -11,8 +12,18 @@ import (
1112
const DEFAULT_TEMPLATE = "host.tags.measurement.field"
1213

1314
var (
14-
fieldDeleter = strings.NewReplacer(".FIELDNAME", "", "FIELDNAME.", "")
15-
sanitizedChars = strings.NewReplacer("/", "-", "@", "-", "*", "-", " ", "_", "..", ".", `\`, "", ")", "_", "(", "_")
15+
allowedChars = regexp.MustCompile(`[^a-zA-Z0-9-:._=\p{L}]`)
16+
hypenChars = strings.NewReplacer(
17+
"/", "-",
18+
"@", "-",
19+
"*", "-",
20+
)
21+
dropChars = strings.NewReplacer(
22+
`\`, "",
23+
"..", ".",
24+
)
25+
26+
fieldDeleter = strings.NewReplacer(".FIELDNAME", "", "FIELDNAME.", "")
1627
)
1728

1829
type GraphiteSerializer struct {
@@ -44,7 +55,7 @@ func (s *GraphiteSerializer) Serialize(metric telegraf.Metric) ([]byte, error) {
4455
}
4556
metricString := fmt.Sprintf("%s %#v %d\n",
4657
// insert "field" section of template
47-
sanitizedChars.Replace(InsertField(bucket, fieldName)),
58+
sanitize(InsertField(bucket, fieldName)),
4859
value,
4960
timestamp)
5061
point := []byte(metricString)
@@ -122,7 +133,7 @@ func InsertField(bucket, fieldName string) string {
122133
if fieldName == "value" {
123134
return fieldDeleter.Replace(bucket)
124135
}
125-
return strings.Replace(bucket, "FIELDNAME", fieldName, 1)
136+
return strings.Replace(bucket, "FIELDNAME", strings.Replace(fieldName, ".", "_", -1), 1)
126137
}
127138

128139
func buildTags(tags map[string]string) string {
@@ -143,3 +154,12 @@ func buildTags(tags map[string]string) string {
143154
}
144155
return tag_str
145156
}
157+
158+
func sanitize(value string) string {
159+
// Apply special hypenation rules to preserve backwards compatibility
160+
value = hypenChars.Replace(value)
161+
// Apply rule to drop some chars to preserve backwards compatibility
162+
value = dropChars.Replace(value)
163+
// Replace any remaining illegal chars
164+
return allowedChars.ReplaceAllLiteralString(value, "_")
165+
}

plugins/serializers/graphite/graphite_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1112

1213
"github.com/influxdata/telegraf/metric"
1314
)
@@ -468,3 +469,95 @@ func TestTemplate6(t *testing.T) {
468469
expS := "localhost.cpu0.us-west-2.cpu.FIELDNAME"
469470
assert.Equal(t, expS, mS)
470471
}
472+
473+
func TestClean(t *testing.T) {
474+
now := time.Unix(1234567890, 0)
475+
tests := []struct {
476+
name string
477+
metric_name string
478+
tags map[string]string
479+
fields map[string]interface{}
480+
expected string
481+
}{
482+
{
483+
"Base metric",
484+
"cpu",
485+
map[string]string{"host": "localhost"},
486+
map[string]interface{}{"usage_busy": float64(8.5)},
487+
"localhost.cpu.usage_busy 8.5 1234567890\n",
488+
},
489+
{
490+
"Dot and whitespace in tags",
491+
"cpu",
492+
map[string]string{"host": "localhost", "label.dot and space": "value with.dot"},
493+
map[string]interface{}{"usage_busy": float64(8.5)},
494+
"localhost.value_with_dot.cpu.usage_busy 8.5 1234567890\n",
495+
},
496+
{
497+
"Field with space",
498+
"system",
499+
map[string]string{"host": "localhost"},
500+
map[string]interface{}{"uptime_format": "20 days, 23:26"},
501+
"", // yes nothing. graphite don't serialize string fields
502+
},
503+
{
504+
"Allowed punct",
505+
"cpu",
506+
map[string]string{"host": "localhost", "tag": "-_:="},
507+
map[string]interface{}{"usage_busy": float64(10)},
508+
"localhost.-_:=.cpu.usage_busy 10 1234567890\n",
509+
},
510+
{
511+
"Special conversions to hyphen",
512+
"cpu",
513+
map[string]string{"host": "localhost", "tag": "/@*"},
514+
map[string]interface{}{"usage_busy": float64(10)},
515+
"localhost.---.cpu.usage_busy 10 1234567890\n",
516+
},
517+
{
518+
"Special drop chars",
519+
"cpu",
520+
map[string]string{"host": "localhost", "tag": `\no slash`},
521+
map[string]interface{}{"usage_busy": float64(10)},
522+
"localhost.no_slash.cpu.usage_busy 10 1234567890\n",
523+
},
524+
{
525+
"Empty tag & value field",
526+
"cpu",
527+
map[string]string{"host": "localhost"},
528+
map[string]interface{}{"value": float64(10)},
529+
"localhost.cpu 10 1234567890\n",
530+
},
531+
{
532+
"Unicode Letters allowed",
533+
"cpu",
534+
map[string]string{"host": "localhost", "tag": "μnicodε_letters"},
535+
map[string]interface{}{"value": float64(10)},
536+
"localhost.μnicodε_letters.cpu 10 1234567890\n",
537+
},
538+
{
539+
"Other Unicode not allowed",
540+
"cpu",
541+
map[string]string{"host": "localhost", "tag": "“☢”"},
542+
map[string]interface{}{"value": float64(10)},
543+
"localhost.___.cpu 10 1234567890\n",
544+
},
545+
{
546+
"Newline in tags",
547+
"cpu",
548+
map[string]string{"host": "localhost", "label": "some\nthing\nwith\nnewline"},
549+
map[string]interface{}{"usage_busy": float64(8.5)},
550+
"localhost.some_thing_with_newline.cpu.usage_busy 8.5 1234567890\n",
551+
},
552+
}
553+
554+
s := GraphiteSerializer{}
555+
for _, tt := range tests {
556+
t.Run(tt.name, func(t *testing.T) {
557+
m, err := metric.New(tt.metric_name, tt.tags, tt.fields, now)
558+
assert.NoError(t, err)
559+
actual, _ := s.Serialize(m)
560+
require.Equal(t, tt.expected, string(actual))
561+
})
562+
}
563+
}

0 commit comments

Comments
 (0)