Skip to content

Commit e338640

Browse files
authored
Apply sanitization to span names too (#43779)
<!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes #43778 Signed-off-by: Israel Blancas <[email protected]>
1 parent 2c7ea61 commit e338640

File tree

5 files changed

+296
-5
lines changed

5 files changed

+296
-5
lines changed

.chloggen/span-name.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: processor/redaction
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Extend database query obfuscation to span names. Previously, database query obfuscation (SQL, Redis, MongoDB) was only applied to span attributes and log bodies. Now it also redacts sensitive data in span names.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [43778]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

processor/redactionprocessor/internal/db/db.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,7 @@ func (o *Obfuscator) ObfuscateAttribute(attributeValue, attributeKey string) (st
157157
func (o *Obfuscator) HasSpecificAttributes() bool {
158158
return o.processAttributesEnabled
159159
}
160+
161+
func (o *Obfuscator) HasObfuscators() bool {
162+
return len(o.obfuscators) > 0
163+
}

processor/redactionprocessor/internal/db/db_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ func TestNewObfuscator(t *testing.T) {
159159
o := NewObfuscator(tt.config)
160160
assert.Len(t, o.obfuscators, tt.expectedObfuscatorCount)
161161
assert.Equal(t, tt.expectedProcessSpecific, o.HasSpecificAttributes())
162+
assert.Equal(t, tt.expectedObfuscatorCount > 0, o.HasObfuscators())
162163
})
163164
}
164165
}

processor/redactionprocessor/processor.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,18 @@ func (s *redaction) processResourceSpan(ctx context.Context, rs ptrace.ResourceS
144144
s.processSpanEvents(ctx, span.Events())
145145

146146
if s.shouldRedactSpanName(&span) {
147-
span.SetName(s.urlSanitizer.SanitizeURL(span.Name()))
147+
name := span.Name()
148+
if s.urlSanitizer != nil {
149+
name = s.urlSanitizer.SanitizeURL(name)
150+
}
151+
if s.dbObfuscator.HasObfuscators() {
152+
var err error
153+
name, err = s.dbObfuscator.Obfuscate(name)
154+
if err != nil {
155+
s.logger.Error(err.Error())
156+
}
157+
}
158+
span.SetName(name)
148159
}
149160
}
150161
}
@@ -426,7 +437,7 @@ func (s *redaction) processStringValueForAttribute(strVal, attributeKey string)
426437
strVal = s.urlSanitizer.SanitizeAttributeURL(strVal, attributeKey)
427438
}
428439

