forked from DataDog/datadog-agent
/
obfuscate.go
183 lines (169 loc) · 5.18 KB
/
obfuscate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
// Package obfuscate implements quantizing and obfuscating of tags and resources for
// a set of spans matching a certain criteria.
package obfuscate
import (
"bytes"
"sync/atomic"
"github.com/StackVista/stackstate-agent/pkg/trace/config"
"github.com/StackVista/stackstate-agent/pkg/trace/pb"
"github.com/StackVista/stackstate-agent/pkg/util/log"
)
//go:generate easyjson -no_std_marshalers $GOFILE
// Obfuscator quantizes and obfuscates spans. The obfuscator is not safe for
// concurrent use.
type Obfuscator struct {
opts *config.ObfuscationConfig
es *jsonObfuscator // nil if disabled
mongo *jsonObfuscator // nil if disabled
sqlExecPlan *jsonObfuscator // nil if disabled
sqlExecPlanNormalize *jsonObfuscator // nil if disabled
creditCards *ccObfuscator // nil if disabled
// sqlLiteralEscapes reports whether we should treat escape characters literally or as escape characters.
// A non-zero value means 'yes'. Different SQL engines behave in different ways and the tokenizer needs
// to be generic.
// Not safe for concurrent use.
sqlLiteralEscapes int32
// queryCache keeps a cache of already obfuscated queries.
queryCache *measuredCache
}
// SQLOptions holds options that change the behavior of the obfuscator for SQL.
// easyjson:json
type SQLOptions struct {
// ReplaceDigits causes the obfuscator to replace digits in identifiers and table names with question marks.
ReplaceDigits bool `json:"replace_digits"`
}
// SetSQLLiteralEscapes sets whether or not escape characters should be treated literally by the SQL obfuscator.
func (o *Obfuscator) SetSQLLiteralEscapes(ok bool) {
if ok {
atomic.StoreInt32(&o.sqlLiteralEscapes, 1)
} else {
atomic.StoreInt32(&o.sqlLiteralEscapes, 0)
}
}
// SQLLiteralEscapes reports whether escape characters should be treated literally by the SQL obfuscator.
func (o *Obfuscator) SQLLiteralEscapes() bool {
return atomic.LoadInt32(&o.sqlLiteralEscapes) == 1
}
// NewObfuscator creates a new obfuscator
func NewObfuscator(cfg *config.ObfuscationConfig) *Obfuscator {
if cfg == nil {
cfg = new(config.ObfuscationConfig)
}
o := Obfuscator{
opts: cfg,
queryCache: newMeasuredCache(),
}
if cfg.ES.Enabled {
o.es = newJSONObfuscator(&cfg.ES, &o)
}
if cfg.Mongo.Enabled {
o.mongo = newJSONObfuscator(&cfg.Mongo, &o)
}
if cfg.SQLExecPlan.Enabled {
o.sqlExecPlan = newJSONObfuscator(&cfg.SQLExecPlan, &o)
}
if cfg.SQLExecPlanNormalize.Enabled {
o.sqlExecPlanNormalize = newJSONObfuscator(&cfg.SQLExecPlanNormalize, &o)
}
if cfg.CreditCards.Enabled {
o.creditCards = newCreditCardsObfuscator(cfg.CreditCards.Luhn)
}
return &o
}
// Stop cleans up after a finished Obfuscator.
func (o *Obfuscator) Stop() {
o.queryCache.Close()
o.creditCards.Disable()
}
// Obfuscate may obfuscate span's properties based on its type and on the Obfuscator's
// configuration.
func (o *Obfuscator) Obfuscate(span *pb.Span) {
switch span.Type {
case "sql", "cassandra":
o.obfuscateSQL(span)
case "redis":
o.quantizeRedis(span)
if o.opts.Redis.Enabled {
o.obfuscateRedis(span)
}
case "memcached":
if o.opts.Memcached.Enabled {
o.obfuscateMemcached(span)
}
case "web", "http":
o.obfuscateHTTP(span)
case "mongodb":
o.obfuscateJSON(span, "mongodb.query", o.mongo)
case "elasticsearch":
o.obfuscateJSON(span, "elasticsearch.body", o.es)
}
}
// ObfuscateStatsGroup obfuscates the given stats bucket group.
func (o *Obfuscator) ObfuscateStatsGroup(b *pb.ClientGroupedStats) {
switch b.Type {
case "sql", "cassandra":
oq, err := o.ObfuscateSQLString(b.Resource)
if err != nil {
log.Errorf("Error obfuscating stats group resource %q: %v", b.Resource, err)
b.Resource = nonParsableResource
} else {
b.Resource = oq.Query
}
case "redis":
b.Resource = o.QuantizeRedisString(b.Resource)
}
}
// compactWhitespaces compacts all whitespaces in t.
func compactWhitespaces(t string) string {
n := len(t)
r := make([]byte, n)
spaceCode := uint8(32)
isWhitespace := func(char uint8) bool { return char == spaceCode }
nr := 0
offset := 0
for i := 0; i < n; i++ {
if isWhitespace(t[i]) {
copy(r[nr:], t[nr+offset:i])
r[i-offset] = spaceCode
nr = i + 1 - offset
for j := i + 1; j < n; j++ {
if !isWhitespace(t[j]) {
offset += j - i - 1
i = j
break
} else if j == n-1 {
offset += j - i
i = j
break
}
}
}
}
copy(r[nr:], t[nr+offset:n])
r = r[:n-offset]
return string(bytes.Trim(r, " "))
}
// replaceDigits replaces consecutive sequences of digits with '?',
// example: "jobs_2020_1597876964" --> "jobs_?_?"
func replaceDigits(buffer []byte) []byte {
scanningDigit := false
filtered := buffer[:0]
for _, b := range buffer {
// digits are encoded as 1 byte in utf8
if isDigit(rune(b)) {
if scanningDigit {
continue
}
scanningDigit = true
filtered = append(filtered, byte('?'))
continue
}
scanningDigit = false
filtered = append(filtered, b)
}
return filtered
}