Skip to content

Commit bd01336

Browse files
committed
[processor/redaction] Apply blocked_values patterns in configuration order
1 parent 4c1583c commit bd01336

4 files changed

Lines changed: 102 additions & 9 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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: bug_fix
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: Apply `blocked_values` patterns in the order they are listed in the configuration instead of a nondeterministic order
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: [49858]
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+
Previously the patterns were applied in Go map iteration order. When two patterns
20+
could match overlapping parts of the same value, the result changed from run to run,
21+
and some orders left data unmasked that another order would have redacted.
22+
23+
# If your change doesn't affect end users or the exported elements of any package,
24+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
25+
# Optional: The change log or logs in which this entry should be included.
26+
# e.g. '[user]' or '[user, api]'
27+
# Include 'user' if the change is relevant to end users.
28+
# Include 'api' if there is a change to a library API.
29+
# Default: '[user]'
30+
change_logs: [user]

processor/redactionprocessor/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ processors:
8787
- ".*token.*"
8888
- ".*api_key.*"
8989
# blocked_values is a list of regular expressions for blocking values of
90-
# allowed span attributes. Values that match are masked
90+
# allowed span attributes. Values that match are masked. The patterns are
91+
# applied in the order they are listed.
9192
blocked_values:
9293
- "4[0-9]{12}(?:[0-9]{3})?" ## Visa credit card number
9394
- "(5[1-5][0-9]{14})" ## MasterCard number
@@ -139,6 +140,11 @@ part of the value is not masked even if it matches the regular expression for a
139140
If the value matches the regular expression for a blocked value only, the matching
140141
part of the value is masked with a fixed length of asterisks.
141142

143+
The `blocked_values` patterns are applied in the order they are listed in the
144+
configuration. When two patterns can match overlapping parts of the same value,
145+
the pattern listed first is applied first, which determines what the later
146+
pattern can still match.
147+
142148
### Precedence between `allowed_values` and `blocked_values`
143149

144150
When both `allowed_values` and `blocked_values` are configured, `allowed_values` takes precedence.

processor/redactionprocessor/processor.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ type redaction struct {
3838
// Attribute keys ignored in a span
3939
ignoreList map[string]string
4040
// Attribute key patterns ignored in a span
41-
ignoreKeyRegexList map[string]*regexp.Regexp
41+
ignoreKeyRegexList []*regexp.Regexp
4242
// Attribute values blocked in a span
43-
blockRegexList map[string]*regexp.Regexp
43+
blockRegexList []*regexp.Regexp
4444
// Attribute values allowed in a span
45-
allowRegexList map[string]*regexp.Regexp
45+
allowRegexList []*regexp.Regexp
4646
// Attribute keys blocked in a span
47-
blockKeyRegexList map[string]*regexp.Regexp
47+
blockKeyRegexList []*regexp.Regexp
4848
// Hash function to hash blocked values
4949
hashFunction HashFunction
5050
// Redaction processor configuration
@@ -631,16 +631,17 @@ func makeIgnoreList(c *Config) map[string]string {
631631
return ignoreList
632632
}
633633

634-
// makeRegexList precompiles all the regex patterns in the defined list
635-
func makeRegexList(_ context.Context, valuesList []string) (map[string]*regexp.Regexp, error) {
636-
regexList := make(map[string]*regexp.Regexp, len(valuesList))
634+
// makeRegexList precompiles all the regex patterns in the defined list,
635+
// preserving the order in which they are listed in the configuration
636+
func makeRegexList(_ context.Context, valuesList []string) ([]*regexp.Regexp, error) {
637+
regexList := make([]*regexp.Regexp, 0, len(valuesList))
637638
for _, pattern := range valuesList {
638639
re, err := regexp.Compile(pattern)
639640
if err != nil {
640641
// TODO: Placeholder for an error metric in the next PR
641642
return nil, fmt.Errorf("error compiling regex in list: %w", err)
642643
}
643-
regexList[pattern] = re
644+
regexList = append(regexList, re)
644645
}
645646
return regexList, nil
646647
}

processor/redactionprocessor/processor_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,62 @@ func TestMultipleBlockValues(t *testing.T) {
10431043
}
10441044
}
10451045

1046+
// TestBlockedValuesAppliedInConfigOrder validates that blocked_values patterns
1047+
// are applied in the order they are listed in the configuration. When two
1048+
// patterns match overlapping regions of the same value, the outcome depends on
1049+
// the application order, so the order must be deterministic and follow the
1050+
// configuration.
1051+
func TestBlockedValuesAppliedInConfigOrder(t *testing.T) {
1052+
emailPattern := `[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}`
1053+
postalCodePattern := `\b\d{3}-\d{4}\b`
1054+
// The local part of this email address also matches the postal code
1055+
// pattern, so the two patterns overlap on this value.
1056+
input := "777-7777@example.com"
1057+
1058+
testCases := []struct {
1059+
name string
1060+
blockedValues []string
1061+
expected string
1062+
}{
1063+
{
1064+
name: "email pattern first masks the whole address",
1065+
blockedValues: []string{emailPattern, postalCodePattern},
1066+
expected: "****",
1067+
},
1068+
{
1069+
name: "postal code pattern first masks only the local part",
1070+
blockedValues: []string{postalCodePattern, emailPattern},
1071+
expected: "****@example.com",
1072+
},
1073+
}
1074+
1075+
for _, tt := range testCases {
1076+
t.Run(tt.name, func(t *testing.T) {
1077+
// Repeat to make a regression to nondeterministic ordering fail
1078+
// reliably instead of intermittently.
1079+
for i := 0; i < 10; i++ {
1080+
config := &Config{
1081+
AllowAllKeys: true,
1082+
BlockedValues: tt.blockedValues,
1083+
}
1084+
processor, err := newRedaction(t.Context(), config, zaptest.NewLogger(t))
1085+
require.NoError(t, err)
1086+
1087+
attrs := pcommon.NewMap()
1088+
attrs.PutStr("email", input)
1089+
processor.processAttrs(t.Context(), attrs)
1090+
val, found := attrs.Get("email")
1091+
require.True(t, found)
1092+
assert.Equal(t, tt.expected, val.Str())
1093+
1094+
body := pcommon.NewValueStr(input)
1095+
processor.processLogBody(t.Context(), body, pcommon.NewMap())
1096+
assert.Equal(t, tt.expected, body.Str())
1097+
}
1098+
})
1099+
}
1100+
}
1101+
10461102
// TestProcessAttrsAppliedTwice validates a use case when data is coming through redaction processor more than once.
10471103
// Existing attributes must be updated, not overridden or ignored.
10481104
func TestProcessAttrsAppliedTwice(t *testing.T) {

0 commit comments

Comments
 (0)