429-
if s.dbObfuscator != nil {
440+
if s.dbObfuscator.HasObfuscators() {
430441
obfuscatedQuery, err := s.dbObfuscator.ObfuscateAttribute(strVal, attributeKey)
431442
if err != nil {
432443
return strVal
@@ -450,7 +461,7 @@ func (s *redaction) processStringValueForLogBody(strVal string) string {
450461
strVal = s.urlSanitizer.SanitizeURL(strVal)
451462
}
452463

453-
if s.dbObfuscator != nil {
464+
if s.dbObfuscator.HasObfuscators() {
454465
obfuscatedQuery, err := s.dbObfuscator.Obfuscate(strVal)
455466
if err != nil {
456467
return strVal
@@ -498,7 +509,7 @@ func (s *redaction) shouldRedactKey(k string) bool {
498509
}
499510

500511
func (s *redaction) shouldRedactSpanName(span *ptrace.Span) bool {
501-
if s.urlSanitizer == nil {
512+
if s.urlSanitizer == nil && !s.dbObfuscator.HasObfuscators() {
502513
return false
503514
}
504515
spanKind := span.Kind()
@@ -507,7 +518,7 @@ func (s *redaction) shouldRedactSpanName(span *ptrace.Span) bool {
507518
}
508519

509520
spanName := span.Name()
510-
if !strings.Contains(spanName, "/") {
521+
if !strings.Contains(spanName, "/") && !s.dbObfuscator.HasObfuscators() {
511522
return false
512523
}
513524
return !s.shouldAllowValue(spanName)

processor/redactionprocessor/processor_test.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"go.opentelemetry.io/collector/pdata/ptrace"
1818
"go.uber.org/zap/zaptest"
1919

20+
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/redactionprocessor/internal/db"
2021
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/redactionprocessor/internal/url"
2122
)
2223

@@ -1791,3 +1792,250 @@ func TestURLSanitizationSpanNameWithBlockedValues(t *testing.T) {
17911792
assert.Equal(t, "GET /api/v1/payments/*", spans.At(0).Name())
17921793
})
17931794
}
1795+
1796+
func TestDBObfuscationSpanName(t *testing.T) {
1797+
t.Run("span name with SQL query should be obfuscated when SQL config enabled", func(t *testing.T) {
1798+
tc := testConfig{
1799+
config: &Config{
1800+
AllowAllKeys: true,
1801+
DBSanitizer: db.DBSanitizerConfig{
1802+
SQLConfig: db.SQLConfig{
1803+
Enabled: true,
1804+
},
1805+
},
1806+
},
1807+
}
1808+
1809+
inBatch := ptrace.NewTraces()
1810+
rs := inBatch.ResourceSpans().AppendEmpty()
1811+
ils := rs.ScopeSpans().AppendEmpty()
1812+
span := ils.Spans().AppendEmpty()
1813+
span.SetName("SELECT * FROM users WHERE id = 123")
1814+
span.SetKind(ptrace.SpanKindClient)
1815+
1816+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1817+
require.NoError(t, err)
1818+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1819+
require.NoError(t, err)
1820+
1821+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1822+
// SQL query should be obfuscated (numbers replaced)
1823+
assert.Contains(t, outSpan.Name(), "SELECT * FROM users WHERE id = ?")
1824+
})
1825+
1826+
t.Run("span name with Redis command should be obfuscated when Redis config enabled", func(t *testing.T) {
1827+
tc := testConfig{
1828+
config: &Config{
1829+
AllowAllKeys: true,
1830+
DBSanitizer: db.DBSanitizerConfig{
1831+
RedisConfig: db.RedisConfig{
1832+
Enabled: true,
1833+
},
1834+
},
1835+
},
1836+
}
1837+
1838+
inBatch := ptrace.NewTraces()
1839+
rs := inBatch.ResourceSpans().AppendEmpty()
1840+
ils := rs.ScopeSpans().AppendEmpty()
1841+
span := ils.Spans().AppendEmpty()
1842+
span.SetName("SET user:12345 value")
1843+
span.SetKind(ptrace.SpanKindClient)
1844+
1845+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1846+
require.NoError(t, err)
1847+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1848+
require.NoError(t, err)
1849+
1850+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1851+
// Redis SET command should have value removed but key retained
1852+
assert.Equal(t, "SET user:12345 ?", outSpan.Name())
1853+
})
1854+
1855+
t.Run("span name without slash should be obfuscated when DB obfuscator enabled", func(t *testing.T) {
1856+
tc := testConfig{
1857+
config: &Config{
1858+
AllowAllKeys: true,
1859+
DBSanitizer: db.DBSanitizerConfig{
1860+
SQLConfig: db.SQLConfig{
1861+
Enabled: true,
1862+
},
1863+
},
1864+
},
1865+
}
1866+
1867+
inBatch := ptrace.NewTraces()
1868+
rs := inBatch.ResourceSpans().AppendEmpty()
1869+
ils := rs.ScopeSpans().AppendEmpty()
1870+
span := ils.Spans().AppendEmpty()
1871+
// No slash in span name
1872+
span.SetName("SELECT count(*) FROM orders WHERE status = 'pending'")
1873+
span.SetKind(ptrace.SpanKindServer)
1874+
1875+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1876+
require.NoError(t, err)
1877+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1878+
require.NoError(t, err)
1879+
1880+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1881+
// Should be obfuscated even without slash
1882+
assert.Contains(t, outSpan.Name(), "SELECT count(*) FROM orders WHERE status = ?")
1883+
})
1884+
1885+
t.Run("span name should be processed by both URL sanitizer and DB obfuscator when both enabled", func(t *testing.T) {
1886+
tc := testConfig{
1887+
config: &Config{
1888+
AllowAllKeys: true,
1889+
URLSanitization: url.URLSanitizationConfig{
1890+
Enabled: true,
1891+
},
1892+
DBSanitizer: db.DBSanitizerConfig{
1893+
SQLConfig: db.SQLConfig{
1894+
Enabled: true,
1895+
},
1896+
},
1897+
},
1898+
}
1899+
1900+
inBatch := ptrace.NewTraces()
1901+
rs := inBatch.ResourceSpans().AppendEmpty()
1902+
ils := rs.ScopeSpans().AppendEmpty()
1903+
span := ils.Spans().AppendEmpty()
1904+
// Span name that could benefit from both sanitizers
1905+
span.SetName("/api/users/123")
1906+
span.SetKind(ptrace.SpanKindClient)
1907+
1908+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1909+
require.NoError(t, err)
1910+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1911+
require.NoError(t, err)
1912+
1913+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1914+
// URL should be sanitized by URL sanitizer
1915+
assert.Equal(t, "/api/users/*", outSpan.Name())
1916+
})
1917+
1918+
t.Run("span name should not be obfuscated when span kind is INTERNAL", func(t *testing.T) {
1919+
tc := testConfig{
1920+
config: &Config{
1921+
AllowAllKeys: true,
1922+
DBSanitizer: db.DBSanitizerConfig{
1923+
SQLConfig: db.SQLConfig{
1924+
Enabled: true,
1925+
},
1926+
},
1927+
},
1928+
}
1929+
1930+
inBatch := ptrace.NewTraces()
1931+
rs := inBatch.ResourceSpans().AppendEmpty()
1932+
ils := rs.ScopeSpans().AppendEmpty()
1933+
span := ils.Spans().AppendEmpty()
1934+
span.SetName("SELECT * FROM users WHERE id = 123")
1935+
span.SetKind(ptrace.SpanKindInternal)
1936+
1937+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1938+
require.NoError(t, err)
1939+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1940+
require.NoError(t, err)
1941+
1942+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1943+
// Should NOT be obfuscated because span kind is INTERNAL
1944+
assert.Equal(t, "SELECT * FROM users WHERE id = 123", outSpan.Name())
1945+
})
1946+
1947+
t.Run("span name with no enabled DB configs should not be DB obfuscated", func(t *testing.T) {
1948+
tc := testConfig{
1949+
config: &Config{
1950+
AllowAllKeys: true,
1951+
DBSanitizer: db.DBSanitizerConfig{
1952+
SQLConfig: db.SQLConfig{
1953+
Enabled: false,
1954+
},
1955+
RedisConfig: db.RedisConfig{
1956+
Enabled: false,
1957+
},
1958+
},
1959+
},
1960+
}
1961+
1962+
inBatch := ptrace.NewTraces()
1963+
rs := inBatch.ResourceSpans().AppendEmpty()
1964+
ils := rs.ScopeSpans().AppendEmpty()
1965+
span := ils.Spans().AppendEmpty()
1966+
// SQL query without slash
1967+
span.SetName("SELECT * FROM users WHERE id = 123")
1968+
span.SetKind(ptrace.SpanKindClient)
1969+
1970+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
1971+
require.NoError(t, err)
1972+
outTraces, err := processor.processTraces(t.Context(), inBatch)
1973+
require.NoError(t, err)
1974+
1975+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
1976+
// Should NOT be obfuscated because no DB configs are enabled and no slash for URL sanitization
1977+
assert.Equal(t, "SELECT * FROM users WHERE id = 123", outSpan.Name())
1978+
})
1979+
1980+
t.Run("span name with Mongo query should be obfuscated when Mongo config enabled", func(t *testing.T) {
1981+
tc := testConfig{
1982+
config: &Config{
1983+
AllowAllKeys: true,
1984+
DBSanitizer: db.DBSanitizerConfig{
1985+
MongoConfig: db.MongoConfig{
1986+
Enabled: true,
1987+
},
1988+
},
1989+
},
1990+
}
1991+
1992+
inBatch := ptrace.NewTraces()
1993+
rs := inBatch.ResourceSpans().AppendEmpty()
1994+
ils := rs.ScopeSpans().AppendEmpty()
1995+
span := ils.Spans().AppendEmpty()
1996+
span.SetName(`{"find":"users","filter":{"_id":"507f1f77bcf86cd799439011"}}`)
1997+
span.SetKind(ptrace.SpanKindClient)
1998+
1999+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
2000+
require.NoError(t, err)
2001+
outTraces, err := processor.processTraces(t.Context(), inBatch)
2002+
require.NoError(t, err)
2003+
2004+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
2005+
// Mongo query should be obfuscated (all values replaced with ?)
2006+
assert.Contains(t, outSpan.Name(), `"find":"?"`)
2007+
assert.Contains(t, outSpan.Name(), `"filter":{"_id":"?"}`)
2008+
})
2009+
2010+
t.Run("span name with multiple database types enabled should be processed sequentially", func(t *testing.T) {
2011+
tc := testConfig{
2012+
config: &Config{
2013+
AllowAllKeys: true,
2014+
DBSanitizer: db.DBSanitizerConfig{
2015+
SQLConfig: db.SQLConfig{
2016+
Enabled: true,
2017+
},
2018+
RedisConfig: db.RedisConfig{
2019+
Enabled: true,
2020+
},
2021+
},
2022+
},
2023+
}
2024+
2025+
inBatch := ptrace.NewTraces()
2026+
rs := inBatch.ResourceSpans().AppendEmpty()
2027+
ils := rs.ScopeSpans().AppendEmpty()
2028+
span := ils.Spans().AppendEmpty()
2029+
span.SetName("SELECT * FROM cache WHERE key = 'user:123'")
2030+
span.SetKind(ptrace.SpanKindServer)
2031+
2032+
processor, err := newRedaction(t.Context(), tc.config, zaptest.NewLogger(t))
2033+
require.NoError(t, err)
2034+
outTraces, err := processor.processTraces(t.Context(), inBatch)
2035+
require.NoError(t, err)
2036+
2037+
outSpan := outTraces.ResourceSpans().At(0).ScopeSpans().At(0).Spans().At(0)
2038+
// Should be processed by all enabled obfuscators
2039+
assert.NotEqual(t, "SELECT * FROM cache WHERE key = 'user:123'", outSpan.Name())
2040+
})
2041+
}

0 commit comments

Comments
 (0)